Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#

A Totally Simple Introduction to the MVVM Concept with Silverlight

Rate me:
Please Sign up or sign in to vote.
4.77/5 (23 votes)
5 May 2011CPOL7 min read 88.3K   3.7K   42   22
In this article, I reduce the use of the MVVM pattern in Silverlight to the absolute minimum lines of code in order to understand the basics.

Note: You may get an error message saying Windows has disabled the DLLs that have been downloaded off of the internet. If that happens, then navigate to those files using Explorer and in the Properties display, you will see a button saying 'unblock'. Click on that and you will be able to compile.

Why MVVM?

In my experience in programming, my guiding maxim has always been 'complexity is the enemy'. As programmers, we are quite good at building programs / systems to a point. Past that point, productivity starts to slow down considerably. We have all been there. You have an idea where the code is that you want to work on but the project has reached a size that finding things starts to take time. Then you can no longer work at the 'speed of thought' but rather at the speed of retrieval.

Patterns are an interesting solution to the problem of 'complexity' in a system. I have always thought of patterns as a sort of innoculation or vaccine for system complexity. To a certain extent, you are adding complexity at the start of the coding process with the hope that you will limit complexity later. The so called MVVM pattern is, I think, a good example of this. The notion of not using the 'code behind' is counter intuitive. Nothing is faster that doing a simple event based app where the UI is in XAML and the coding is in the code behind. The big 'but' is as the system grows, you fairly quickly reach a complexity productivity plateau. We have all been there. I know I have.

With MVVM, we simply leverage the concept of 'binding' in Silverlight to the maximum. We are all familiar with data binding but with the potential of 'command' binding, we can totally separate the UI (View) from the processing (ViewModel). The abstract process flow in this pattern is Model<->ViewModel<->View. This leads to the much touted by-product of 'separation of concerns'. This is the concept that it is a huge advantage in development to have the development of the UI so completely separate from the processing (wiring) that the UI segment of the process can be switched out without causing exceptions in the code. MVVM is also considered a good strategy from the point of testability.

The problem from a coder's point of view is that this is a new paradigm that relies on syntax and conventions that are not obvious. There is a learning curve both for the style and the basic functions like getting data to the page responding to events and validation.

My strategy when confronted with large 'chunks' of new technology is to try and reduce the pieces to an absolutely simple implementation. I spent a lot of time looking for a good example and while there are a number of good articles on MVVM, I wanted to cover all the basics of MVVM in one pass.

  • Separation of concerns
  • Data binding
  • Command binding
  • Validation
  • Unit testing
  • Link to a framework (Prism)

The UI on the app is very simple.

MainPage.PNG

You can add the data from the text box and it will show up in the data grid. If you try and add data that is too long, you will get an error message for the field and the pop-up. While this validation might seem to be overkill, I am just trying to show the various 'moving parts' of the MVVM strategy.

The XAML

XML
<UserControl x:Class="MVVMtest1.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"
    xmlns:app="clr-namespace:MVVMtest1"
    mc:Ignorable="d"
    d:DesignHeight="324" d:DesignWidth="459" 
	xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" >
    <UserControl.DataContext>
        <app:HelloWorldModel />
    </UserControl.DataContext>
    <Grid x:Name="LayoutRoot" Background="White" Height="326" Width="469">
        <sdk:DataGrid AutoGenerateColumns="true" Height="182" 
	ItemsSource="{Binding Path=myData}" 
                      HorizontalAlignment="Left" Margin="159,107,0,0" 
                      Name="dataGrid1" VerticalAlignment="Top" Width="108" >            
        </sdk:DataGrid>
        <Button Content="Add Data" Height="23" HorizontalAlignment="Left" 
                Command="{Binding SaveCommand}"  
                CommandParameter="{Binding Text,ElementName=txtText,Mode=TwoWay, 
                ValidatesOnDataErrors=True}"
                Margin="96,45,0,0" Name="btnAdd" VerticalAlignment="Top" Width="75" />
        <TextBox TabIndex="0" Height="23" Text="{Binding inputValue, Mode=TwoWay, 
                    ValidatesOnDataErrors=True}" HorizontalAlignment="Right" 
			Margin="0,44,163,0" 
                    Name="txtText" VerticalAlignment="Top" Width="120"  />
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="150,12,0,0" 
		Name="textBlock1" 
                   Text="MVVM Hello World Example" VerticalAlignment="Top" 
			FontWeight="Bold" />
        <TextBlock Height="23" HorizontalAlignment="Left" 
		Margin="96,78,0,0" Name="textBlock2"
                   Text="Please limit your data to 10 characters" 
			VerticalAlignment="Top" Width="225" />
        <Grid Visibility="{Binding Path=MessageVisibility}">
            <Grid.RowDefinitions>
                <RowDefinition Height="2*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Rectangle Grid.RowSpan="2" Fill="Black" Opacity="0.08" />
            <Border Grid.Row="0" BorderBrush="blue" BorderThickness="1" CornerRadius="10"
                        Background="White"
                        HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid Margin="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto" />
                        <RowDefinition Height="40" />
                    </Grid.RowDefinitions>
                    <TextBlock Text="{Binding Path=Message}" 
                               MinWidth="150"
                               MaxWidth="300"
                               MinHeight="30"
                               TextWrapping="Wrap" Grid.Row="0" Margin="10, 5, 10, 5" />
                    <Button Content="OK" Grid.Row="1" 
                            Margin="5" Width="100"
                            Command="{Binding Path=HideMessageCommand}"/>
                </Grid>
            </Border>
        </Grid>
    </Grid>
