我创建了一个实现INotifyPropertyChanged
接口的基类。此类还包含一个通用函数SetProperty
来设置任何属性的值并在必要时提高PropertyChanged
事件。
Public Class BaseClass
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional ByVal propertyName As String = Nothing) As Boolean
If Object.Equals(storage, value) Then
Return False
End If
storage = value
Me.OnPropertyChanged(propertyName)
Return True
End Function
Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional ByVal propertyName As String = Nothing)
If String.IsNullOrEmpty(propertyName) Then
Throw New ArgumentNullException(NameOf(propertyName))
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
然后我有一个课程,应该保存一些数据。为了简单起见,它仅包含一个属性(在此示例中)。
Public Class Item
Public Property Text As String
End Class
然后,我有一个从基类继承并使用数据保存类的第三类。该第三类应该是WPF窗口的ViewModel。
我不列出RelayCommand
类的代码,因为您可能自己都有一个实现。请记住,该类执行命令时执行给定函数。
Public Class ViewModel
Inherits BaseClass
Private _text1 As Item 'data holding class
Private _text2 As String 'simple variable
Private _testCommand As ICommand = New RelayCommand(AddressOf Me.Test)
Public Sub New()
_text1 = New Item
End Sub
Public Property Text1 As String
Get
Return _text1.Text
End Get
Set(ByVal value As String)
Me.SetProperty(Of String)(_text1.Text, value)
End Set
End Property
Public Property Text2 As String
Get
Return _text2
End Get
Set(ByVal value As String)
Me.SetProperty(Of String)(_text2, value)
End Set
End Property
Public ReadOnly Property TestCommand As ICommand
Get
Return _testCommand
End Get
End Property
Private Sub Test()
Me.Text1 = "Text1"
Me.Text2 = "Text2"
End Sub
End Class
然后我有使用ViewModel类作为DataContext
的WPF窗口。
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text1}" Height="24" Width="100" />
<TextBox Text="{Binding Text2}" Height="24" Width="100" />
<Button Height="24" Content="Fill" Command="{Binding TestCommand}" />
</StackPanel>
</Window>
您可以看到,此窗口仅包含两个文本框和一个按钮。文本框绑定到属性Text1
和Text2
,该按钮应该执行命令TestCommand
。
执行命令时,属性 Text1
和 Text2
均具有值。而且由于这两个属性都引发了PropertyChanged
事件,因此应在我的窗口中显示这些值。
,但仅在我的窗口中显示了值" text2"。
属性Text1
的值是" text1",但是在该属性获得其值之前,该属性的 PropertyChanged
事件似乎是升高的。
是否有任何方法可以在我的基类中更改SetProperty
功能以在属性获得其值后提高PropertyChanged
?
谢谢您的帮助。
实际发生了什么?
这不起作用,因为属性不像字段那样行事。
执行Me.SetProperty(Of String)(_text2, value)
时,发生的是传递对字段_text2
的引用,而不是其值,因此SetProperty
函数可以修改参考中的内容,并修改了字段。
但是,当您执行Me.SetProperty(Of String)(_text1.Text, value)
时,编译器会看到属性的Getter,因此它将首先调用 get get _text1 的属性,然后将引用传递到返回值作为参数。因此,当您的函数SetProperty
接收ByRef
参数时,它是getter的返回值,,而不是实际字段值。。
根据我在这里了解的内容,如果您说您的属性是Byref,则编译器将在退出函数调用时自动更改字段ref ...因此,这可以解释为什么事件发生后它会更改... p>这个其他博客似乎确认了这种奇怪的行为。
在C#中,等效代码不会编译。.net不愿意通过引用传递属性,因为像埃里克·利珀特(Eric Lippert)这样的人已经进入其他地方(我昏昏欲睡地回忆起埃里克(Eric这将需要一个怪异的解决方法或另一个,所有这些都有C#团队认为是不可接受的缺点)。
vb做到了,但是作为一个相当奇怪的特殊情况:我看到的行为是我期望的,如果它创建了一个临时变量,该变量是通过引用传递的,然后将其值分配给属性之后方法完成。这是一个解决方法(由埃里克·利普特(Eric Lippert)在下面的评论中确认,另请参见@martin verjans的出色答案),副作用对于任何不知道如何在.NET中实现byref
/ref
的人违反直觉。
当您考虑它时,它们不能使其正常工作,因为VB.NET和C#(以及F#,以及Ironpython等)必须相互兼容,因此VB ByRef
参数必须与从C#代码传递的C#ref
参数兼容。因此,任何解决方法都必须完全是呼叫者的责任。在理智的范围内,这将其限制为在呼叫开始之前和返回之后可以做的事情。
这是ECMA 335(通用语言基础架构)标准必须说的( ctrl f 搜索" byref
"):
-
&sect; i.8.2.1.1&nbsp;托管指针和相关类型
a 托管指针(§i.12.1.1.2)或 byRef (§i.8.6.1.3,§i.12.4.1.5.2),可以指向数组的复合类型的局部变量,参数,字段。...
换句话说,就编译器而言,ByRef storage As T
实际上是存储位置中代码放置值的存储位置的地址。在运行时非常有效,但没有提供句法糖魔法的范围。属性是 一对方法,一个getter和setter(当然只有一个或另一个)。
因此,如您所描述的那样,storage
在SetProperty()
内获取新值,并且SetProperty()
完成后,_text1.Text
具有新值。但是编译器引入了一些神秘的恶作剧,这导致实际事件的实际顺序不是您所期望的。
结果,SetProperty
不能以您编写的方式在Text1
中使用。我已经测试的最简单修复程序是直接在Text1
的设置器中调用OnPropertyChanged()
。
Public Property Text1 As String
Get
Return _text1.Text
End Get
Set(ByVal value As String)
_text1.Text = value
Me.OnPropertyChanged()
End Set
End Property
没有办法处理至少一点丑陋的方法。您可以像Text2
一样给Text1
一个常规的备份字段,但是您需要将其与_text1.Text
同步。这比上述IMO丑,因为您必须将两者保持同步,并且在Text1
设置器中仍然有额外的代码。