WPF And The Model View View Model Pattern

A example on how to create a WPF application using the Model-View-ViewModel (MVVM) design pattern.

Creating a WPF Application Using MVVM Pattern

A example on how to create a WPF application using the Model-View-ViewModel (MVVM) design pattern.

Introduction

Some time ago, I was searching for a design pattern to be used for a WPF application that I will be creating. I stumbled upon the MVVM design pattern and many developers recommend using this pattern for WPF applications.

This is what I’ve learned so far. The View represents the user interface. The Model is the data that is typically shown on the View. However, the View does not know that the Model exists. This is where the ViewModel comes into play. The View can access the ViewModel while the ViewModel can access the Model. It should not work the other way around. The Model does not know that the ViewModel exist in the same way the ViewModel does not know anything about the View.

I won’t be discussing the advantages or disadvantages of using MVVM. There are a lot of developers who are more knowledgeable on the topic. Instead, I’ll show an example on how to create a WPF application which uses MVVM.

MainView

The following screenshot shows the main window.



On the left side is a TreeView control. When a TreeViewItem is selected, it shows the appropriate panel on the right. The figure above shows the main window when the Sales Orders is selected. It shows a DataGrid for displaying the sales orders and a Button for showing a dialog used in creating a new sales order. Let’s take a look at the equivalent XAML file.

<Window

    x:Class="View.MainView"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:controls="clr-namespace:View.Controls"

    xmlns:sovm="clr-namespace:ViewModel.SalesOrders;assembly=ViewModel"

    xmlns:sovw="clr-namespace:View.SalesOrders"

    Title="Main View"

    Width="400"

    Height="200">

   

    <Window.Resources>

        <DataTemplate DataType="{x:Type sovm:SalesOrdersViewModel}">

            <sovw:SalesOrdersView/>

        </DataTemplate>

    </Window.Resources>

   

    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition Height="*"/>

            <RowDefinition Height="Auto"/>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="Auto"/>

            <ColumnDefinition Width="*"/>

        </Grid.ColumnDefinitions>

       

        <TreeView

            Padding="0,4,12,0">

 

            <controls:CommandTreeViewItem

                Header="Sales Orders"

                Command="{Binding SelectViewModelCommand}"

                CommandParameter="Sales Orders"/>          

 

        </TreeView>           

       

        <ContentControl

            Grid.Column="1"

            Content="{Binding SelectedViewModel}"/>

       

    </Grid>

</Window>

 

When a TreeViewItem is clicked, the system sets the content by using commands and data binding. However, not all controls have a Command dependency property, like a TreeViewItem. We’ll have to create another class that inherits from TreeViewItem and implements ICommandSource to add the Command dependency property and other related properties. We’ll take a look at this later.

The ContentControl in the code above is the control that takes up the window’s space on the right. Its content is bound to the SelectedViewModel property of the MainViewModel. The SelectedViewModel property returns an object of type ViewModel. The ViewModel class is the base class of all ViewModel classes in this example.

The ViewModel base class is not a control, so when we try to set this as the content of the ContentControl, the application will only display its string representation. This behavior can be overridden by using a DataTemplate. When you look at the window’s resources, you will see a DataTemplate resource. When the content is set to a SalesOrdersViewModel object, WPF will look in the resources for a DataTemplate where the DataType property is set to the SalesOrdersViewModel type. If it founds one, it will use the template defined in that resource.

 

MainViewModel

Let’s take a look at where the properties in the MainView are bounded to. The following code listing shows the MainViewModel.

 

public class MainViewModel : ViewModel

{

    private ViewModel selectedViewModel;

 

    private ViewModel salesOrdersViewModel;

 

    private ICommand selectViewModelCommand;

 

    public ViewModel SelectedViewModel

    {

        get

        {

            return selectedViewModel;

        }

        set

        {

            if (object.ReferenceEquals(selectedViewModel, value) != true)

            {

                selectedViewModel = value;

                NotifyPropertyChanged("SelectedViewModel");

            }

        }

    }

 

    public ICommand SelectViewModelCommand

    {

        get

        {

            if (selectViewModelCommand == null)

            {

                selectViewModelCommand = new RelayCommand

                (

                    param =>

                    {

                        switch (param.ToString())

                        {

                            case "Sales Orders":

                                if (salesOrdersViewModel == null)

                                {

                                    salesOrdersViewModel = new SalesOrdersViewModel();

                                }

                               

                                SelectedViewModel = salesOrdersViewModel;

                                break;

 

                            default:

                                throw new NotImplementedException();

                        }

                    }

                );

            }

 

            return selectViewModelCommand;

        }

    }

}

 

