Using Model – View – ViewModel with Silverlight

The View – Model – ViewModel design pattern, also known as MVVM, is getting more popular these days. I have found it extremely easy to use when developing very different applications and have used the design pattern recently in both ASP.NET, WPF and Silverlight applications. However easy as it might be is seems to confuse people as I have seen some terrible examples where people make a complete mess of things.

Josh Smith did an excellent screen cast for Pixel8 on using MVVM with WPF, you can find it here.

 

Even thought the UI technology used doesn’t change the basic MVVM pattern there are some subtle differences, like not easily being able to use ICommand in Silverlight, so I decided to create a small Silverlight sample.

 

The basic structure goes like this:

  • The user interacts with a View, implemented as a Silverlight user control.
  • The View is data bound to a ViewModel. This is the most important step to remember. The ViewModel is just another class.
  • The ViewModel is a wrapper for a Model. Think of the Model as the data and the business rules.

The Model

The Model I am using is very simple and has two read-write properties, FirstName and LastName, and a single read-only property, FullName. The FullName property represents some business rule on how a name should be formatted. I know it is a bit of a lame business rule but it is just a sample [:)]. The Model also implements INotifyPropertyChanged so the UI can be updated whenever a value is updated. The complete model looks like this:

   1: using System.ComponentModel;
   2:  
   3: namespace SilverlightMVVMDemo.Model
   4: {
   5:     public class Customer : INotifyPropertyChanged
   6:     {
   7:         private string _firstName;
   8:         private string _lastName;
   9:  
  10:         public string FirstName
  11:         {
  12:             get { return _firstName; }
  13:             set
  14:             {
  15:                 _firstName = value;
  16:                 OnPropertyChanged("FirstName");
  17:                 OnPropertyChanged("FullName");
  18:             }
  19:         }
  20:         public string LastName
  21:         {
  22:             get { return _lastName; }
  23:             set
  24:             {
  25:                 _lastName = value;
  26:                 OnPropertyChanged("LastName");
  27:                 OnPropertyChanged("FullName");
  28:             }
  29:         }
  30:  
  31:  
  32:         public string FullName
  33:         {
  34:             get { return FirstName + " " + LastName; }
  35:         }
  36:  
  37:         public event PropertyChangedEventHandler PropertyChanged;
  38:  
  39:         protected void OnPropertyChanged(string propertyName)
  40:         {
  41:             if (PropertyChanged != null)
  42:             {
  43:                 var args = new PropertyChangedEventArgs(propertyName);
  44:                 PropertyChanged(this, args);
  45:             }
  46:         }
  47:     }
  48: }


 



The View



The View is just a Silverlight user control that makes things visible for the user. All controls use data binding to get to their values. There is one interesting twist here. Just like I had a business rule about how to format a FullName I have a rule about the font weight to use to display the FullName. This is just UI so not a business rule and as a result it is not stored in the Model. We do not want to code this in the UI as that makes things hard to test. So that leaves one place, the ViewModel [:)]. And the View uses data binding to set the FontWeight property.



The complete View looks like this:



   1: <UserControl
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:SilverlightMVVMDemo_ViewModel="clr-namespace:SilverlightMVVMDemo.ViewModel" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Class="SilverlightMVVMDemo.View.CustomerView" 
   5:     Width="400" Height="300" mc:Ignorable="d">
   6:     <Grid x:Name="LayoutRoot" 
   7:           Background="White">
   8:         <Grid.RowDefinitions>
   9:             <RowDefinition Height="0.133*"/>
  10:             <RowDefinition Height="0.133*"/>
  11:             <RowDefinition Height="0.133*"/>
  12:             <RowDefinition Height="0.18*"/>
  13:             <RowDefinition Height="0.42*"/>
  14:         </Grid.RowDefinitions>
  15:         <Grid.ColumnDefinitions>
  16:             <ColumnDefinition Width="0.3*"/>
  17:             <ColumnDefinition Width="0.7*"/>
  18:         </Grid.ColumnDefinitions>
  19:         <TextBlock Text="Firstname:" 
  20:                    Margin="8"/>
  21:         <TextBox Text="{Binding Path=FirstName, Mode=TwoWay}" 
  22:                  Grid.Column="1" 
  23:                  Margin="8,8,8,8"/>
  24:         <TextBlock Text="Lastname:" 
  25:                    Grid.Row="1" 
  26:                    Margin="8"/>
  27:         <TextBox Text="{Binding Path=LastName, Mode=TwoWay}" 
  28:                  Grid.Row="1" 
  29:                  Grid.Column="1" 
  30:                  Margin="8,8,8,8"/>
  31:         <TextBlock Text="Fullname:" 
  32:                    Grid.Row="2" 
  33:                    Margin="8"/>
  34:         <TextBox Text="{Binding Path=FullName}" 
  35:                  Grid.Row="2" 
  36:                  Grid.Column="1" 
  37:                  Margin="8,8,8,8"
  38:                  FontWeight="{Binding Path=FullNameFontWeight}"
  39:                  IsReadOnly="True"/>
  40:         <Button Content="Save"
  41:                 Click="Button_Click"
  42:                 Grid.Row="3" 
  43:                 Grid.Column="1" />
  44:     </Grid>
  45: </UserControl>




The code behind is very simple and looks like this:



   1: using System.Windows.Controls;
   2: using SilverlightMVVMDemo.ViewModel;
   3:  
   4: namespace SilverlightMVVMDemo.View
   5: {
   6:     public partial class CustomerView : UserControl
   7:     {
   8:         public CustomerView()
   9:         {
  10:             InitializeComponent();
  11:             DataContext = new CustomerViewModel();
  12:         }
  13:  
  14:         private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
  15:         {
  16:             var viewModel = (CustomerViewModel)DataContext;
  17:             viewModel.Save();
  18:         }
  19:     }
  20: }


