Silverlight with MVVM

Sul Aga photo
0
Last Updated
Feb 25, 2013
Source Code

Introduction

In this sample code I will build a Silverlight application by using the MVVM pattern. The idea of the sample code is to imply the MVVM design pattern techniques in building a Silverlight application. In this sample application I will build the three different layers of the MVVM pattern and they are View, Model and ViewModel.

The Model

The Model layer in MVVM contains your data access methods and your business objects. In this sample code we are using a WCF service to build our Model layer. The Model here contains merely of two methods. One method which returns a list of random generated products and the other one is to update a given product. The latter returns true without doing any actual updates because we are not using any data source. Here is the listing for the service contract, contract implementation and the Product object.

[DataContract]
public class Product
{
  [DataMember]
  public int Id { get; set; }
  [DataMember]
  public string Name { get; set; }
  [DataMember]
  public double Price { get; set; }
  [DataMember]
  public string Description { get; set; }
}

WCF Service contract

[ServiceContract]
public interface IProductService
{
    [OperationContract]
    bool UpdateProduct(int productId);
    [OperationContract]
    List<product> GetProducts();
}

IProductService implementation

[ServiceContract]
public interface IProductService
{
  public class ProductService : IProductService
  {
      public bool UpdateProduct(int productId)
      {
        return true;
      }
      public List<product> GetProducts()
      {
          var productList = new List<product>();
          for (int i = 1; i < 21; i++)
          {
            productList.Add(new Product { Id = i, Name = "Product " + i, Description = "This is Product " + i, Price = i * 1.1 });
          }
         return productList;
     }
  }
}

The View

The View layer in MVVM contains the UI xaml markup. Note that in order for the View to trigger events and for these events to be handeled in the ViewModel, there has to be a sort of Binding between the two layers. In this example we are binding the View to the ViewModel using the DataContext propery of the GridView. The DataContext in this case will be our ViewModel


 <UserControl.Resources>
    <viewModels:MainPageViewModel x:Key="ViewModel" />
  </UserControl.Resources>
  
    <Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource ViewModel}}" >

Note how we are defining our ViewModel class as a resource then using that resource in the DataContext property of the GridView When binding our View to our ViewModel we can then make use of all the properties which are offered by this ViewModel. In Figure 1 you can see that we can populate the products drobdown list, display the selected product properties and trigger the update product event when clicking on the update button. All these actions are performed on UI but being processed in the ViewMode. There is no code in the View code behind file. Everything goes into the ViewModel. Here is the listing of the View (UI)


<UserControl xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"  x:Class="SilverlightSampleCodeMVVM.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
      xmlns:viewModels="clr-namespace:SilverlightSampleCodeMVVM.ViewModel"
    d:DesignHeight="545" d:DesignWidth="550" Width="545" Height="900">
    <UserControl.Resources>
        <viewModels:MainPageViewModel x:Key="ViewModel" />        
    </UserControl.Resources>
    <toolkit:BusyIndicator IsBusy="{Binding Source={StaticResource ViewModel}, Path=IsLoading, Mode=TwoWay}">
        
        <Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource ViewModel}}" ShowGridLines="False" VerticalAlignment="Top">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition Height="50" />
                <RowDefinition Height="200"/>
                
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="Products" Style="{StaticResource Title}" />
            <TextBlock Text="Select a Product" Grid.Row="1" Grid.Column="0" Style="{StaticResource Label}" />
            <ComboBox Style="{StaticResource ProductList}" DisplayMemberPath="Name" 
                      SelectedItem="{Binding Path=CurrentProduct, Mode=TwoWay}" 
                      ItemsSource="{Binding Path=ProductList}" Grid.Row="1" Grid.Column="1" 
                      Height="24" Name="ComboBoxProducts" />
            <TextBlock Text="Product Id" Grid.Row="2" Grid.Column="0" Style="{StaticResource Label}" />
            <TextBlock Text="{Binding Path=CurrentProduct.Id}" Grid.Row="2" 
                       Grid.Column="1" Style="{StaticResource LabelValue}" />
            <TextBlock Text="Name" Grid.Row="3" Grid.Column="0" Style="{StaticResource Label}" />
            <TextBlock Text="{Binding Path=CurrentProduct.Name, Mode=TwoWay}" 
                       Grid.Row="3" Grid.Column="1" Style="{StaticResource LabelValue}" />
            <TextBlock Text="Description" Grid.Row="4" Grid.Column="0" Style="{StaticResource Label}" />
            <TextBlock Text="{Binding Path=CurrentProduct.Description, Mode=TwoWay}" 
                       Grid.Row="4" Grid.Column="1" Style="{StaticResource LabelValue}" />
            <TextBlock Text="Price" Grid.Row="5" Grid.Column="0" Style="{StaticResource Label}" />
            <TextBlock Text="{Binding Path=CurrentProduct.Price, Mode=TwoWay}" Grid.Row="5" 
                       Grid.Column="1" Style="{StaticResource LabelValue}" />
            <Button Grid.Row="6" Grid.ColumnSpan="2" Command="{Binding UpdateProductCommand}" 
                    Content="Update" Style="{StaticResource UpdateButtonStyle}" />
            <TextBlock Text="{Binding Path=Message,Mode=TwoWay}" Grid.Row="7" 
                       Grid.ColumnSpan="2" Style="{StaticResource MessageStyle}" />
            
           
        </Grid>
    </toolkit:BusyIndicator>