The source properties of the data bindings in the MainView are found in the MainViewModel. The TreeViewItem Command is bounded to the SelectViewModelCommand property. The SelectViewModelCommand returns a RelayCommand object. The RelayCommand class implements the ICommand interface. I just found the RelayCommand class from a great article created by Josh Smith at MSDN magazine (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx). Normally, you have to create a new class for every custom command and implement Execute and CanExecute methods. The RelayCommand eliminates this by passing delegates to the constructor, so you only need to create one or two methods, not a class.

 

There are two RelayCommand constructors. The first one accepts an Action<object> parameter. It represents the Execute method of the ICommand. Its CanExecute method always return true. On the other hand, the second constructor also accepts a Predicate<object> which represents the CanExecute method.

 

In the above code listing, we used the first constructor where the first parameter uses a lambda expression. Here, the param is an object wherein its value is set by setting the value of the CommandParameter property. This property is useful if we have multiple TreeViewItem objects and we need to differentiate them from one another.

 

In order to use the ViewModel, the DataContext of the MainView window should be set, which is shown below.

 

/// <summary>

/// Interaction logic for MainView.xaml

/// </summary>

public partial class MainView : Window

{

    public MainView()

    {

        InitializeComponent();

 

        DataContext = new MainViewModel();

    }

}

 

Implementing the ICommandSource

Some controls, like a Button, have a Command dependency property already. It is because the Button class implements the ICommandSource. However, some controls like the TreeViewItem do not. As mentioned before, we need to create a class that inherits from TreeViewItem and implement ICommandSource. The following code listing shows this class.

 

internal class CommandTreeViewItem : TreeViewItem, ICommandSource

{

    #region Constructor

 

    public CommandTreeViewItem()

        : base()

    {

    }

 

    #endregion

 

    #region ICommandSource Members

 

    public static readonly DependencyProperty CommandProperty =

        DependencyProperty.Register

        (

            "Command",

            typeof(ICommand),

            typeof(CommandTreeViewItem),

            new PropertyMetadata((ICommand)null, new PropertyChangedCallback(CommandChanged))

        );

 

    public static readonly DependencyProperty CommandParameterProperty =

        DependencyProperty.Register

        (

            "CommandParameter",

            typeof(object),

            typeof(CommandTreeViewItem),

            new PropertyMetadata((object)null)

        );

 

    public static readonly DependencyProperty CommandTargetProperty =

        DependencyProperty.Register

        (

            "CommandTarget",

            typeof(IInputElement),

            typeof(CommandTreeViewItem),

            new PropertyMetadata((IInputElement)null)

        );

 

    public ICommand Command

    {

        get

        {

            return (ICommand)GetValue(CommandProperty);

        }

        set

        {

            SetValue(CommandProperty, value);

        }

    }

 

    public object CommandParameter

    {

        get

        {

            return (object)GetValue(CommandParameterProperty);

        }

        set

        {

            SetValue(CommandParameterProperty, value);

        }

    }

 

    public IInputElement CommandTarget

    {

        get

        {

            return (IInputElement)GetValue(CommandTargetProperty);

        }

        set

        {

            SetValue(CommandTargetProperty, value);

        }

    }

 

    #endregion

 

    #region ICommandSource-Related

 

    private static EventHandler canExecuteChangedHandler;

 

    // Command dependency property change callback.

    private static void CommandChanged(DependencyObject d,

        DependencyPropertyChangedEventArgs e)

    {

        CommandTreeViewItem commandTreeViewItem = (CommandTreeViewItem)d;

        commandTreeViewItem.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);

    }

 

    // Add a new command to the Command Property.

    private void HookUpCommand(ICommand oldCommand, ICommand newCommand)

    {

        // If oldCommand is not null, then we need to remove the handlers.

        if (oldCommand != null)

        {

            RemoveCommand(oldCommand, newCommand);

        }

        AddCommand(oldCommand, newCommand);

    }

 

    // Remove an old command from the Command Property.

    private void RemoveCommand(ICommand oldCommand, ICommand newCommand)

    {

        EventHandler handler = CanExecuteChanged;

        oldCommand.CanExecuteChanged -= handler;

    }

 

    // Add the command.

    private void AddCommand(ICommand oldCommand, ICommand newCommand)

    {

        EventHandler handler = new EventHandler(CanExecuteChanged);

        canExecuteChangedHandler = handler;

        if (newCommand != null)

        {

            newCommand.CanExecuteChanged += canExecuteChangedHandler;

        }

    }

 

    private void CanExecuteChanged(object sender, EventArgs e)

    {

 

        if (this.Command != null)

        {

            RoutedCommand command = this.Command as RoutedCommand;

 

            // If a RoutedCommand.

            if (command != null)

            {

                if (command.CanExecute(CommandParameter, CommandTarget))

                {

                    this.IsEnabled = true;

                }

                else

                {

                    this.IsEnabled = false;

                }

            }

            // If a not RoutedCommand.

            else

            {

                if (Command.CanExecute(CommandParameter))

                {

                    this.IsEnabled = true;

                }

                else

                {

                    this.IsEnabled = false;

                }

            }

        }

    }

 

    protected override void OnSelected(RoutedEventArgs e)

    {

        base.OnSelected(e);

 

        if (this.Command != null)

        {

            RoutedCommand command = Command as RoutedCommand;

 

            if (command != null)

            {

                command.Execute(CommandParameter, CommandTarget);

            }

            else

            {

                ((ICommand)Command).Execute(CommandParameter);

            }

        }

    }

 

    #endregion

}

 

