我创建了一个实现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
然后我的WPF窗口使用ViewModel类作为其DataContext
。
<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>
如您所见,此窗口仅包含两个TextBox和一个按钮。 TextBoxes绑定到属性Text1
和Text2
,按钮应该执行命令TestCommand
。
执行命令时,属性Text1
和Text2
都会被赋予一个值。由于这两个属性都会引发PropertyChanged
事件,因此这些值应显示在我的窗口中。
但只有价值&#34; Text2&#34;在我的窗口中显示。
属性Text1
的值是&#34; Text1&#34;,但似乎在属性获取其值之前引发了此属性的PropertyChanged
事件。
有没有办法在我的基类中更改SetProperty
函数,以便在属性获得值后提升PropertyChanged
?
感谢您的帮助。
答案 0 :(得分:4)
实际发生了什么?
这不起作用,因为属性不像字段那样。
执行Me.SetProperty(Of String)(_text2, value)
时,会发生对字段_text2
的引用而不是其值的引用,因此SetProperty
函数可以修改引用内的内容,以及字段已修改。
但是,当你执行Me.SetProperty(Of String)(_text1.Text, value)
时,编译器会看到属性的getter,因此它将首先调用 _text1 的Get属性,然后将引用传递给返回值as参数。因此,当您的函数SetProperty
正在接收ByRef
参数时,它是getter的返回值,而不是实际的字段值。
根据我的理解here,如果您说您的属性是ByRef,编译器将在您退出函数调用时自动更改字段引用...这样就可以解释为什么它在事件发生后会发生变化。 ..
This other blog似乎证实了这种奇怪的行为。
答案 1 :(得分:3)
在C#中,等效代码无法编译。 .NET通过引用传递属性并不方便,原因就像Eric Lippert这样的人已经进入其他地方(我模糊地回忆起Eric在某个地方看到了C#,但现在找不到它 - 松散地说,它需要一个奇怪的解决方法或另一个,所有这些都有C#团队认为不可接受的缺点)。
VB是这样做的,但作为一个相当奇怪的特殊情况:我所看到的行为是我所期望的,如果它创建一个临时变量,通过引用传递,然后然后将其值分配给属性方法完成。这是一种解决方法(由Eric Lippert在下面的评论中确认,另见@Martin Verjans的优秀答案)副作用与任何不知道如何实施byref
/ ref
的人相反在.NET中。
当你考虑它时,它们不能使它正常工作,因为VB.NET和C#(和F#,和IronPython等等)必须相互兼容,所以a VB ByRef
参数必须与从C#代码传入的C#ref
参数兼容。因此,任何解决方法都必须完全是来电者的责任。在理智的范围内,这限制了它在呼叫开始之前以及在它返回之后可以做的事情。
以下是ECMA 335 (Common Language Infrastructure) standard必须说的内容( Ctrl + F 搜索“byref
”):
§I.8.2.1.1托管指针及相关类型
托管指针(§I.12.1.1.2)或 byref (§I.8.6.1.3,§I.12.4.1.5.2),可以指向局部变量,参数,复合类型的字段或数组的元素。 ...
换句话说,就编译器而言,ByRef storage As T
实际上是代码放置值的内存中存储位置的地址。它在运行时非常有效,但没有为getter和setter提供语法糖魔法的空间。属性是一对方法,一个getter和一个setter(当然只是一个或另一个)。
正如您所描述的那样,storage
在SetProperty()
中获取新值,在SetProperty()
完成后,_text1.Text
具有新值。但编译器引入了一些隐匿的恶作剧,导致事件的实际序列不符合您的预期。
因此,SetProperty
无法以Text1
的方式使用OnPropertyChanged()
。我测试过的最简单的修复方法是直接在Text1
的setter中调用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
。
Text1
没有办法处理这个至少有点难看的事情。您可以为Text2
提供_text1.Text
常规支持字段,但是您需要将其与Text1
保持同步。这比上面的IMO更丑陋,因为你必须保持两者同步,并且你仍然在HADOOP_CONF_DIR
setter中有额外的代码。