Model–View–ViewModel in Silverlight

1. Introduction

Whatever software application you want to create, the most important problem that must be solved is the tight coupling. Mixing one layer with another is a very common mistake and it is the main reason for your application to be tightly coupled. For example: as a practical example in this article I will create a simple data entry application, which purpose is to load, create, edit, delete and save data. The most straightforward way to create the application is to put everything in the user interface (handling the button’s click events and writing the code there). It is the easiest way but it is far from the best. This will produce a low quality code and high complexity. And when the things are tightly coupled, one change can lead you into chasing breaking changes in the whole code. So the most important thing is to keep the layers separate, one layer – one responsibility. Yes, it is true, that creating an application with separate tiers requires additional work, but this may save you a headache in the future.

The most famous solutions and approaches (patterns) for creating a multi layer application are the MVC and MVP patterns. Since Silverlight does not require reinventing the wheel, these patterns and practices can be applied with great success when you create a Silverlight application. In previous articles I showed you how the Model-View-Presenter (MVP) pattern and the Model-View-Controller (MVC) pattern can be used in Silverlight. Today I decided to continue and to present you another pattern – it is the Model-View-ViewModel (MVVM). MVVM is tailor-made for WPF and it is an adaptation of the MVC and MVP.

View live demo

Download source

2. Introducing the Model – View – ViewModel pattern

For the first time the MVVM pattern has been unveiled by John Gossman in 2005. Since the MVVM is a very similar to the MVC (MVP), let’s see what are the differences and the resemblances.

As you can guess, the pattern separates responsibilities across three components:

  • The model is responsible for business behaviors, state management. It is very important because it wraps the access to the data.
  • The view is responsible only for the rendering of the UI elements.

These are the common things, and they should not be new to you if you are already familiar with the MVC or MVP patterns. The third component is the one that brings the difference. You probably remember that the role of the controller’s (presenter’s) is to ensure the interaction between the model and the view. One of the main characteristics of the MVVM pattern is that the third component – the ViewModel does not contain a reference to the view. Hmmm, but we expect the ViewModel to serve as a bridge between the model and the view, then how it will manage and update the view? When XAML came into our world a lot of things changed. One of them was the introduction of the declarative programming which saves us a lot of code and offers us a great flexibility, and an easy way to create animations, styling, and data bindings. Yes, the answer is data binding; this is the core of the MVVM pattern.

The bindings between the view and the ViewModel are possible because a ViewModel instance is set as a DataContext of a view. The view binds to properties on a ViewModel, which exposes data contained in model objects. When a property in the model is changed, then the new value is automatically propagated to the view through the data binding. The model is necessary to implement the INotifyPropertyChanged or theINotifyCollectionChanged interfaces.

3. Differences between WPF and Silverlight

As you will see in the practical example later in the article, one of the aspects that makes the MVVM a great pattern is the data binding infrastructure. By using a data binding, you get loose coupling between the view and the ViewModel and entirely avoid the need for writing code in the ViewModel in order to update the view. The binding system in WPF also supports input validation which is not offered in Silverlight. In WPF you can use theIDataErrorInfo interface. Another important difference between the two systems is that Silverlight does not supportcommands which will allow the view to directly consume the ViewModel functionality.

4. Practical example

In the next section I will present you a step by step example, which does not necessarily represent a real world example. It aims to help you understand how to implement the MVVM pattern. In a few words, the example represents a catalog with cars that offers you the standard functionalities for any CRUD application such as add, edit, delete, load and save.

4.1 The model

So let’s start with the model first. On the analogy of the MVC and MVP patterns the model in the MVVM is responsible for the business behavior, state management and data access. Since in the MVVM the data binding system is very important, the model must implement the INotifyPropertyChanged or INotifyCollectionChanged (Observable Collection) interface in order to bubble changes up the stack. In order to ensure the communication between the model and the view model we should create an interface for the model. This is good practice because later we could replace the model with other implementations (for example, testing purpose). You can see the interface for the model on the next diagram.

It contains various methods, properties and events that are required for data access or its manipulation. As you can see the business object Car implements the INotifyPropertyChanged interface. The CarsCollection type is a custom class which inherits from ObservableCollection<T>. Thus whenever we add or delete object(s) from the collection, the user interface will be automatically updated.

Now let’s take a look at the most important aspects of the model implementation. The method SaveChangesconstructs a new XDocument and saves it in an xml file in your isolated storage.

XElement carsElement = new XElement( "cars" );
foreach ( Car c in cars )
{
    XElement carElement = new XElement( "car",
        new XAttribute( "id", c.Id ),
        new XAttribute( "brand", c.Brand ),
        new XAttribute( "model", c.Model ),
        new XAttribute( "producer", c.Producer ),
        new XAttribute( "productionDate", c.ProductionDate ),
        new XAttribute( "price", c.Price ),
        new XAttribute( "image", c.ImageUrl ) );
    carsElement.Add( carElement );
}
 
XDocument doc = new XDocument( carsElement );
 
using ( IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication() )
{
    using ( IsolatedStorageFileStream isoStream = 
        new IsolatedStorageFileStream( "CarsData.xml", FileMode.OpenOrCreate, 
                                        FileAccess.Write, 
                                        isoStore ) )
    {
        doc.Save( isoStream );
        isoStream.Flush();
    }
}