</UserControl>

The ViewModel

The ViewModel class will wire the View with the Model. It communicates with the View using Commands and Properties Binding. It communicates with the Model using the WCS service we created earlier. Here is the list of properties and the implementation of one of them

private Product _currentProduct;  
  private ObservableCollection<Product> _productList;
  private string _message;
  private bool _isLoading;
  public ObservableCollection<Product> ProductList
  {
     get
     {
        return _productList;
     }
     set
     {
        if (_productList != value)
        {
           _productList = value;
           RaisePropertyChanged("ProductList");
        }
     }
  }

Here is the description of each property:

Product _currentProduct: The current selected product from the product dropdown list. This property is being bind to the View to display the properties of a selected product like price.
ObservableCollection<Product> _productList: The products list retreived from the WCF service which is being bind to the products dropdown list in the View. This List is of type ObservableCollection. The advantage of this list type is that it provides notification whenever the list is changed either by adding or removing items from it.
string _message: A feedback message which tells the user about update product event. private bool _isLoading;
bool _isLoading: a flag which is used to display or hide the busy loading box
Note how we are notifying the UI when any property is set using RaisePropertyChanged method. I will show the implementation of this method later. The rest of the properties are implemented in the same way

Commands

Commands are the way to wire UI events to ViewModel. For example in this example we have an update button for the product. When the user clicks this button it should fire the update method in the ViewModel. The way we are implementing this is by using RelayCommand type. Here is the constructor and update command listing. Note how we are binding the UpdateProductCommand to the UpdateProduct method.

public MainPageViewModel()
 {
   if (!IsDesignTime)
   {
      IsLoading = true;
      GetProducts();
      UpdateProductCommand = new RelayCommand(UpdateProduct);
   }
 }
 public RelayCommand UpdateProductCommand
 {
    get;
    private set;
 }

GetProducts and UpdateProduct

In these two methods we are calling our WCF service to retreive the products and to update a given product when the user clicks the update button. Note how we are using the lambda expression to call the WCF service asynchronously. Note also the seeting of the IsLoading flag which is bound to the BusyIndicator dialouge in the UI

private void GetProducts()
  {
    var serviceClient = new ProductServiceClient();
    serviceClient.GetProductsCompleted += (s, e) => { ProductList = e.Result; IsLoading = false;};
    serviceClient.GetProductsAsync();
  }
  private void UpdateProduct()
  {
    IsLoading = true;
    var serviceClient = new ProductServiceClient();
    serviceClient.UpdateProductCompleted += (s, e) => { Message = (e.Result) ? "Product successfully updated" : 
                                                                                  "Unable to update     roduct"; IsLoading = false; };
    serviceClient.UpdateProductAsync(CurrentProduct.Id);
  }

RelayCommand and the ViewModel base class

The RelayCommand class implements the ICommand interface. It implements the following two methods for this interface:

  • CanExecute: rturns a boolean specifying whether the command is enabled or disabled
  • Execute: in this method you call your handler to the command

Here is the listing for the RelayCommand implementation. Note how we are setting our handler method in the constructor. Also note how we are making use of the _isEnabled flag in the CanExecute method. If we set this to false then the Update product button will be disabled

public class RelayCommand : ICommand
  {
  private readonly Action _handler;
  private bool _isEnabled;
  public RelayCommand(Action handler)
  {
  _handler = handler;
  }
  public bool IsEnabled
  {
  get { return _isEnabled; }
  set
  {
  if (value != _isEnabled)
  {
  _isEnabled = value;
  if (CanExecuteChanged != null)
  {
  CanExecuteChanged(this, EventArgs.Empty);
  }
  }
  }
  }
  public bool CanExecute(object parameter)
  {
  return IsEnabled;
  }
  public event EventHandler CanExecuteChanged;
  public void Execute(object parameter)
  {
  _handler();
  }
  }

In the ViewModelBase class we are implementing the INotifyPropertyChanged interface. In this class we are making use of the PropertyChanged event. This event will be raised once a property is being changed in the ViewModel so the UI can reflect the change. IsDesignTime property will return true if we are in the design time and false otherwise. Here is the listing of the ViewModelBase class.

public class ViewModelBase: INotifyPropertyChanged
  {
  public bool IsDesignTime
  {
  get { return DesignerProperties.IsInDesignTool; }
  }
  #region INotifyPropertyChanged Members
  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void RaisePropertyChanged(string propName)
  {
  var propChanged = PropertyChanged;
  if (propChanged != null)
  {
  propChanged(this, new PropertyChangedEventArgs(propName));
  }
  }
  #endregion
  }

Putting everything together

Figure 1 will explain all the various components of this application and how they are communicating with each other.

Figure 1: Silverlight with MVVM

Summary

In this sample code we went thought creating a simple Silverlight application by following the MVVM design pattern. We created all the various building blocks of this pattern and they are View, Model and ViewModel. If you need more information about this pattern please read this article which explains more about MVVM.

Comments