</UserControl>

While there a number of points of 'magic' in MVVM, the first point is the data binding. The following code snippet sets the data binding of the entire user control to the ViewModel with the code:

XML
<UserControl.DataContext>
    <app:HelloWorldModel />
</UserControl.DataContext>

With this data binding all of the controls with the user control can be bound to the properties of the ViewModel. As well the 'commands' and 'events' on the user control can be bound to 'command properties' in the ViewModel. First let's look at the simple binding. The DataGrid ItemSource is bound to the 'myData' property. Since this property is defined as an ObservableCollection, this data binding is implicitly 2 way and any changes in this collection will show on the screen 'automagically'.

C#
ItemsSource="{Binding Path=myData}"

Next, we will bind to the command event of the button.

C#
Command="{Binding SaveCommand}"  
                CommandParameter="{Binding Text,ElementName=txtText,Mode=TwoWay, 
                ValidatesOnDataErrors=True}"

The important points of this binding are:

  • We are binding to the Command property, not the click event.

    This is the basic commanding functionality that is built into Silverlight 4.0. Binding to events is out of scope for this article.

  • We can define a parameter for this command that is a property of another control.
  • In this parameter, we can add validation simply by adding the ValidatesOnDataErrors=True syntax.

The ViewModel

So now we have the XAML set up, let's take a look at the ViewModel code to see how everything works.

C#
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Windows;
using Microsoft.Practices.Prism.Commands;

namespace MVVMtest1
{
    public class HelloWorldModel : INotifyPropertyChanged, IDataErrorInfo
    {
        private string _inputValue;
        private string _message;
        private bool isError = false;

        public HelloWorldModel()
        {
            LoadData();
            DefineCommands();
            MessageVisibility = Visibility.Collapsed;
        }
        
        public string Message
        {
            get { return _message; }
            set
            {
                _message = value;
                NotifyPropertyChanged("Message");
            }
        }
                                                                                         
