Click here to Skip to main content
Click here to Skip to main content

A Totally Simple Introduction to the MVVM Concept with Silverlight

By , 5 May 2011
 

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

<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:

<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'.

ItemsSource="{Binding Path=myData}"

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

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.

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:

using Microsoft.Practices.Prism.Commands;

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

// 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:

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)

About the Author

Pat Capozzi
Architect Lionbridge
United States United States
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralGood articlememberaicha200816 Oct '12 - 12:08 
thank you, it's a good article.
I have only one remark about the code illustrated in this article : if I copy/past it , it will not works because there will be an error : "dataitemclass" is not recognized :it have to be the name of the model : Dataitemclass.
GeneralMy vote of 5memberFarhan Ghumra28 Aug '12 - 2:51 
Excellent
GeneralINotifyPropertyChanged not usedmemberTic126 Aug '11 - 0:15 
Hi Pat,
 
I have noticed that though you have inherited viewmodel from InotifyPropertyChanged but you have never used it. Am i correct.
Also need to understand from you why indexer(
public string this[string propertyName]
) in viewmodel is only handling inputvalue property but not Message propertythough both have same signature.
 
Regards
Tic
QuestionCan't debugmemberTic110 Aug '11 - 0:21 
Hi Pat,
 
I am fan of your simplistic coding. However i am not able to debug this code.
I added breakpoints on each method of HelloWorldModel class but none of the breakpoint is hit during runtime.
Do we have to attach to something as i can't even see this application executable in task manager.
I am using IIS7. If you are using attach process while debugging, please let me know the process name to which i have to attach.
 
Thanks in advance.
 
Cheers
Tic1
GeneralMy vote of 5mvpdefwebserver9 May '11 - 11:06 
MVVM is complicated so you did make it simple Smile | :)
GeneralMy vote of 1memberMember 31648667 May '11 - 22:11 
Simple? - No, you are making something simple complicated!
GeneralMy vote of 4memberWilliam E. Kempf5 May '11 - 9:30 
You did a fairly decent job of showing the minimal essence of the pattern, but the article failed to explain much of it. There are also some glaring code issues (OnPropertyChanged and NotifyPropertyChanged are duplicates and certainly can lead to confusion for someone new to this). Also, the message display for validation would have been better handled through the validation adorner, though your approach did illustrate how such things could be done for other purposes. All in all, not bad, but there's room for improvement.
GeneralMy vote of 1memberMember 41304074 May '11 - 10:06 
Sorry, but it is not so simple MVVM sample.
GeneralRe: My vote of 1memberpjacapozzi4 May '11 - 11:46 
MVVM is inherently complicated, so it is not possible to reduce it to something that is inherently simple. What I have tried to do is make the basic pieces easily understandable.
GeneralProblem loading SilverlightTest1 projectmemberTic13 May '11 - 21:54 
Hi,
I am trying to open the solution.All but SilverlightTest1 project opens up.
While opening SilverlightTest1 project , it gives following error and exits.
 
C:\Documents and Settings\bhaskar.sharma\Desktop\MVVMHelloWorldPrism\MVVMHelloWorldPrism\SilverlightTest1\SilverlightTest1.csproj : error : Unable to read the project file 'SilverlightTest1.csproj'.
C:\Documents and Settings\bhaskar.sharma\Desktop\MVVMHelloWorldPrism\MVVMHelloWorldPrism\SilverlightTest1\SilverlightTest1.csproj(115,3): The imported project "C:\Documents and Settings\bhaskar.sharma\Desktop\MVVMHelloWorldPrism\MVVMHelloWorldPrism\SilverlightTest1\Microsoft.Silverlight.Toolkit.Build.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.

 
Kindly fix this and upload the working one.
Also please share the location of model
 
