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?
1 Answer 1
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
.
-
\$\begingroup\$ That exactly is the main sissue, which way to go!
Product
as in model (or rest of application) doesn't needINotifyPropertyChanged', it's only required to update UI so only MVVM layer needs it. So why implement
INotifyPropertyChanged` there, seems like it will make code over complicated. But most of the type these nested properties does bind to UI so we need that interface as well. Not sure if one should use other tricks to do without needing this interface? \$\endgroup\$zadane– zadane2020年07月16日 18:36:09 +00:00Commented Jul 16, 2020 at 18:36 -
\$\begingroup\$ @zadane: I'm not aware of any tricks as workarounds for using
INotifyPropertyChanged
. As an alternative you could use theDependencyProperty/DependencyObject
scheme, but that doesn't seems to be a "trick". \$\endgroup\$user73941– user739412020年07月16日 18:46:48 +00:00Commented Jul 16, 2020 at 18:46 -
\$\begingroup\$ A trick that works is to new instance of parent property and that forces the bindings of child properties to be executed as well but like you can see its pretty dirty. \$\endgroup\$zadane– zadane2020年07月16日 21:03:52 +00:00Commented Jul 16, 2020 at 21:03
List<Product>
but now viewmodel will have 'List<ProductVM>' I wonder if this be frown upon in anyway? \$\endgroup\$