        public string inputValue
        {
            get { return _inputValue; }
            set
            {
                _inputValue = value;
                OnPropertyChanged("inputValue");
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private ObservableCollection<dataitemclass> _myData;

        public ObservableCollection<dataitemclass> myData
        {
            get
            {
                return _myData;
            }
        }

        private void LoadData()
        {
            _myData = new ObservableCollection<dataitemclass>();           
            _myData.CollectionChanged += 
		new System.Collections.Specialized.NotifyCollectionChangedEventHandler
		(_myData_CollectionChanged);
            _myData.Add(new DataItemClass { Id= _myDataId, Name= "first line" });
            _myData.Add(new DataItemClass { Id = _myDataId, Name = "second line" });
            _myData.Add(new DataItemClass { Id = _myDataId, Name = "third line" });
        }

        int _myDataId = 1;
        
        void _myData_CollectionChanged(object sender, 
		System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            _myDataId++;
        }        

        private Visibility messageVisibility;
        public Visibility MessageVisibility
        {
            get { return messageVisibility; }
            set
            {
                if (messageVisibility != value)
                {
                    messageVisibility = value;
                    NotifyPropertyChanged("MessageVisibility");
                }
            }
        }

        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }        

        private ICommand _SaveCommand;
        public ICommand SaveCommand
        {
            get
            {
                return _SaveCommand;
            }
        }

        private ICommand _HideMessageCommand;
        public ICommand HideMessageCommand
        {
            get
            {
                return _HideMessageCommand;
            }
        }

        private void DefineCommands()
        {
            // this uses the prism framework but could also use a simple class 
            //(ICommandImplementation.cs)
            _SaveCommand = new DelegateCommand<string> (OnSaveCommand);
            _HideMessageCommand = new DelegateCommand(OnHideMessageCommand);
        }

        private void OnHideMessageCommand()
        {
            MessageVisibility = Visibility.Collapsed;
        }

        private void OnSaveCommand(string s)
        {
            if (!isError)
            {
                _myData.Add(new DataItemClass { Id = _myDataId, Name = s });
                this.inputValue = "";
            }
        }

        public string Error
        {
            get { return null; }
        }

        public string this[string propertyName]
        {
            get
            {
                if (propertyName == "inputValue")
                {
                    if (this.inputValue != null)
                    {
                        if (this.inputValue.Length > 10)
                        {
                            isError = true;
                            MessageVisibility = Visibility.Visible;
                            _message = "Why are you typing such long words";
                            Message = _message;
                            return _message;
                        }
                        else
                        {
                            isError = false;
                        }
                    }
                }
                return null;
            }
        }
    }

    public class DataItemClass 
    {       
        public int Id { get; set; }      
        public string Name { get; set; }           
    }
}

There is a lot of magic on this page. Note that this class inherits from INotifyPropertyChanged and IDataErrorInfo. These interfaces provide the interaction which we get for 'free' between this class and the XAML. So, when we add an item to the collection behind the myData property, then the datagrid on the UI is updated 'automagically'. Also when a property 'get' occurs, an event triggers a 'get' on the 'public string this[string propertyName]' property and this get is the basis for the implementation of the IDataErrorInfo. All of this functionality is enabled by the call to the constructor which happens as a result of the data binding in the XAML.

The ability for this class to process commands from the XAML is the key functionality that enables MVVM. The commands are bound to properties on the ViewModel that invoke events. There must be some code that implements ICommand in order for this strategy to work. You can use a wide range of 'frameworks' from a simple single class (I have included one of these in the code sample) or you can use something like Prism that has been released into open source by the Microsoft Patterns and Practices team. I used Prism (slight overkill for this project) which involved referencing the DLL and adding a using:

C#
using Microsoft.Practices.Prism.Commands;

The three 'pieces' of code needed for the command structure are shown below:

C#
// the property referenced in the XAML
private ICommand _SaveCommand;
public ICommand SaveCommand
{
    get
    {
        return _SaveCommand;
    }
}

// called by the constructor
private void DefineCommands()
{
    // this uses the prism framework but could also use a simple class
    // (ICommandImplementation.cs)
    _SaveCommand = new DelegateCommand<string> (OnSaveCommand);
    _HideMessageCommand = new DelegateCommand(OnHideMessageCommand);
}

// this is the actual 'work'
private void OnSaveCommand(string s)
{
    if (!isError)
    {
        _myData.Add(new DataItemClass { Id = _myDataId, Name = s });
        this.inputValue = "";
    }
}

The project then can implement ICommand. This is the totally simple implementation of the basic binding and command structure.

The problem with all the attempts to 'manage' basic display and CRUD tasks in the past has always been the 5% rule. The 'framework' works beautifully for 95% of the functionality needed but you then spend 95% of the project development on that last 5% of the functionality. Hopefully MVVM will not end in the pantheon of fallen frameworks. There are some more key pieces of this MVVM pattern implementation that speak to the suitability of this technology for enterprise development like validation and testability.

Validation

I have included a simple validation example in the code. We implement IDataErrorInfo which has the following code for its base implementation:

C#
public class ErrorClass : IDataErrorInfo    
{
    public string Error
    {
        get { throw new NotImplementedException(); }
    }