Actually, when implementing the ICommandSource interface, we only need to implement the following read-only properties: Command, CommandParameter and CommandTarget. We could implement these as normal properties. However, we need to implement them as dependency properties so that we can set their values using XAML.

 

I got most of the code in the ICommandSource-Related region from the example shown in the ICommandSource topic of the MSDN Library. The only difference here is that I overridden the OnSelected method so that when the TreeViewItem is selected, the Command is executed.

 

The Mediator Pattern

To create a new sales order, we have to implement the Button which shows the SalesOrderView dialog for creating a new sales order. Let’s take a look at the SalesOrdersView, the UserControl that contains that Button.

 

<UserControl

    x:Class="View.SalesOrders.SalesOrdersView"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit">

    <Grid>

 

        <Grid.RowDefinitions>

            <RowDefinition Height="*"/>

            <RowDefinition Height="Auto"/>

        </Grid.RowDefinitions>

       

        <toolkit:DataGrid

            AutoGenerateColumns="False"

            ItemsSource="{Binding SalesOrders}"

            CanUserAddRows="False"

            CanUserDeleteRows="False">

            <toolkit:DataGrid.Columns>

                <toolkit:DataGridTextColumn

                    Header="Control No."

                    Binding="{Binding ControlNumber}"

                    Width="*"/>

                <toolkit:DataGridTextColumn

                    Header="Customer"

                    Binding="{Binding Customer}"

                    Width="*"/>

            </toolkit:DataGrid.Columns>

        </toolkit:DataGrid>

 

        <Border

            BorderBrush="DarkGray"

            BorderThickness="1"

            Grid.Row="1">

            <Button

                Margin="4"

                HorizontalAlignment="Right"

                Content="New Sales Order"

                Command="{Binding NewSalesOrderCommand}"/>

        </Border>

    </Grid>

</UserControl>

 

As you can see, we will use a command for showing the SalesOrderView. The problem is, the Command is bound to the NewSalesOrderCommand property which is located in the SalesOrdersViewModel, but the one responsible for creating and showing the dialog is the SalesOrdersView. So it means that the ViewModel should be able to tell the View that it should create and show the dialog. As you might remember, the ViewModel does not know that the View exists.

 

One solution to this problem is by using the Mediator pattern. This pattern allows objects to communicate indirectly by using a mediator. This reduces the dependencies between objects. Typically, the Mediator pattern uses the following classes: Mediator, ConcreteMediator, Colleague and ConcreteColleague.

 

A Mediator is an abstract class that defines how the colleagues communicate. The ConcreteMediator is a concrete class that inherits from the Mediator. A Colleague is also an abstract class which is the base class of the ConcreteColleague objects that will communicate with one another using the Mediator.

 

We’ll only be implementing a ConcreteMediator class to make it easier for us. The following code listing shows the Mediator class.

 

public sealed class Mediator

{

    private static Mediator instance = new Mediator();

 

    private readonly Dictionary<string, List<Action<object>>> callbacks

        = new Dictionary<string, List<Action<object>>>();

 

    private Mediator() { }

 

    public static Mediator Instance

    {

        get

        {

            return instance;

        }

    }

 

    public void Register(string id, Action<object> action)

    {

        if (!callbacks.ContainsKey(id))

        {

            callbacks[id] = new List<Action<object>>();

        }

 

        callbacks[id].Add(action);

    }

 

    public void Unregister(string id, Action<object> action)

    {

        callbacks[id].Remove(action);

 

        if (callbacks[id].Count == 0)

        {

            callbacks.Remove(id);

        }

    }

 