The method LoadCars will first try to load the data from your isolated storage. If it fails then it will load the data from a default xml file which is attached in the application xap file.

var qry = ( from c in xDoc.Descendants( "car" )
        select Car.CreateNewCar(
           Int32.Parse( c.Attribute( "id" ).Value ),
           c.Attribute( "model" ).Value,
           c.Attribute( "brand" ).Value,
           c.Attribute( "producer" ).Value,
           DateTime.Parse( c.Attribute( "productionDate" ).Value ),
           Decimal.Parse( c.Attribute( "price" ).Value ),
           c.Attribute( "image" ).Value ) );
 
return qry.ToArray();

Whenever the user is adding a new car or editing an existing we should validate the data. Silverlight does not support the IDataErrorInfo interface, that’s why we should create some simple custom validation. This is the work of theValidateCar method.

public static readonly List<string> PropertiesForValidation = new List<string>(
       new string[] { "Model", "Brand", "Producer", "Price", "ProductionDate" } );
 
public string ValidateCar( Car c )
{
    string error = null;
 
    error = this.ValidateModel( c );
    if ( error != null )
        return error;
 
    error = this.ValidateBrand( c );
    if ( error != null )
        return error;
 
    error = this.ValidateProducer( c );
    if ( error != null )
        return error;
 
    error = this.ValidatePrice( c );
    if ( error != null )
        return error;
 
    error = this.ValidateProductionDate( c );
    if ( error != null )
        return error;
 
    return error;
}

The AddCar and DeleteCar methods are pretty straightforward. The four events are used to notify the observers when the data is loaded and saved or something goes wrong.

4.2 View and view model

In our application we have a simple design that is used to browse and edit car information. You can see the main page on the next figure.

In order to create the view I used a standard controls from the Silverlight library (buttons, text blocks, text boxes, tab control and data grid). As you remember the view model does not contain a reference to the view. The connection between the components is realized through data binding. You can see the view model class on the next diagram.

The view model must meet the following requirements:

  • It has to expose a data bindable collection – this is the CarsCollection. It must be an ObservableCollection so any changes will be automatically reflected on the user interface.
  • Another important issue is to provide a default constructor. Otherwise if your view model object does not have a default constructor then when you start the application you will receive my favorite exception: AG_E_PARSER_UNKNOWN_TYPE

Once our view model meets these requirements, it is ready to be bound directly to the XAML. In order to implement the data binding you should create a new instance of the view model as a resource (in the resources of your view) and then to bind it to the main container (in our case this is a Grid). You can see the whole procedure on the next figures.

First we need a data bindable collection and default constructor:

public CarsViewModel() :
    base()
{
}
 
\\ Other properties and methods
 
public CarsCollection CarsCollection
{
    get { return this.CarModel.CarsCollection; }
}

Second we should declare a new instance of the view model in the resources of the view:

Third we should set bind the view model to the main container:

<Grid x:Name="LayoutRoot" Background="Transparent"
          DataContext="{Binding Path=CarsCollection, >
          Source={StaticResource CarsViewModelDS}}"
 
<!-- Insert here the child elements -->
 
</Grid>

Finally we should again use data binding to propagate the data source to our data grid (which will display the data). Here you can see the XAML mark up for our data grid.

<data:DataGrid x:Name="dgrWokrspace" 
   RowDetailsVisibilityMode="VisibleWhenSelected" AutoGenerateColumns="False"
   ItemsSource="{Binding}">
 
<data:DataGrid.Columns>
    <data:DataGridTextColumn Binding="{Binding Id}" Header="Id" IsReadOnly="True"/>
    <data:DataGridTextColumn Binding="{Binding Brand}" Header="Brand" IsReadOnly="True"/>
    <data:DataGridTextColumn Binding="{Binding Model}" Header="Model" IsReadOnly="True"/>
    <data:DataGridTextColumn Binding="{Binding Price,  Header="Price" IsReadOnly="True"/>
        Converter={StaticResource PriceConverter}}"
    <data:DataGridTextColumn Binding="{Binding ProductionDate,  Header="Production Date" 
        Converter={StaticResource DateTimeConverter}}"
        IsReadOnly="True"/>
    <data:DataGridTemplateColumn Header="Image" IsReadOnly="True">
        <data:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Image Width="40" Height="40" Source="{Binding ImageUrl}" 
                    Stretch="Fill"/>
            </DataTemplate>
        </data:DataGridTemplateColumn.CellTemplate>
    </data:DataGridTemplateColumn>
</data:DataGrid.Columns>
 
</data:DataGrid>

Now whenever the LoadCars method of the view model is invoked,the data grid will be automatically populated.

You can see the live demo here

5. Final words

In this article I showed you another design pattern that can be used in the software development. It is the Model – View – ViewModel pattern. The pattern is tailor – made for WPF. However, the pattern can be used with great success in Silverlight applications (despite of some limitations). The MVVM is a simple but extremely effective set of guidelines for designing and implementing a Silverlight/WPF application. Like the other two patterns, it allows you to create a strong separation between the presentation, behavior and data, making it easier to control and maintain each one of them.

posted @ 2011-09-08 10:40  Areas  阅读(375)  评论(0编辑  收藏  举报