    public string this[string columnName]
    {
        get { throw new NotImplementedException(); }
    }
}

You can look at the code in my class above to see how this can be wired into your ViewModel class. It helps to put breakpoints in to follow the flow of the processing for this functionality as it is quite complicated.

error.PNG

The Message Box

Without the code behind, it is somewhat more difficult to message the user with a modal message box. I have included the code for this. In the example, if the user tries to enter data that is longer than 10 characters, then the TextBox shows an error and the modal message box is displayed.

popup.PNG

Testing MVVM

Finally, I have included a couple of tests. The first thing to remember when you are testing Silverlight with MVVM is DO NOT USE the standard test framework that can be installed with a typical Visual Studio project. Use the Silverlight Unit Test Application template. The first test in the example shows how to test a 'command' and the second shows how to test validation.

runTests.PNG

The test template has a useful screen display that allows for running all or a selected group of tests.

The results look like this:

units.PNG

I have tried to put together the most simple example of the base implementation of the MVVM pattern. There is, of course, much more to building out Silverlight applications such as converters and behaviors but hopefully this will get you over that nasty initial learning curve.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Lionbridge
United States United States
I have programmed in a variety of languages while building everything from process control to ERP systems. I am an early adopter of AJAX and RIA Internet programming styles. I have worked in number of capacities from programmer to lead to architect. I have worked for most of the majors on the west coast and have seen what works and what doesn't in software engineering.

Comments and Discussions

 
GeneralGood article Pin
aicha200816-Oct-12 12:08
aicha200816-Oct-12 12:08 
GeneralMy vote of 5 Pin
Farhan Ghumra28-Aug-12 2:51
professionalFarhan Ghumra28-Aug-12 2:51 
GeneralINotifyPropertyChanged not used Pin
Tic126-Aug-11 0:15
Tic126-Aug-11 0:15 
QuestionCan't debug Pin
Tic110-Aug-11 0:21
Tic110-Aug-11 0:21 
GeneralMy vote of 5 Pin
defwebserver9-May-11 11:06
defwebserver9-May-11 11:06 
GeneralMy vote of 1 Pin
Member 31648667-May-11 22:11
Member 31648667-May-11 22:11 
GeneralMy vote of 4 Pin
William E. Kempf5-May-11 9:30
William E. Kempf5-May-11 9:30 
GeneralMy vote of 1 Pin
Member 41304074-May-11 10:06
Member 41304074-May-11 10:06 
GeneralRe: My vote of 1 Pin
Pat Capozzi4-May-11 11:46
Pat Capozzi4-May-11 11:46 
GeneralProblem loading SilverlightTest1 project Pin
Tic13-May-11 21:54
Tic13-May-11 21:54 
GeneralRe: Problem loading SilverlightTest1 project Pin
Pat Capozzi4-May-11 11:50
Pat Capozzi4-May-11 11:50 
GeneralRe: Problem loading SilverlightTest1 project Pin
Tic16-May-11 0:48
Tic16-May-11 0:48 
GeneralRe: Problem loading SilverlightTest1 project Pin
Pat Capozzi6-May-11 5:06
Pat Capozzi6-May-11 5:06 
GeneralRe: Problem loading SilverlightTest1 project [modified] Pin
Tic17-May-11 5:07
Tic17-May-11 5:07 
GeneralRe: Problem loading SilverlightTest1 project Pin
Pat Capozzi9-May-11 18:40
Pat Capozzi9-May-11 18:40 
GeneralRe: Problem loading SilverlightTest1 project [modified] Pin
hero36163-May-12 3:51
hero36163-May-12 3:51 
GeneralRe: Problem loading SilverlightTest1 project Pin
Member 91333329-Aug-12 4:28
Member 91333329-Aug-12 4:28 
GeneralRe: Problem loading SilverlightTest1 project Pin
Pat Capozzi9-Aug-12 4:51
Pat Capozzi9-Aug-12 4:51 
GeneralAdmittedly, I've just skimmed it but.. Pin
Dave Cross3-May-11 21:48
professionalDave Cross3-May-11 21:48 
GeneralRe: Admittedly, I've just skimmed it but.. Pin
Garrett.Be3-May-11 23:02
Garrett.Be3-May-11 23:02 
GeneralMy vote of 5 Pin
Tic13-May-11 18:24
Tic13-May-11 18:24 
GeneralRe: My vote of 5 Pin
Bikram Panjikar3-Sep-13 19:07
Bikram Panjikar3-Sep-13 19:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.