Hands-on MVVM 4b: Communicate through the data model

I presented this material at Dallas XAML on May 4, 2010. Please download the demo code and follow along. This post takes us the rest of the way the project called “Step4”.

When we left the project in part 4, we had injected a layer between the model and the view. In the process, we broke updates. Now let’s fix them.

Add INotifyPropertyChanged to the view model
Updates stopped working because we are now data binding to the view model. As of yet, the view model does not implement INotifyPropertyChanged. As I mentioned in part 2, there are really only two reasons to ever fire PropertyChanged:

  1. Something other than the view changes the property, or
  2. The property depends upon another property.

Neither WPF nor Silverlight need you to fire PropertyChanged for a property that the view itself is changing. The view already knows that it has changed. It’s the one that changed it!

The updates that failed are all related to dependent properties. DisplayAs depends upon first name, last name, and email. Title depends upon DisplayAs. We moved both of these properties into the view model. So the view model needs to know when these properties change.

Possible solution: interception
Since the view model wraps the data model, it is possible to intercept changes on their way to the data model. The PersonViewModel understands what Title depends upon, and it intercepts all of those properties. It can fire the appropriate PropertyChanged events.

public string FirstName
{
    get { return _person.FirstName; }
    set { _person.FirstName = value; FirePropertyChanged("Title"); }
}

public string LastName
{
    get { return _person.LastName; }
    set { _person.LastName = value; FirePropertyChanged("Title"); }
}

public string Email
{
    get { return _person.Email; }
    set { _person.Email = value; FirePropertyChanged("Title"); }
}

public string Phone
{
    get { return _person.Phone; }
    set { _person.Phone = value; }
}

public DisplayStrategyViewModel DisplayAs
{
    get { return new DisplayStrategyViewModel(_person, _person.DisplayAs); }
    set { _person.DisplayAs = value.DisplayStrategy; FirePropertyChanged("Title"); }
}

When the user changes any of the properties that Title depends upon, the view model fires PropertyChanged for the Title. Title does not depend upon Phone, so that one doesn’t fire. The view model is keeping track of dependencies.

But what about the DisplayStrategyViewModel? It doesn’t intercept the properties that it depends upon. It needs to be notified when FirstName, LastName, or Email is changed. That means that PersonViewModel would have to send these notifications to DisplayStrategyViewModel. That’s too much coupling between the two. Let’s find a different solution.

Better solution: indirect notification

The view models already have something in common. They both know about the Person data model. Let’s allow them to communicate with each other through the data model.

When the PersonDataModel changes one of the properties on the Person object, the DisplayStrategyViewModel should hear about it. It needs to know about those property changes. We have, conveniently enough, a mechanism for listening to property changed events. Although INotifyPropertyChanged is intended for data binding, you can use it for your own internal notification as well.

Person already implements INotifyPropertyChanged. But it used to fire property changed events for dependent properties. Now we want to know about changes to the properties themselves. Let’s add those notifications.

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; FirePropertyChanged("FirstName"); }
    }

    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; FirePropertyChanged("LastName"); }
    }

    public string Email
    {
        get { return _email; }
        set { _email = value; FirePropertyChanged("Email"); }
    }

    public string Phone
    {
        get { return _phone; }
        set { _phone = value; FirePropertyChanged("Phone"); }
    }
}

Person no longer keeps track of dependent properties. That’s the view model’s job. Now it’s firing PropertyChanged events so that the view models can collaborate with each other. To close the loop, DisplayStrategyViewModel needs to listen.

public class DisplayStrategyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public DisplayStrategyViewModel(
        Person person,
        DisplayStrategy displayStrategy)
    {
        _person = person;
        _displayStrategy = displayStrategy;

        _person.PropertyChanged += PersonPropertyChanged;
    }

    private void PersonPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "FirstName" || e.PropertyName == "LastName" || e.PropertyName == "Email")
            FirePropertyChanged("Display");
    }
}

Now when the PersonViewModel changes one of the properties that DisplayStrategyViewModel depends upon, it can notify the view.

Best solution: automatic dependency management

Wouldn’t it be better if you didn’t have to write all of that dependency management code in the view model? You don’t, if you use Update Controls. But this isn’t an Update Controls presentation, so back to doing it manually.

Finish it out

To finish the transition from the interception solution to the notification solution, we remove the interceptors from the PersonViewModel. Instead, that view model also listens to changes.

public class PersonViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private Person _person;

    public PersonViewModel(Person person)
    {
        _person = person;

        _person.PropertyChanged += PersonPropertyChanged;
    }

    void PersonPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        FirePropertyChanged("Title");
    }
}

Now all of the view models listen to changes in the data model. If anything changes the data model, the view model will notify the view of changes to dependent properties. It’s like a publish-subscribe mechanism, but we’re using the data model itself. The data model has become the conduit for messages.

Next time, we’ll upgrade our application to manage a collection of people. In the process, we will dispel some of the myths about ObservableCollection.

Leave a Reply

You must be logged in to post a comment.