Tuesday, January 24, 2012

The MVVM Pattern: MVP Evolved

So far, we have taken a close look at two of the architectural patterns in the Model-View-Whatever family, the MVC and MVP patterns. These patterns both enjoy widespread use across platforms, languages, and runtime environments, and are both still in heavy use.

Today, we're going to look at a pattern that is somewhat more specialized, and at least at this point, primarily finds use in WPF applications: the Model-View-ViewModel pattern. MVVM is a natural evolution of MVP that takes advantage of WPF's features, as we'll see in a minute. But first, what is MVVM and how did it develop?

What's Wrong With MVP?

When you hear about a "new" architectural pattern coming along to replace whatever you're already doing, the first question that ought to spring to mind is "Why?" If someone came up with a new pattern that is gaining credibility, it must be objectively superior to all the existing patterns in at least some aspect. Since people were doing just fine writing WPF applications before MVVM, we should start by asking "What was wrong with MVP?"

Most of these patterns we have discussed can be used in a variety of situations, but really shine in a particular environment that closely matches their underlying assumptions. Traditional ASP.NET web applications, for example, have a forced separation of their View from the rest of the application, via the HTTP channel, that makes the Passive View MVP pattern particularly suitable for them. By contract, the ASP.NET MVC Framework includes automatic bi-directional data binding, and the AJAX and jQuery support generates interactive user commands that need interpreting and routing, as appropriate, which makes the MVC pattern a better fit.

Strictly speaking, nothing was "wrong" with MVP: it is a perfectly valid pattern for writing WPF applications. However, there were a few things about its implementation that weren't as optimal for WPF -- that failed to take advantage of everything WPF brings to the table. The MVVM pattern was derived to fully utilize WPF's features to make the developer's job easier and more efficient. For WPF, MVVM is a "better fit" than MVP.

The key aspect to an MVP pattern, and the key difference with MVVM, is that the Presenter class has a deep level of knowledge about the View(s) it is bound to. Even when the View is also bound directly to a Model class, as was typical with MVP in WPF, there is still a significant amount of direct intervention from the Presenter to get data into a View. In WPF, the simplest and most "primitive" way to implement a Presenter is in the code-behind for a XAML page: give every control a name, and bind input events to code-behind methods, and you have a tighlty integrated Presenter and View. The alternative was to move the code-behind code into a separate class, but now you have a bunch of scaffolding code in your code-behind to forward events and property accessors between the View's XAML and it's Presenter. Plus, you now have two places acting as Observers for the Model: the View, for simple bound data, and the Presenter, for anything more complex.

The WPF UI framework, however, is built around the concept of data binding and notifications. The DependencyObject and DependecyProperty classes, combined with the existing INotifyPropertyChanged interface, are built from the ground up to be highly, automatically responsive to changes in the data model bound to a XAML element. Any code we end up writing in our Presenter to update UI fields seems like something we should be able to get from WPF for "free".

WPF also provides a useful abstraction of user input via the ICommand interface, which we'll look at in more detail in a future post. The commanding infrastructure handles the two common aspects of user input: responding the the user's commands, and notifying the user when a given command is unavailable. Instead of relying on our View forward Click events to the Presenter, which then has to manually wire and unwire them, we'd like to simply execute a Presenter command directly.

The big picture that starts to come out of this is that the MVP pattern itself is "backwards" from WPF's perspective. WPF is all about declarative UIs written in XAML, describing what the UI needs to do, and letting WPF deal with the how. What we need is a component to supply all the data our View needs, in the correct format, and to expose the operations our View can use to manipulate that data. We need a Model... for our View.

Enter: The ViewModel