    public void SendMessage(string id, object message)

    {

        callbacks[id].ForEach(action => action(message));

    }

}

 

The mediator is a singleton that keeps a list of all callbacks within the application. An object may register by sending a string identifier and a callback method that gets called when another object sends a message using the same string identifier. Going back to the example, here is the code-behind of the SalesOrdersView.

 

/// <summary>

/// Interaction logic for SalesOrdersView.xaml

/// </summary>

public partial class SalesOrdersView : UserControl

{

    public SalesOrdersView()

    {

        InitializeComponent();

 

        Mediator.Instance.Register

        (

            "Sales Order",

            ShowSalesOrder

        );

    }

 

    private void ShowSalesOrder(object param)

    {

        SalesOrderView view = new SalesOrderView((SalesOrderViewModel)param);

        view.Owner = Window.GetWindow(this);

        view.ShowDialog();

    }

}

 

The SalesOrderView registers the ShowSalesOrder method. It gets called when the SalesOrdersViewModel sends the appropriate message using the mediator. The following code listing shows the SalesOrdersViewModel class.

 

public class SalesOrdersViewModel : ViewModel

{

    private ICommand newSalesOrderCommand;

 

    public ICommand NewSalesOrderCommand

    {

        get

        {

            if (newSalesOrderCommand == null)

            {

                newSalesOrderCommand = new RelayCommand

                (

                    param =>

                    {

                        SalesOrder salesOrder = new SalesOrder();

 

                        Mediator.Instance.SendMessage("Sales Order", new SalesOrderViewModel(salesOrder));

                    }

                );

            }

 

            return newSalesOrderCommand;

        }

    }

}

 

As you might remember, the “New Sales Order” Button’s Command property is bounded to the NewSalesOrderCommand property in the SalesOrdersViewModel. This command sends a message to the SalesOrdersView using the mediator. It passes a SalesOrderViewModel object as its parameter. This is needed by the SalesOrdersView to create a SalesOrderView window like the one shown below.

 


 

Model and Data Validation

As you might have noticed in the last figure, the TextBox controls have red borders and the Save Button is disabled. This is because we added some data validation. The Save Button won’t be enabled unless the Control No. and Customer fields have values. To explain this further, here is the XAML file of the SalesOrderView.

 

<Window

    x:Class="View.SalesOrders.SalesOrderView"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Sales Order"

    SizeToContent="WidthAndHeight"

    WindowStartupLocation="CenterOwner">

    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition Height="Auto"/>

            <RowDefinition Height="Auto"/>

            <RowDefinition Height="Auto"/>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="Auto"/>

            <ColumnDefinition Width="Auto"/>

        </Grid.ColumnDefinitions>

 

        <Label

            Content="Control No:"/>

        <TextBox

            Grid.Column="1"

            Margin="4"

            Width="250"

            Text="{Binding SalesOrder.ControlNumber, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>

 

        <Label

            Grid.Row="1"

            Content="Customer:"/>

        <TextBox

            Grid.Row="1"

            Grid.Column="1"

            Margin="4"

            Text="{Binding SalesOrder.Customer, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>

 

        <StackPanel

            Grid.Row="2"

            Grid.Column="1"

            Margin="4"

            Height="23"

            Orientation="Horizontal"

            HorizontalAlignment="Right">

            <Button

                Margin="0,0,4,0"

                Width="75"

                Content="Save"

                Command="{Binding SaveCommand}"/>

            <Button

                Width="75"

                Content="Cancel"

                Command="{Binding CancelCommand}"/>

        </StackPanel>

 

    </Grid>

</Window>

 

Notice the binding expression in the Text property of both TextBox controls. Here, the an Error attached event will be raised on the bound object in case a validation error occurs during the update of the binding source property.

 

There are two types of validation error that may occur: an error that is raised by the IDataErrorInfo implementation of the source object and an exception that is thrown while updating the value of the binding source property.

 

The first type of error requires a bit of explanation. Let’s take a look at the SalesOrder class, where the binding source properties are found.

 

public class SalesOrder : Model

{

    private string controlNumber;

 

    private string customer;

 

    [Required]

    public string ControlNumber

    {

        get

        {

            return controlNumber;

        }

        set

        {

            if (object.ReferenceEquals(controlNumber, value) != true)

            {

                controlNumber = value;

                NotifyPropertyChanged("ControlNumber");

            }

        }

    }

 

    [Required]

    public string Customer

