Search code examples
wpfvb.netinotifypropertychangedbyref

Property vs. Variable as ByRef parameter


I created a base class that implements the INotifyPropertyChanged interface. This class also contains a generic function SetProperty to set the value of any property and raise the PropertyChanged event, if necessary.

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

Then I have a class, that is supposed to hold some data. For the sake of simplicity it only contains one property (in this example).

Public Class Item
    Public Property Text As String
End Class

Then I have a third class that inherits from the base class and uses the data holding class. This third class is supposed to be a ViewModel for a WPF window.

I don't list the code for the RelayCommand class, since you probably all have an implementation yourself. Just keep in mind, that this class executes the given function, when the command is executed.

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

And then I have my WPF window that uses the ViewModel class as its 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>

As you can see, this window contains only two TextBoxes and a button. The TextBoxes are bound to the properties Text1 and Text2 and the button is supposed to execute the command TestCommand.

When the command is executed both properties Text1 and Text2 is given a value. And since both properties raise the PropertyChanged event, these values should be shown in my window.

But only the value "Text2" is shown in my window.

The value of property Text1 is "Text1", but it seems that the PropertyChanged event for this property is raised before the property got its value.

Is there any way to change the SetProperty function in my base class to raise the PropertyChanged after the property got its value?

Thank you for your help.


Solution

  • What actually happens ?

    This doesn't work because the properties don't behave as fields do.

    When you do Me.SetProperty(Of String)(_text2, value), what happens is that the reference to the field _text2 is passed instead of its value, so the SetProperty function can modify what's inside the reference, and the field is modified.

    However, when you do Me.SetProperty(Of String)(_text1.Text, value), the compiler sees a getter for a property, so it will first call the Get property of _text1, then pass the reference to the return value as parameter. So when your function SetProperty is receving the ByRef parameter, it is the return value from the getter, and not the actual field value.

    From what I understood here, if you say that your property is ByRef, the compiler will automatically change the field ref when you exit the function call... So that would explain why it's changing after your event...

    This other blog seems to confirm this strange behavior.