The purpose of a ViewModel is to encapsulate all of the data and behavior that a given View needs, into a single class that can act as the data source for the entire XAML object. The ViewModel exposes properties that the View can bind to, using WPF data binding, and operations that the View can execute, via the commanding support. It also provides notifications to the View, which acts as an Observer (thanks to WPF's property system), to automatically refresh itself as needed.

Now, the most obvious way to implement this new backwards-MVP pattern is much the same as our obvious MVP implementation: the code-behind for the View. We could do something like this in our View's code-behind class to get the data binding behavior:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <TextBox Text="{Binding ChangeDue}" />
</Window>
public static readonly DependencyProperty ChangeDueProperty = DependencyProperty.Register("ChangeDue", typeof(decimal), typeof(TenderView), new UIPropertyMetadata(decimal.Zero));

public decimal ChangeDue
{
    get
    {
        return (decimal)this.GetValue(ChangeDueProperty);
    }
    set
    {
        this.SetValue(ChangeDueProperty, value);
    }
}
Here, we are effectively making every View its own ViewModel. But having all of this in the code-behind isn't really the best option, for several reasons. It turns out that having a separate object for the View to bind to is more than just a stylistically "better" approach. It allows us to do some pretty cool things with data templating, which I'll hopefully cover in a later post. And, while its never been an issue for me, some development teams prefer to have their XAML authored by non-developers, using Blend for example, which means little to no code-behind is involved.

Instead, lets move this stuff into its own class, a TenderViewModel. (Note the name already implies the very tight coupling between the View and the underlying ViewModel.) The code looks pretty much the same as before:
class TenderViewModel : DependencyObject, INotifyPropertyChanged
{
    public static readonly DependencyProperty ChangeDueProperty = DependencyProperty.Register("ChangeDue", typeof(decimal), typeof(TenderViewModel), new UIPropertyMetadata(decimal.Zero));
    public static readonly DependencyProperty BalanceDueProperty = DependencyProperty.Register("BalanceDue", typeof(decimal), typeof(TenderViewModel), new UIPropertyMetadata(decimal.Zero));
    public static readonly DependencyProperty TenderAmountProperty = DependencyProperty.Register("TenderAmount", typeof(decimal), typeof(TenderViewModel), new UIPropertyMetadata(decimal.Zero));
    public static readonly DependencyProperty TenderTypeProperty = DependencyProperty.Register("TenderType", typeof(int), typeof(TenderViewModel), new UIPropertyMetadata(0));

    public Transaction Transaction
    {
        get;
        private set;
    }

    public TenderViewModel(Transaction transaction)
    {
        this.Transaction = transaction;
    }

    public decimal ChangedDue
    {
        get
        {
            return (decimal)this.GetValue(ChangeDueProperty);
        }
        set
        {
            this.SetValue(ChangeDueProperty, value);
        }
    }

    public decimal BalanceDue
    {
        get
        {
            return (decimal)this.GetValue(BalanceDueProperty);
        }
        set
        {
            this.SetValue(BalanceDueProperty, value);
        }
    }

    public decimal TenderAmount
    {
        get
        {
            return (decimal)this.GetValue(TenderAmountProperty);
        }
        set
        {
            this.SetValue(TenderAmountProperty, value);
        }
    }

    public int TenderType
    {
        get
        {
            return (int)this.GetValue(TenderTypeProperty);
        }
        set
        {
            this.SetValue(TenderTypeProperty, value);
        }
    }
}
Notice that our ViewModel object is derived from DependencyObject, which allows us to add dependency properties for our UI to bind to. We also implement INotifyPropertyChanged, although at the moment we don't actually use it. These two elements of our ViewModel perform roughly the same job, in two slightly different ways.

Observing The ViewModel

The heart of WPF's data binding power is the property system, which allows you to author your WPF views in a declarative grammar like XAML, and still get dynamic, responsive behavior from your UI. All visual elements in the WPF library ultimately derive from the DependencyObject class, which provides the property system logic for its various properties. Whenever a WPF property on a DependencyObject is bound to another object through a WPF Binding, it tries to become an Observer of the data source to watch for state changes.

The property system first checks to see if the data source element in question is a DependencyProperty. These properties are registered with WPF for each type of DependencyObject descendant, and can be looked up based on the property name and the type of its containing class. (Certain parts of WPF, particularly the styling/templating parts, also require that your dependency properties follow the naming convention seen in the above code fragment.) If a dependency property is found, WPF will use the inherent behaviors of the property system to subscribe to change notifications for that property.

If the bound object is not a dependency property, WPF will fall back on the events system for these change notifications. Its next step is to check the containing class to see if it implements INotifyPropertyChanged. If so, the DependencyObject subscribes to that event, and starts listening for notifications that a property it cares about has changed. If that interface isn't implemented, the final option is to look for an explicitly named PropertyNameChanged event, and subscribe to that. For example, our ViewModel could have exposed ChangedDueChanged and BalanceDueChanged events, and gotten the same change notification benefits.

Overall, the DependencyProperty option is usually the best choice, if possible, because it enables a variety of other features that only work for dependency properties. If that's not an option, INotifyPropertyChanged is a very commonly-used interface, and not at all WPF specific, so its a pretty decent plan B. For example, your ViewModel may include one or more aggregate properties that have no explicit backing store of their own, but still need to participate in the property system, for which the PropertyChanged event is an ideal solution. The third option, individual *Changed events for each property, should probably be a last resort. It might be useful if you expect your ViewModel to be used outside of WPF, where someone may want to hook into those events for their own purposes (such as is commonly done in Windows Forms applications).

Commanding The ViewModel

So, we have our data flowing out of the ViewModel into the View, now we need to go back the other way. As we did before with the Presenter, we could hook up a code-behind event to handle a button click and forward the event to the ViewModel, but WPF gives us a nicer option. The WPF commanding system very nicely complements the WPF property system in giving us a declarative way to execute operations against our ViewModel directly.

The various input-style UI elements in WPF, like a Button, MenuItem, or HyperLink, have a Command property that can be set to any implementation of ICommand. This interface includes two methods, CanExecute and Executed, that the input source can use to adapt itself to user interaction and state changes. Lets look at a simple example of a custom ICommand implementation. There are more elegant ways to accomplish this same goal, but this will serve to at least illustrate the point. First, we make a class that implements our command. This command is specifically built to operate on a specific type of ViewModel component:
class AddPaymentCommand : ICommand
{
    public event EventHandler CanExecuteChangedl;

    public bool CanExecute(object parameter)
    {
        var model = parameter as TenderViewModel;
        return (model.TenderType > 0) && (model.TenderAmount > decimal.Zero);
    }

    public void Execute(object parameter)
    {
        var model = parameter as TenderViewModel;
        model.AddNewPayment();
    }
}
We then expose one of these commands via our ViewModel, and bind our button to it:
<!-- TenderView.xaml -->
<Button Command="{Binding AddPayment}" CommandParameter="{Binding}" />
// TenderViewModel.cs:
public ICommand AddPayment;

public TenderViewModel(Transaction transaction)
{
    this.Transaction = transaction;
    this.AddPayment = new AddPaymentCommand();
}

public void AddNewPayment()
{
    this.Transaction.AddPayment(this.TenderType, this.TenderAmount);
    this.BalanceDue = this.Transaction.BalanceDue;
    this.ChangedDue = this.Transaction.ChangeDue;
}
Our ViewModel now fully encapsulates the logic and data needed for our View, and there is no code-behind class for the View itself. Displaying this on the screen is a simple matter of setting up the View's data context properly:
var view = new TenderView();
view.DataContext = new TenderViewModel(this.CurrentTransaction);
view.Show();
Beyond MVVM

The MVVM pattern is very powerful, particularly when combined with WPF's data templates. Hopefully I'll show more about this in a later post, but first, we have one final pattern to take a look at. This pattern is the next stage in the evolution of MVVM (though I'm not yet entirely sold on it), and tries to combine the best parts of MVVM and MVP. Not surprisingly, this pattern has started being referred to as Model-View-Presenter-ViewModel, or MVPVM, and we'll see more about this next time.

0 comments: