There are many examples of using INotifyPropertyChanged on the web but I could not find one example written in VB so here is my explanation of how you know you need it and how to implement it in VB.
Let's start with a very simple page.
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid Name="NumbersDataGrid" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding Numbers}" AutoGenerateColumns="False" IsReadOnly="true">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding Number}"/>
</DataGrid.Columns>
</DataGrid>
<Button Name="AddButton" Grid.Row="1" Grid.Column="0" Content="Add Row" Click="AddButton_Click"></Button>
<Button Name="DeleteButton" Grid.Row="1" Grid.Column="1" Content="Delete Row" Click="DeleteButton_Click"></Button>
<Button Name="IncrementButton" Grid.Row="1" Grid.Column="2" Content="Increment" Click="IncrementButton_Click"></Button>
</Grid>
</Window>
This XAML defines a window whose DataContext is its own code behind. The window contains a DataGrid that is bound to a public property called Numbers. It also has three buttons to add and delete rows, and increment the number in the first row. The code behind looks like this...
Class MainWindow
Public Class cNumber
Public Property Number As Integer
Public Sub New(Number As Integer)
Me.Number = Number
End Sub
End Class
Public Property Numbers As New System.Collections.Generic.List(Of cNumber) From {New cNumber(1)}
Private Sub AddButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Numbers.Add(New cNumber(Numbers.Count + 1))
End Sub
Private Sub DeleteButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
If Numbers.Count > 0 Then
Numbers.RemoveAt(Numbers.Count - 1)
End If
End Sub
Private Sub IncrementButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
If Numbers.Count > 0 Then
Numbers(0).Number += 1
End If
End Sub
End Class
If you run this example you will see none of the buttons work. Even though the Numbers collection is being modified, the grid does not reflect the changes without being explicitly rebound every time the collection changes.
We can get the Add and Delete buttons working by simply changing the Generic.List to an ObjectModel.ObservableCollection. The ObservableCollection is really just a List that implements INotifyPropertyChanged whenever a property of the collection is changed ie a row is added or deleted or a member of the list is changed.
Public Property Numbers As New System.Collections.ObjectModel.ObservableCollection(Of cNumber) From {New cNumber(1)}
Change the List to an ObservableCollection and try the application again.
The [Add] and [Delete] work as promised, but [Increment] does not.
If this was an ObservableCollection of Integer everything would work right now. But in real life we will have more complex DataGrids that need to be bound to a collection of classes which is why I used the cNumber class in this example. Now pay attention.
When a collection contains structures or objects it doesn't actually contain the objects, it contains pointers to those objects. When you modify the contents of the object, you don't change its address so the collection doesn't change and does not notify the bound control that something changed. So to deal with modifications to objects, the object itself has to be responsible for change notification which means it has to implement INotifyPropertyChanged.
We start off by changing the cNumber class to implement INotifyPropertyChange like this...
Public Class cNumber
Implements System.ComponentModel.INotifyPropertyChanged
Once you have added this implementation VisualStudio will automatically declare a PropertyChanged event in your code. You will see this line added to the cNumber class...
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Underneath this event add this new method. It will be called whenever a property setter detects a change.
Private Sub NotifyPropertyChanged(Name As String)
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(Name))
End Sub
The following steps need to be done for every bound, modifiable property - this can be a pain.
We are going to have to modify the setter for the Number property which means we need to explicitly declare the private member. Add the declaration for _Number and also add the explicit getter and setter for the Number property like this...
Private _Number As Integer
Public Property Number As Integer
Get
Return _Number
End Get
Set(value As Integer)
If _Number <> value Then
_Number = value
NotifyPropertyChanged("Number")
End If
End Set
End Property
As you can see, raising the PropertyChanged event simply says "The value of this property changed, go tell everyone." If you run the application now you can see that clicking [Increment] increments the value of the first row.
No comments:
Post a Comment