My model:
sealed class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
My ViewModel:
// INotifyPropertyChanged notifies the View of property changes, so that Bindings are updated.
sealed class MyViewModel : INotifyPropertyChanged { private User user;
public string FirstName {
get {return user.FirstName;}
set {
if(user.FirstName != value) {
user.FirstName = value;
OnPropertyChange("FirstName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
public string LastName {
get { return user.LastName; }
set {
if (user.LastName != value) {
user.LastName = value;
OnPropertyChange("LastName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
// This property is an example of how model properties can be presented differently to the View.
// In this case, we transform the birth date to the user's age, which is read only.
public int Age {
get {
DateTime today = DateTime.Today;
int age = today.Year - user.BirthDate.Year;
if (user.BirthDate > today.AddYears(-age)) age--;
return age;
}
}
// This property is just for display purposes and is a composition of existing data.
public string FullName {
get { return FirstName " " LastName; }
}
public MyViewModel() {
user = new User {
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Now.AddYears(-30)
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName) {
if(PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in xaml we use binding:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="4" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold"/>
<Label Grid.Column="0" Grid.Row="1" Margin="4" Content="First Name:" HorizontalAlignment="Right"/>
<!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. -->
<TextBox Grid.Column="1" Grid.Row="1" Margin="4" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="2" Margin="4" Content="Last Name:" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="4" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="3" Margin="4" Content="Age:" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="1" Grid.Row="3" Margin="4" Text="{Binding Age}" HorizontalAlignment="Left"/>
</Grid>
The code behind:
public partial class MainWindow : Window
{
private readonly MyViewModel _viewModel;
public MainWindow() {
InitializeComponent();
_viewModel = new MyViewModel();
// The DataContext serves as the starting point of Binding Paths
DataContext = _viewModel;
}
}
My question is that for binding. For example, if FirstName
is changed, the view is changed as well. We know string in C# is immutable, when the value changes a new object is created. But in WPF binding, if FirstName
is changed, is a new FirstName object created? I assume it is NO since we use binding to hold the value in one object. So does OnPropertyChange
remove the immutable internally?
CodePudding user response:
No, FirstName
is not reassigned or mutated. It is a reference to a backing string
that is reassigned.
When you have an auto-implemented property:
public string FirstName { get; set; }
It is compiled (roughly) into:
private string _firstName;
public string FirstName
{
get => _firstName;
set => _firstName = value;
}
As you can see, assigning a value to FirstName
passes the value as an argument to the setter
for FirstName
which reassigns _firstName
(the private backing field). FirstName
is never changed.
This is also why INotifyProperyChanged
is required. Because the binding is on FirstName
and FirstName
never changes, the framework needs a way to tell when the value referenced by FirstName
changes.
CodePudding user response:
In MVVM, you bind to Properties (not to the backing members).
When you change a Property, like a string
, then you technically place a new object behind the class member (i.e. the backing instance of a property).
OnPropertyChanged
tells that to the View, so it calls the Property Getter (not the string instance) again, getting a the new instance etc...
If you would not, the View would hold and display a reference to the old string, which is no longer used by your Viewmodel
Then, we have
ObservableCollections
and Observable
types, that can do this notification automatically, because they are mutable, meaning they stay the same instance, while only their content changes.
Most commonly, ObservableCollections are used, to notify the View, that it contents has changed (while the collection instance stayed the same). (element added, removed..)
In that case, you dont need to Notify, because the View has registered to that event.
If you would assign a new instance to an observableCollection member, the view would also not update correctly without Propertychanged, because the original instance of the collection would no longer change.