    {

        get

        {

            return customer;

        }

        set

        {

            if (object.ReferenceEquals(customer, value) != true)

            {

                customer = value;

                NotifyPropertyChanged("Customer");

            }

        }

    }

}

 

The set accessor for each property raises the PropertyChanged event so that the binding target, which is in the View, is notified when the source property’s value is changed. The NotifyPropertyChanged method is in the NotifyAndDataError base class. We’ll take a look at this class later.

 

You can also notice that the RequiredAttribute is applied to both properties. It is a custom attribute, which is shown in the following code listing.

 

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]

public sealed class RequiredAttribute : Attribute

{

}

 

The RequiredAttribute class is applied to a property that must have a value. The logic, however, is located in the NotifyAndDataError class, which implements IDataErrorInfo.

 

public abstract class NotifyAndDataError : INotifyPropertyChanged, IDataErrorInfo

{

    #region INotifyPropertyChanged Members

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    #endregion

 

    protected void NotifyPropertyChanged(string propertyName)

    {

        if (PropertyChanged != null)

        {

            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

    }

 

    #region IDataErrorInfo Members

 

    public string Error

    {

        get

        {

            StringBuilder error = new StringBuilder();

 

            foreach (PropertyInfo propertyInfo in GetType().GetProperties())

            {

                foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))

                {

                    if (attribute is RequiredAttribute)

                    {

                        object value = GetType().GetProperty(propertyInfo.Name).GetValue(this, null);

 

                        if (value == null)

                        {

                            error.Append(propertyInfo.Name);

                            error.AppendLine(" must not be null.");

                        }

                    }

                }

            }

 

            return error.ToString();

        }

    }

 

    public string this[string columnName]

    {

        get

        {

            StringBuilder error = new StringBuilder();

 

            foreach (Attribute attribute in GetType().GetProperty(columnName).GetCustomAttributes(true))

            {

                if (attribute is RequiredAttribute)

                {

                    object value = GetType().GetProperty(columnName).GetValue(this, null);

 

                    if (value == null)

                    {

                        error.Append(columnName);

                        error.AppendLine(" must not be null.");

                    }

                }

            }

 

            return error.ToString();

        }

    }

 

    #endregion

}

 

The binding expression checks the this[string columnName] property. It checks if a property is marked with the RequiredAttribute. If it is, it checks the value of the property. If null, then an error is returned. You can add your custom attributes if you want to. However, it is recommended that you use an existing validation framework.

 

In the example, if the return value of this property is “Customer must not be null.”, then the TextBox will have a red border, which is the default behavior. You can change this by specifying your own template in the Validation.ErrorTemplate property of the control.

 

Once the fields have been corrected, the Save Button will be enabled. Enabling and disabling is done using an ICommand, which is found in the SalesOrderViewModel class.

 

public class SalesOrderViewModel : ViewModel

{

    private SalesOrder salesOrder;

 

    private ICommand saveCommand;

 

    private ICommand cancelCommand;

 

    public SalesOrderViewModel(SalesOrder salesOrder)

    {

        this.salesOrder = salesOrder;

    }

 

    public SalesOrder SalesOrder

    {

        get { return salesOrder; }

    }

 

    public ICommand SaveCommand

    {

        get

        {

            if (saveCommand == null)

            {

                saveCommand = new RelayCommand

                (

                    param =>

                    {

                        // Save model here using your data access module.

 

                        Mediator.Instance.SendMessage("Save Sales Order", SalesOrder);

                    },

                    param =>

                    {

                        return SalesOrder.Error.Length == 0;

                    }

                );

            }

 

            return saveCommand;

        }

    }

 

    public ICommand CancelCommand

    {

        get

        {

            if (cancelCommand == null)

            {

                cancelCommand = new RelayCommand

                (

                    param =>

                    {

                        Mediator.Instance.SendMessage("Cancel Sales Order", null);

                    }

                );

            }

 

            return cancelCommand;

        }

    }

}

 

Notice that we’ve used the RelayCommand class’ second constructor. It accepts a Predicate<object> that determines whether the Execute method can be executed. If the Error property is empty, then the button is enabled and the SalesOrder object can be saved. The following screenshot shows the SalesOrderView having non-empty fields.

 


 

In this example, I am just adding the SalesOrder object to an ObservableCollection<SalesOrder> object. You should create your own data access module for saving and loading your data. Going back, after clicking on the Save Button, the sales order is added to the data grid.


 

 

The Visual Studio 2008 example can be downloaded here.

By Michael Detras   Popularity  (16387 Views)
Biography - Michael Detras
.NET developer. Interested in WPF, Silverlight, and XNA.
My blog