Problem
Here is my simple model.
public class Product
{
public String Name { get; set; }
public int Price { get; set; }
public String Info { get; set; }
}
When I use this in viewmodel as following:
public MainViewModel()
{
Item = new Product() { Name = "Coffee", Price = 4, Info = "Coffe is hot" };
}
protected Product _item;
public Product Item
{
get { return _item; }
set
{
_item = value;
NotifyPropertyChanged("Item");
}
}
In my XAML, I am binding to the nested properties.
<StackPanel>
<TextBox Text="{Binding Item.Name}"/>
<TextBox Text="{Binding Item.Price}"/>
<TextBox Text="{Binding Item.Info}"/>
<Button Content="Change Item" Click="Change_Item_Clicked"/>
</StackPanel>
I change only nested property in the click event:
private void Change_Item_Clicked(object sender, RoutedEventArgs e)
{
MainViewModel vm = DataContext as MainViewModel;
vm.Item.Name = "Donut";
}
This obviously doesn’t update the UI since the parent property implements INotifyPropertyChanged
but not the nested one.
So I came up with the following solution and it does then update the UI fine.
I am essentially creating viewmodel version of the Product
class as below that implments INotifyPropertyChanged
.
public class ProductVM : Notifier
{
protected Product _p;
public ProductVM(Product p)
{
_p = p;
}
public String Name
{
get { return _p.Name; }
set
{
_p.Name = value;
NotifyPropertyChanged("Name");
}
}
public int Price
{
get { return _p.Price; }
set
{
_p.Price = value;
NotifyPropertyChanged("Price");
}
}
public String Info
{
get { return _p.Info; }
set
{
_p.Info = value;
NotifyPropertyChanged("Info");
}
}
}
Now the viewmodel use its version of the Product class as defined above that has ability to notify on changes.
class MainViewModel : Notifier
{
public MainViewModel()
{
Item = new ProductVM( new Product() { Name = "Coffee", Price = 4, Info = "Coffe is hot" });
}
protected ProductVM _item;
public ProductVM Item
{
get { return _item; }
set
{
_item = value;
NotifyPropertyChanged("Item");
}
}
}
This works great but what do you guys think of this approach? If we are going to bind to a property (nested or top level), wouldn’t it be best for all these properties to implment the INotifyPropertyChanged?
What naming conversion can I use to name ProductVM
better? Is there a less cody way to achieve this?
Solution
To me it looks fine. If you can or will not modify the model objects (Product
) by letting them implement INotifyPropertyChanged
then this approach is the price to pay for using XAML. In larger projects, it can be a little annoying to seemingly write the same thing twice, but you on the other hand have a true separation of concerns, and your solution is an implementation of the MVVM-pattern.
To me ProductVM
is an OK name, I always name my view objects that way.
Instead of using string literals in the call to NotifyPropertyChanged
you can use nameof(<property name>)
:
NotifyPropertyChanged(nameof(Name));
this will make it easier to maintain, if the property name changes.
Another approach is to define `NotifyPropertyChanged` as:
private void NotifyPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
then you don’t need to provide the property name in the call:
public String Name
{
get { return _p.Name; }
set
{
_p.Name = value;
NotifyPropertyChanged();
}
}
CallerMemberName
requires a reference to System.Runtime.CompilerServices
.