Good way to refresh databinding on all properties of a ViewModel when Model changes
Asked Answered
M

4

37

Short Version

If I update the Model object that my ViewModel wraps, what's a good way to fire property-change notifications for all the model's properties that my ViewModel exposes?

Detailed Version

I'm developing a WPF client following the MVVM pattern, and am attempting to handle incoming updates, from a service, to data being displayed in my Views. When the client receives an update, the update appears in the form of a DTO which I use as a Model.

If this model is an update to an existing model being shown in the View, I want the associated ViewModel to update its databound properties so that the View reflects the changes.

Let me illustrate with an example. Consider my Model:

class FooModel
{
  public int FooModelProperty { get; set; }
}

Wrapped in a ViewModel:

class FooViewModel
{
  private FooModel _model;

  public FooModel Model 
  { 
    get { return _model; }
    set 
    { 
      _model = value; 
      OnPropertyChanged("Model"); 
    }
  }

  public int FooViewModelProperty
  {
    get { return Model.FooModelProperty; }
    set 
    {
      Model.FooModelProperty = value;
      OnPropertyChanged("FooViewModelProperty");
    }    
}

The Problem:

When an updated model arrives, I set the ViewModel's Model property, like so:

instanceOfFooVM.Model = newModel;

This causes OnPropertyChanged("Model") to fire, but not OnPropertyChanged("FooViewModelProperty"), unless I call the latter explicitly from Model's setter. So a View bound to FooViewModelProperty won't update to display that property's new value when I change the Model.

Explicitly calling OnPropertyChanged for every exposed Model property is obviously not a desirable solution, and neither is taking the newModel and iterating through its properties to update the ViewModel's properties one-by-one.

What's a better approach to this problem of updating a whole model and needing to fire change notifications for all its exposed properties?

Moralist answered 10/1, 2011 at 21:0 Comment(0)
D
68

According to the docs:

The PropertyChanged event can indicate all properties on the object have changed by using either null or String.Empty as the property name in the PropertyChangedEventArgs.

Delwin answered 10/1, 2011 at 21:26 Comment(6)
I'm sure I read this at some point, but I'd utterly forgotten about it. This seems like the best approach when a ViewModel's entire Model has changed. Many thanks!Moralist
I use this method all the time to refresh objects. I would not suggest doing this after every property change but for an initial object load it works great.Changchangaris
if using MVVM-light this will give an error. there's no actual solution here unfortunately but see also stackoverflow.com/questions/3554831Inna
My tests showed that I only String.Empty works. Null didn't work for meDecagram
@Alon Catz right, I also tried using null as propertyName value and didn't get all class binded properties updated, However using string.Empty as propertyName value does update all class binded properties.Stenophyllous
@AlonCatz I agree, null does not work. "" or string.Empty does.Timely
M
7

One option is to listen to your own events, and make a helper routine to raise the other notifications as required.

This can be as simple as adding, in your constructor:

public FooViewModel()
{
    this.PropertyChanged += (o,e) =>
      {
          if (e.PropertyName == "Model")
          {
               OnPropertyChanged("FooViewModelProperty");
               // Add other properties "dependent" on Model here...
          }
      };
}
Misinterpret answered 10/1, 2011 at 21:11 Comment(5)
I expected to see something AOP-like among the answers, but I really love simplicity. Up-voted :). Maybe another approach could be just extract all dependent OnPropertyChanged invocations to a separate method and call it from FooModel's setter.Dyan
@Anvaka: I've got, internally, a helper routine that does this via "AddDepedentProperty" and expression trees... It's a useful option.Misinterpret
Thanks for this approach. Its downside, though, is that it preserves the problem of having to maintain a set of OnPropertyChanged calls for all the properties exposed by my ViewModel; when properties are added, for instance, this set must be kept up to date. I believe @Joe White has hit upon what I need...Moralist
@djacobson: That's a good option, too- be aware that it redoes EVERY property bound to that object - if your VM is a simple Model wrapper, it's perfect - but if it includes other application logic, it can trigger more notifications than required.Misinterpret
Good call, and something I will certainly keep in mind! In my case, I think it will be a trade-off worth making... at least until proven otherwise. :)Moralist
S
1

Whenever your Model property is set, subscribe to its own PropertyChanged event. When your handler gets called, fire off your own PropertyChanged event. When the Model is set to something else, remove your handler from the old Model.

Example:

class FooViewModel
{
    private FooModel _model;

    public FooModel Model 
    { 
        get { return _model; }
        set 
        { 
            if (_model != null)
            {
                _model.PropertyChanged -= ModelPropertyChanged;
            }

            if (value != null)
            {
                value.PropertyChanged += ModelPropertyChanged;
            }

            _model = value; 
            OnPropertyChanged("Model"); 
        }
    }

    public int FooViewModelProperty
    {
        get { return Model.FooModelProperty; }
        set 
        {
            Model.FooModelProperty = value;
            OnPropertyChanged("FooViewModelProperty");
        } 
    }

    private void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Here you will need to translate the property names from those
        // present on your Model to those present on your ViewModel.
        // For example:
        OnPropertyChanged(e.PropertyName.Replace("FooModel", "FooViewModel"));
    }
}
Seeto answered 10/1, 2011 at 21:33 Comment(0)
C
0
    Implements INotifyPropertyChanged
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged 

    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(String.Empty))

For VB.net if anybody else needs it. If you have already implemented "INotifyPropertyChanged" then the last line is all you need.

Clementina answered 4/11, 2016 at 18:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.