In an ideal world there would be no code at all, or only that setting the DataContext but as Silverlight makes it a little harder to use commands there is a single line, okay two counting the cast, to pass the save command on to the ViewModel.



 



The ViewModel



The ViewModel class is the one that glues the View and the Model together, hence its name [:)]. basically the ViewModel wraps each Model object, so if you want to use a collection of customer models you create a collection of CustomerViewModel objects and databind against them. The ViewModel catches all PropertyChanged events from the Model and passes them on to the user interface. Whenever the FullName property changes the FullNameFontWeight might also change so in that case an extra PropertyChanged event is raised. Note that calculation of the FontWeight to use is part of the ViewModel as the UI depends on this but it is not a business rule per se.



Another example of a property like this would be the CSS class to use in an ASP.NET application. basically if a UI property changes, create a property get to do so and databind it.



The complete ViewModel class looks like this:



   1: using System.ComponentModel;
   2: using System.Windows;
   3: using SilverlightMVVMDemo.Model;
   4:  
   5: namespace SilverlightMVVMDemo.ViewModel
   6: {
   7:     public class CustomerViewModel : INotifyPropertyChanged
   8:     {
   9:         private Customer _model;
  10:  
  11:         public CustomerViewModel()
  12:         {
  13:             Model = new Customer() { FirstName = "Maurice", LastName = "de Beijer" };
  14:         }
  15:  
  16:         public CustomerViewModel(Customer model)
  17:         {
  18:             Model = model;
  19:         }
  20:  
  21:  
  22:         public Customer Model
  23:         {
  24:             get { return _model; }
  25:             private set
  26:             {
  27:                 if (_model != null)
  28:                     _model.PropertyChanged -= ModelPropertyChanged;
  29:  
  30:                 _model = value;
  31:  
  32:                 if (_model != null)
  33:                     _model.PropertyChanged += ModelPropertyChanged;
  34:             }
  35:         }
  36:  
  37:         void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
  38:         {
  39:             OnPropertyChanged(e.PropertyName);
  40:  
  41:             if (e.PropertyName == "FullName")
  42:                 OnPropertyChanged("FullNameFontWeight");
  43:         }
  44:  
  45:  
  46:         public string FirstName
  47:         {
  48:             get { return Model.FirstName; }
  49:             set { Model.FirstName = value; }
  50:         }
  51:         public string LastName
  52:         {
  53:             get { return Model.LastName; }
  54:             set { Model.LastName = value; }
  55:         }
  56:  
  57:         public string FullName
  58:         {
  59:             get { return Model.FullName; }
  60:         }
  61:  
  62:         public FontWeight FullNameFontWeight
  63:         {
  64:             get
  65:             {
  66:                 if (FullName.Length > 20)
  67:                     return FontWeights.ExtraBold;
  68:                 else if (FullName.Length > 15)
  69:                     return FontWeights.Bold;
  70:                 else if (FullName.Length > 10)
  71:                     return FontWeights.Normal;
  72:                 else
  73:                     return FontWeights.Light;
  74:             }
  75:         }
  76:  
  77:         public event PropertyChangedEventHandler PropertyChanged;
  78:  
  79:         protected void OnPropertyChanged(string propertyName)
  80:         {
  81:             if (PropertyChanged != null)
  82:             {
  83:                 var args = new PropertyChangedEventArgs(propertyName);
  84:                 PropertyChanged(this, args);
  85:             }
  86:         }
  87:  
  88:         internal void Save()
  89:         {
  90:             // Implement the logic to save the customer.
  91:         }
  92:     }
  93: }


In this case I duplicated all properties from the Model in the ViewModel. This is not a must, some people prefer to expose and bind to the Model directly. It saves a few lines of code but I prefer not to do so. Of course there is no need to duplicate properties the UI is never going to bind to directly.



 



Model-View-ViewModel guidelines



Not very difficult at all as long as you keep to a few guidelines:



  • Never set any UI properties from your CS code with the exception of the DataContext.
  • If you need an event handler in your code behind, like the button click, make it a single line and pass the request onto the ViewModel.
  • All UI control properties that need to change at runtime do so by data binding to a ViewModel property.
  • Organize your ViewModel types per View and not per Model. It is perfectly fine to create a ViewModel that wraps two different Models. A View has only a single DataContext so you cannot bind it to multiple ViewModel objects.


 



[f1]
[f2]



And now on Twitter at http://twitter.com/mauricedb.

6 thoughts on “Using Model – View – ViewModel with Silverlight

  1. Great post! Thanks for taking the time to write this up. One question…instead of setting the DataContext in the code-behind, couldn’t you set it in the XAML by using < UserControl.DataContext > ?

    This would definitely not work if you needed to do anything to the ViewModel before setting it as the data context. But in your case, it seems like it would work to just set it in the XAML.

  2. Hi, just as a suggestion you should try and keep the UI specific logic out of the ViewModel, in this case by using a type-converter. So create a type-converter that yeilds the “boldness”type, and takes in the full name. Cheers.

  3. I’m still a bit confused on the ViewModel. “The ViewModel class is the one that glues the View and the Model together, hence its name”

    Has this not always been the “Controller” in the MVC pattern?

    There must be something different with the ViewModel, but the difference is not clear to me yet.

  4. With MVC the View typically doesn’t datatbind but the Controller sets the data from the Model in View element (controls).

    With MVP the Presenter typically arranges for the View to databind to the Model but doesn’t sit in between so is not aware of all interaction between the two.

    With a ViewModel the View databinds to the ViewModel which gets the data from the View. So the ViewModel knows about all interaction between the View and the Model. Typicall there is no direct connection.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>