Windows 8 Store Apps - Embracing the MVVM Pattern (Part 1)

20 January 2013

Summary:
In this post I introduce the use of the Model-View-ViewModel pattern when developing Windows 8 Store apps.

Introduction

I briefly discussed the Model-View-ViewModel (MVVM) pattern in a previous post on Data Binding in WPF and Windows Store Apps. In this post we take a more detailed look at MVVM and how it can be used to aid the development and maintenance of Windows Store apps.

Fundaments of MVVM

The MVVM architectural pattern was originally developed by Microsoft in 2005 as a specialization of the Model-View-Presenter (MVP) pattern. MVP was itself derived from the well-known Model-View-Controller (MVC) pattern. MVVM was created to leverage the advanced data binding features in WPF and Silverlight (and now WinRT apps) and facilitate a strict separation-of-concerns between the (XAML-defined) view and the model.

In Windows Store MVVM-based apps:

  • The Model encapsulates business/data logic
  • The View is defined using XAML
  • The ViewModel makes model data available to the view and responds to changes in the view
  • The strict separation-of-concerns in MVVM means that a view can call the ViewModel, but not the model. Similarly, the model can't call the view directly (it shouldn't know anything about it)

In Windows Store and WPF apps, the goal when using the MVVM pattern is to minimize the amount of "glue" code needed to manage the flow of data between the View and ViewModel. Interactions between the View and ViewModel are achieved through data bindings specified in the View's XAML, and change notifications raised by the ViewModel.

Is the added complexity of MVVM worth it?

It should be noted at this point that for simple apps, MVVM's added complexity represents over-engineering. In such scenarios, the Model and ViewModel will not be separate entities, but part of the View's code-behind class. This is the default WPF/WinRT Visual Studio development model where the View's code-behind class contains model objects, event handlers, data-access code, etc.

As apps become more complex, the benefits of MVVM become more apparent. A separation of concerns and loose-coupling between architectural layers allows changes to be isolated and contained, and testing of individual system components becomes easier (permitting a TDD approach). It also facilitates an approach to development where UX designers concentrate on creating the UI layer (working in XAML-only, with bindings to the ViewModel), while developers code back-end business and data-access logic.

The plumbing behind MVVM

Before getting too far into the details of MVVM, let's consider the fundamental techniques (the "plumbing") that allow MVVM to work with Windows Store apps. To help explore the practical issues, I'll create a simple app based on the "Blank App" Windows Store app template in Visual Studio.

  • The app has a ModelView class which has two properties:
    1. ListData - an ObservableCollection<string> that holds a list of items
    2. AddItemCommand - an ICommand-derived class that responds to commands issued by the view to add items to the list

  • The main page has a Button and a ListView:
    • Clicking the Button fires a command to the ViewModel to add an item to its list
    • The ListView binds its ItemsSource property to the ViewModel's list

  • The main page also has a TextBox and another Button that sends the contents of the TextBox as an optional parameter as part of the "AddItem" command
  • In this demo, there's no separate Model class - the model functionality is part of the ViewModel class

The following diagram shows the View and ViewModel classes and how they communicate:

Once you've got your blank app setup in Visual Studio 2012, add a new class named RelayCommand. This class forms a reusable command-target and, essentially, contains a delegate for the method to call when the command is executed (e.g. when a button is clicked):

using System;
using System.Windows.Input;

namespace MvvmPlumbingDemo
{
    public class RelayCommand : ICommand
    {
        // Event that fires when the enabled/disabled state of the cmd changes
        public event EventHandler CanExecuteChanged;  

        // Delegate for method to call when the cmd needs to be executed        
        private readonly Action<object> _targetExecuteMethod;  

        // Delegate for method that determines if cmd is enabled/disabled        
        private readonly Predicate<object> _targetCanExecuteMethod;  

        public bool CanExecute(object parameter)
        {
            return _targetCanExecuteMethod == null || _targetCanExecuteMethod(parameter);
        }

        public void Execute(object parameter)
        {
            // Call the delegate if it's not null
            if (_targetExecuteMethod != null) _targetExecuteMethod(parameter);
        }

        public RelayCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod = null)
        {
            _targetExecuteMethod = executeMethod;
            _targetCanExecuteMethod = canExecuteMethod;
        }

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}

The definition of the ViewModel class is as follows. Notice how the AddItemCommand property is wired-up to the DoAddItem() method:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using MvvmPlumbingDemo.Annotations;

namespace MvvmPlumbingDemo
{
    public sealed class ViewModel : INotifyPropertyChanged
    {
        // This event fires when the model's data changes. The View will consume the event
        public event PropertyChangedEventHandler PropertyChanged;

        // The View's "Add Item" Button will bind to this property
        public RelayCommand AddItemCommand { get; set; }

        // The View's ListView will bind to this property
        private ObservableCollection<string> _listData;
        public ObservableCollection<string> ListData
        {
            get { return _listData; }
            set { _listData = value; OnPropertyChanged(); }
        }

        public ViewModel()
        {
            // Set up the action for the command
            AddItemCommand = new RelayCommand(DoAddItem);

            // Create default data for the list
            _listData = new ObservableCollection<string> { "Item 0", "Item 1", "Item 2" };
        }

        public void DoAddItem(object itemText)
        {
            var newItem = itemText as string;
            if (string.IsNullOrEmpty(newItem)) newItem = "Item " + _listData.Count.ToString();

            // When we add an item the ObservableCollection<T> will raise a OnPropertyChanged 
            // event which the view will consume
            _listData.Add(newItem);  
        }

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Finally, the main page's XAML looks like this. You can see that the page's default DataContext is set to the ViewModel (an instance of the ViewModel is created by the <local:ViewModel /> statement). Notice also how we bind the Button element's Command property to the AddItemCommand property in the ViewModel, and that the ListView's ItemsSource property is bound to the ViewModel's ListData property:

<Page
    x:Class="MvvmPlumbingDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MvvmPlumbingDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <!-- Create an instance of the ViewModel class and set the page's data context to that instance -->
    <Page.DataContext>
        <local:ViewModel />
    </Page.DataContext>
    
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center">
            
            <StackPanel Orientation="Horizontal" Margin="0,20,0,10">
                <TextBox x:Name="NewItemTextBox" Width="200" />
                
                <!-- Bind the button to the AddItemCommand property in the ViewModel -->
                <!-- Notice how we send an optional param with the command (in this case, text to add to the list) -->
                <Button Content="Add Item" 
                        Command="{Binding AddItemCommand}" 
                        CommandParameter="{Binding ElementName=NewItemTextBox, Path=Text}"/>
            </StackPanel>

            <!-- Bind the button to the AddItemCommand property in the ViewModel -->
            <Button Content="Add Item With Default Name" HorizontalAlignment="Center" 
                    Command="{Binding AddItemCommand}"/>
            
            <!-- Bind the list's ItemsSource to the ListData property in the ViewModel -->
            <ListView Height="Auto" 
                      Width="300" 
                      Margin="0,10,0,0" 
                      ItemsSource="{Binding ListData}"/>
        </StackPanel>
    </Grid>
</Page>

If we look at the code-behind class for the main page we'll see that it's spookily empty!

using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace MvvmPlumbingDemo
{
    // Nothing much going on in here <tumbleweed>... :-)
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }
    }
}

References

These are the main references I used in relation to this post:

Conclusion

In this post I introduced fundamental MVVM concepts and demonstrated the key techniques used when developing Windows 8 Store app with the MVVM pattern. In part two of this post we'll explore a more detailed example that demonstrates how to use attached properties to add commands to elements that don't support firing commands by default.