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.
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:
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()
{
_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:
private ICommand _SaveCommand;
public ICommand SaveCommand
{
get
{
return _SaveCommand;
}
}
private void DefineCommands()
{
_SaveCommand = new DelegateCommand<string> (OnSaveCommand);
_HideMessageCommand = new DelegateCommand(OnHideMessageCommand);
}
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.
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.
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.
The test template has a useful screen display that allows for running all or a selected group of tests.
The results look like this:
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.