Introduction
This article summarizes two utility classes used for data and command bindings in Silverlight MVVM applications and demonstrates how to use the two classes with a running example.
Background
MVVM design pattern is now the default design pattern for building Silverlight applications. Data binding and Command binding are the fundamental building blocks when developing MVVM applications in Silverlight. This article will summarize two very commonly used utility classes to help implement bindings in Silverlight MVVM applications and presents a running example to use these two classes. The two utility classes that will be introduced are the following:
- The "
ViewModelBase
" class is to help in implementing data binding.
- The "
RelayCommand
" class is to help in implementing command binding.
If you are interested in more general discussions related to MVVM in Silverlight, you can easily find references, such as this one, this one and this one.
The attached Visual Studio solution has two projects:
- The "
MVVMUtility
" project is the Silverlight application.
- The "
MVVMUtilityWeb
" project is an ASP.NET web application that hosts the Silverlight application. It also exposes a WCF service to be consumed by the Silverlight application to build the application's data model.
The attached Visual Studio solution is built in Visual Studio 2010 and Silverlight 4. I will first present the two utility classes and then introduce the MVVM application to demonstrate how to use them. I will recommend you to download the attached Visual Studio solution and run the Silverlight application before reading this article. If you know what the demo application does, it should make your reading of this article easier.
The Data and Command Binding Utility Classes
The two utility classes are implemented in the "BindingUtilities" folder in the "MVVMUtility
" project. The following is the "ViewModelBase
" class:
using System;
using System.Windows;
using System.ComponentModel;
namespace MVVMUtility.BindingUtilities
{
public abstract class ViewModelBase
: DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsDesignTime
{
get { return DesignerProperties.IsInDesignTool; }
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
This class is a utility class to help in implementing data binding. It is a simple wrapper class of the INotifyPropertyChanged interface. The INotifyPropertyChanged
interface is the key for data binding. If the data changes in the view models need to be reflected in the XAML components through bindings, all the view models in a Silverlight MVVM application need to implement this interface. With this utility class, the view models can simply inherit from it and call the "NotifyPropertyChanged
" method without working on the details of the INotifyPropertyChanged
interface.
Another important part in building Silverlight MVVM applications is command binding. In supporting the implementations of command binding, the "RelayCommand
" class is created:
using System;
using System.Windows.Input;
namespace MVVMUtility.BindingUtilities
{
public class RelayCommand : ICommand
{
private readonly Action handler;
private bool isEnabled;
public RelayCommand(Action handler)
{
this.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();
}
}
}
The ICommand interface allows the Silverlight applications to process user commands, such as Button clicks, in the view models. The "RelayCommand
" class wraps the basic implementations of the ICommand
interface, so the view models do not need to work on the ICommand
interface directly. You will soon see how the "ViewModelBase
" and "RelayCommand
" classes are used in building view models in the example Silverlight application. But before going to the view model, we shall take a look at the application's data model first.
The Data Model
This MVVM application's data model is implemented in two places, the WCF service in the host web application and the StudentModel
class in the Silverlight application. The WCF service is implemented in the "StudentService.svc.cs" file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace MVVMUtilityWeb
{
[DataContract]
public class Student
{
[DataMember(Order = 0)]
public string ID { get; set; }
[DataMember(Order = 1)]
public string Name { get; set; }
[DataMember(Order = 2)]
public DateTime EnrollmentDate { get; set; }
[DataMember(Order = 3)]
public int Score { get; set; }
[DataMember(Order = 4)]
public string Gender { get; set; }
}
[ServiceContract]
public class StudentService
{
[OperationContract]
public List<Student> GenerateStudents(int NoOfStudents)
{
List<Student> students = new List<Student>();
Random rd = new Random();
for (int i = 1; i <= NoOfStudents; i++)
{
int score = Convert.ToInt16(60 + rd.NextDouble() * 40);
students.Add(new Student()
{
ID = "ID No." + i.ToString(),
Name = "Student Name " + i.ToString(),
EnrollmentDate = DateTime.Now,
Score = score,
Gender = (score >= 80) ? "Famle" : "Male"
});
}
return students;
}
}
}
This is a simple WCF service. It has a Data Contract "Student
" and a Service Contract "StudentService
". The Operation Contract "GenerateStudents
" takes an integer representing the number of students that the WCF clients want to retrieve and creates a list of randomly generated "Student
" objects to send back to the clients. After adding this WCF service as a Service Reference in the Silverlight application, the "StudentModel
" class is implemented in the "Models" folder:
using System;
using System.Collections.Generic;
using MVVMUtility.StudentService;
namespace MVVMUtility.Models
{
public class StudentModel
{
public void GetStudents(int NumberOfStudent,
EventHandler<GenerateStudentCompletedEventArgs> targetFunction)
{
StudentServiceClient client = new StudentServiceClient();
client.GenerateStudentCompleted
+= new EventHandler<GenerateStudentCompletedEventArgs>(targetFunction);
client.GenerateStudentAsync(NumberOfStudent);
}
}
}
Since all the student generating functionalities have been implemented in the WCF service, the "StudentModel
" has only one method which calls the WCF service to retrieve the list of "Student
" objects. One of the input parameters of the "GetStudents
" method is the reference to a Callback function. This callback function will be called asynchronously when the result from the WCF service comes back. This callback function will be implemented in the application's view model.
The View Model
The view model of the Silverlight application is implemented in the "MainPageViewModel
" class in the "ViewModels" folder:
using System;
using MVVMUtility.BindingUtilities;
using System.Collections.Generic;
using MVVMUtility.Models;
using System.Collections.ObjectModel;
using System.Windows;
using MVVMUtility.StudentService;
namespace MVVMUtility.ViewModels
{
public class NoOfStudentsDropdownItem
{
public int NoOfStudents { get; set; }
public string DisplayText { get; set; }
}
public class MainPageViewModel : ViewModelBase
{
public List<NoOfStudentsDropdownItem> ListNoOfStudents { get; private set; }
private NoOfStudentsDropdownItem selectedNoOfStudents;
public NoOfStudentsDropdownItem SelectedNoOfStudents
{
get { return selectedNoOfStudents; }
set
{
if (selectedNoOfStudents != value)
{
selectedNoOfStudents = value;
NotifyPropertyChanged("SelectedNoOfStudents");
ListOfStudents = null;
}
}
}
private List<Student> listOfStudents;
public List<Student> ListOfStudents
{
get { return listOfStudents; }
private set
{
if (listOfStudents != value)
{
listOfStudents = value;
NotifyPropertyChanged("ListOfStudents");
}
}
}
private string message;
public string Message
{
get { return message; }
private set
{
if (message != value)
{
message = value;
NotifyPropertyChanged("Message");
}
}
}
private Visibility messageVisibility;
public Visibility MessageVisibility
{
get { return messageVisibility; }
set
{
if (messageVisibility != value)
{
messageVisibility = value;
NotifyPropertyChanged("MessageVisibility");
}
}
}
private void WireCommands()
{
GetStudentsCommand = new RelayCommand(GetStudents);
GetStudentsCommand.IsEnabled = true;
HideMessageCommand = new RelayCommand(HideMessage);
HideMessageCommand.IsEnabled = true;
}
private void ShowMessage(string message)
{
Message = message;
MessageVisibility = Visibility.Visible;
}
public RelayCommand GetStudentsCommand { get; private set; }
private void GetStudents()
{
if (SelectedNoOfStudents.NoOfStudents == -1)
{
ShowMessage("Please select the number of students to retrieve.");
MessageVisibility = Visibility.Visible;
return;
}
StudentModel model = new StudentModel();
model.GetStudents(SelectedNoOfStudents.NoOfStudents, GetStudentsCompleted);
}
private void GetStudentsCompleted(object sender,
GenerateStudentsCompletedEventArgs e)
{
try { ListOfStudents = (List<Student>)e.Result; }
catch
{
string message = "Unable to obtain the students from the WCF service"
+ " - you may have lost internet access or the server is down.";
ShowMessage(message);
ListOfStudents = null;
}
}
public RelayCommand HideMessageCommand { get; private set; }
private void HideMessage()
{
MessageVisibility = Visibility.Collapsed;
}
public MainPageViewModel()
{
InitiateViewModel();
}
private void InitiateViewModel()
{
ListNoOfStudents = new List<NoOfStudentsDropdownItem>();
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = -1, DisplayText = "* Please Select *" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 5, DisplayText = "5" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 10, DisplayText = "10" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 20, DisplayText = "20" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 50, DisplayText = "50" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 100, DisplayText = "100" });
SelectedNoOfStudents = ListNoOfStudents[0];
MessageVisibility = Visibility.Collapsed;
WireCommands();
}
}
}
By inheriting from the utility class "ViewModelBase
" and using the utility class "RelayCommand
", it is fairly easy to implement the properties and commands in the view model. This view model class implements 5 public
properties and 2 public
"RelayCommand
" objects. The public
properties are the following:
- The "
ListNoOfStudents
" property will be used by the XAML view to display a dropdown box for the users to select the "number of students" to retrieve from the WCF service.
- The "
SelectedNoOfStudents
" property is used by the XAML view to inform the view model the "number of students" selected by the users.
- The "
ListOfStudents
" property is the list of "Student
" objects retrieved from the WCF service.
- The "
Message
" is a String type property. Through binding, the XAML view can display the text set to this property.
- The "
MessageVisibility
" property controls the Visibility of the XAML components that display the "Message
" information.
The public
"RelayCommand
" objects are the following:
- The "
GetStudentsCommand
" will be used by the view to "command" the view model to issue the WCF call to retrieve the students.
- The "
HideMessageCommand
" will be used by the view to hide the "Message
" text displayed to the users.
You should pay some attention to how the "ViewModelBase
" and "RelayCommand
" classes are used and how they simplify the development of the view model. Now we can take a look at the view of the application and how the view model is bound to the XAML view.
The View
The view of the Silverlight application is the "MainPage.xaml" file:
<UserControl
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
x:Class="MVVMUtility.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" Style="{StaticResource WindowStyle}"
d:DesignHeight="500" d:DesignWidth="800">
<UserControl.DataContext>
<Binding Source="{StaticResource MainPageViewModel}" />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock Text="Please select the number of the students"
VerticalAlignment="Center" />
<ComboBox Width="150" Margin="10, 0, 5, 0"
ItemsSource="{Binding Path=ListNoOfStudents, Mode=OneTime}"
DisplayMemberPath="DisplayText"
SelectedItem="{Binding Path=SelectedNoOfStudents,
Mode=TwoWay}" />
<Button Content="Get students" Width="130"
Command="{Binding Path=GetStudentsCommand}" />
</StackPanel>
<sdk:DataGrid Grid.Row="1" Margin="0, 5, 0, 5"
IsReadOnly="True" ColumnWidth="120"
ItemsSource="{Binding Path=ListOfStudents, Mode=OneWay}">
</sdk:DataGrid>
</Grid>
<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>
This XAML view has the following important components:
- A Combobox bound to the "
ListNoOfStudents
" and "SelectedNoOfStudents
" properties. It takes the list of dropdown items from the "ListNoOfStudents
" property and sends the user selection to the view model through "SelectedNoOfStudents
" property.
- A Button bound to the "
GetStudentsCommand
" command. It will be used by the users to issue the command to make the WCF call.
- A Datagrid bound to the "
ListOfStudents
" property. It is used to display the students obtained from the WCF service.
This XML file also includes a Grid to display messages to the users. The visibility of this grid is bound to the "MessageVisibility
" property. The Textblock in this Grid displays the text information in the "Message
" property and the button in this grid issues the "HideMessageCommand
" to hide this grid. This Silverlight application completely conforms to the MVVM pattern. You may take a look at the code behind file of "MainPage.xaml", which is minimized to have only the constructor:
using System.Windows.Controls;
using System.Windows;
namespace MVVMUtility
{
public partial class MainPage : UserControl
{
public MainPage() { InitializeComponent(); }
}
}
The view model is defined in the "App.xaml" file as a static resource. It is bound to the XAML view at the beginning of the "MainPage.xaml" file. The following is the "App.xaml" file:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VModel="clr-namespace:MVVMUtility.ViewModels"
x:Class="MVVMUtility.App">
<Application.Resources>
<Style TargetType="UserControl" x:Key="WindowStyle">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="12" />
</Style>
<VModel:MainPageViewModel x:Key="MainPageViewModel" />
</Application.Resources>
</Application>
The Host Web Application "MVVMUtilityWeb"
We now finish the development of the Silverlight application. Before the test run of the application, I want to briefly introduce the host web page "Default.aspx" in the "MVVMUtilityWeb
" project:
<%@ Page Language="C#" AutoEventWireup="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>MVVM Utility</title>
<link type="text/css" rel="Stylesheet" href="Styles/SilverlightStyle.css" />
<script type="text/javascript" src="Scripts/Silverlight.js"></script>
</head>
<body>
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," id="SilverlightObject"
type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/MVVMUtility.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50826.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0"
style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376"
alt="Get Microsoft Silverlight" style="border-style:none"/></a>
</object><iframe id="_sl_historyFrame"
style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
</body>
</html>
When you create a Silverlight application, Visual Studio automatically creates a host web page for it. Instead of using the web page created by Visual Studio, the "default.aspx" in this article consolidates the Silverlight related CSS styles and JavaScripts into "Styles\SilverlightStyle.css" and "Scripts\Silverlight.js" files, where the "Styles\SilverlightStyle.css" file is the following:
html, body {height: 100%; overflow: auto;}
body {padding: 0; margin: 0;}
#silverlightControlHost {height: 100%; text-align:center;}
#SilverlightObject, #silverlightControlHost {min-width:600px; min-height:400px;}
Run the Application
We now complete the development of the example Silverlight application, we can then test run it. Set the "MVVMUtilityWeb
" project as the start up project and the "Default.aspx" as the start page, we can launch the application. If we leave the dropdown box as "* please select *
" and click the "Get students" button, we can see a modal message windows shows up asking us to make a selection.
Click the "OK" button to close the modal window, select the "number of students" and click the "Get students" button, the WCF service is called and a list of students are retrieved. The following shows the result when we retrieve 10 students from the WCF service.
Points of Interest
- This article introduced two very commonly used utility classes to implement data and command bindings in Silverlight MVVM applications. The attached Visual Studio project is a fully conformed Silverlight MVVM application which demonstrates how to use these two classes.
- When developing view models for Silverlight MVVM applications, you do not have to use the "
ViewModelBase
" and "RelayCommand
" classes. But they do make your code cleaner by saving many lines of code compared with working on the INotifyPropertyChanged and ICommand interfaces directly.
- MVVM is a good pattern when developing Silverlight applications. It makes unit testing much easier. But you may not always get all the support you need to implement MVVM. This is particularly true when your application becomes complex and you are using third party controls. In such cases, implementing MVVM may become difficult and you should be able to make these cases as "exceptions". Silverlight is still evolving. According to Scott Guthrie, we should expect to see better MVVM support from Silverlight 5.
- You can choose two-way, one-way, and one-time binding modes in your Silverlight MVVM applications. The more powerful the binding mode is, the more resource it takes. You should always choose the least powerful binding mode that meets your requirements.
- I hope you like my postings and I hope this article can help you one way or the other.
History
This is the first revision of this article.