3
\$\begingroup\$

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?

\$\endgroup\$
3
  • \$\begingroup\$ Avoid working with "Click" etc., instead use commands to bind actions: c-sharpcorner.com/UploadFile/e06010/wpf-icommand-in-mvvm \$\endgroup\$
    – BCdotWEB
    Jul 16, 2020 at 7:29
  • 1
    \$\begingroup\$ WRT NotifyPropertyChanged: perhaps consider avoiding magic string: stackoverflow.com/questions/7728465/… \$\endgroup\$
    – BCdotWEB
    Jul 16, 2020 at 7:32
  • \$\begingroup\$ @BCdotWEB The Click event was meant to just demonstrate updating the property quickly. Command would be the right thing. I wanted to present my approach on using model class/struct with nest properties in viewmodel in good and effective way. Also typically we have objects like List<Product> but now viewmodel will have 'List<ProductVM>' I wonder if this be frown upon in anyway? \$\endgroup\$
    – zadane
    Jul 16, 2020 at 17:52

1 Answer 1

1
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\begingroup\$ That exactly is the main sissue, which way to go! Product as in model (or rest of application) doesn't need INotifyPropertyChanged', 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
    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 the DependencyProperty/DependencyObject scheme, but that doesn't seems to be a "trick". \$\endgroup\$
    – user73941
    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
    Jul 16, 2020 at 21:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.