Cheers
TicArch
GeneralRe: Problem loading SilverlightTest1 projectmemberpjacapozzi4 May '11 - 11:50 
Are you still having problems? It have tried loading the project on another machine and it works fine. you might need to download some silverlight code from http://www.silverlight.net/getstarted
GeneralRe: Problem loading SilverlightTest1 projectmemberTic16 May '11 - 0:48 
Hi,
I tried installing all the latest silverlight tools for visual studio from the link provided by you but it didn't worked.
I even uninstalled whole VS, reinstalled it, then installed silverlighttoolsfor visual studio update and then tried opening it , but same issue.Then i installed Visual studio service pack 1 and tried opening again but same problem.
 
Can you please let me know as what all updates are installed on your machine where this solution is working.
Also request you to build this solution on a system which has all the latest updates installed.
 
Looking forward for your help as this is the only MVVM implementation which seems to be workable for me
 
Cheers
Tic
GeneralRe: Problem loading SilverlightTest1 projectmemberPat Capozzi6 May '11 - 5:06 
Hi Tic,
 
In order to troubleshoot this, I need to know a couple of things.
 
As I understand it, you can get everything runing except the test project, Is this correct?
 
Does it show as 'unavailable' in the solution?
 
If you try and add a new project to the solution can you see the choice / template 'Silverlight Unit Test Application' when you highlight the Silverlight choice in Installed templates?
 
If you can let me know this information, then we can move forward.
 
regards,
 
PatC
GeneralRe: Problem loading SilverlightTest1 project [modified]memberTic17 May '11 - 5:07 
Hi,
 
Due to installs and uninstalls of different service packs, my visual studio got messed up and now it is not even starting.
Will have to get it restored and will then get back to you soon.
Meanwhile,can you please let me know whether you have installed Visual Studio 2010 service pack1 directly on your computer or have you installed Silverlight4_Tools.exe first and then VS SP1.I will take your path for the whole new setup
 
Cheers
Tic
 
Edited on Monday:-
Oh my god, I think we need only two projects(MVVMHelloWorld, MVVMHelloWorld.Web) to run this and it seems that we don't need SilverlightTest1 project.I am mentioning this as i am able to run the project in the fashion identical to what you have written.
Please confirm my understanding
 
Cheers
Tic

modified on Monday, May 9, 2011 1:55 AM

GeneralRe: Problem loading SilverlightTest1 projectmemberPat Capozzi9 May '11 - 18:40 
Hi,
 
The Silverlight MVVM demo will run fine with just the two project. You just need the silverlight test project for the tests.
GeneralRe: Problem loading SilverlightTest1 project [modified]memberhero36163 May '12 - 3:51 
I had the same problem. If you install Silverlight_4_Toolkit_April_2010 all three projects work fine.
GeneralRe: Problem loading SilverlightTest1 projectmemberMember 91333329 Aug '12 - 4:28 
yes i can see but it's still shows unavailable in solution
GeneralRe: Problem loading SilverlightTest1 projectmemberPat Capozzi9 Aug '12 - 4:51 
The problem probably relates to the version of silverlight that you are running. If you are running 5 then change the line in the .csproj where it mentions the silverlight version to v5.0 from v4.0
GeneralAdmittedly, I've just skimmed it but..memberDave Cross3 May '11 - 21:48 
I can see the viewmodel because you told me that was what it was. I can guess that the XAML represents the View because it's clearly a UI. Where's the model? And what part does it play? How dows all this 'auto-magically' stuff happen anyway? More importantly, how can I fix it if I don't like the way it operates?
 
I'm still just as confused as ever.
 
But then I'm an old dog and I like to be able to see the plumbing.
 
Dave Cross
GeneralRe: Admittedly, I've just skimmed it but..memberGarrett.Be3 May '11 - 23:02 
The model is :
public class DataItemClass 
    {       
        public int Id { get; set; }      
        public string Name { get; set; }           
    }
For the other questions, it's better the author explains it. I think he'll be easier to understand than me as I'm just starting to use this pattern.
GeneralMy vote of 5memberTic13 May '11 - 18:24 
Excellent article. Thanks a lot for helping beginners like me.It is really very simplyfied.Now i am going to download the code and run it. God Bless you!!

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 5 May 2011
Article Copyright 2011 by Pat Capozzi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid