03 WPF MVVM Basics
03 WPF MVVM Basics
In this blog post, you learn how easy it is to use the Model-View-View-Model
(MVVM) design pattern in WPF applications. This blog post is a step-by-step
illustration of how to build a WPF application to display a list of users. You are going
to perform the following steps.
using System.ComponentModel;
using System.Reflection;
namespace Common.Library
{
/// <summary>
/// This class implements the INotifyPropertyChanged event
/// </summary>
public class CommonBase : INotifyPropertyChanged
{
/// <summary>
/// The PropertyChanged Event to raise to any UI object
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The PropertyChanged Event to raise to any UI object
/// The event is only invoked if data binding is used
/// </summary>
/// <param name="propertyName">The property name
that is changing</param>
protected void RaisePropertyChanged(string propertyName)
{
// Grab a handler
PropertyChangedEventHandler handler = this.PropertyChanged;
namespace Common.Library
{
/// <summary>
/// Base class for all view models
/// </summary>
public class ViewModelBase : CommonBase
{
}
}
Create Folders
Add a new folder called EntityClasses.
Add a new folder called Models.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Common.Library;
namespace WPF.Sample
{
[Table("User", Schema = "dbo")]
public class User : CommonBase
{
private int _UserId;
private string _UserName = string.Empty;
private string _Password = string.Empty;
private string _FirstName = string.Empty;
private string _LastName = string.Empty;
private string _EmailAddress = string.Empty;
private bool _IsLoggedIn = false;
[Required]
[Key]
public int UserId
{
get { return _UserId; }
set {
_UserId = value;
RaisePropertyChanged("UserId");
}
}
[Required]
public string UserName
{
get { return _UserName; }
set {
_UserName = value;
RaisePropertyChanged("UserName");
}
}
[Required]
public string Password
{
get { return _Password; }
set {
_Password = value;
RaisePropertyChanged("Password");
}
}
[Required]
public string FirstName
{
get { return _FirstName; }
set {
_FirstName = value;
RaisePropertyChanged("FirstName");
}
[Required]
public string LastName
{
get { return _LastName; }
set {
_LastName = value;
RaisePropertyChanged("LastName");
}
}
[Required]
public string EmailAddress
{
get { return _EmailAddress; }
set {
_EmailAddress = value;
RaisePropertyChanged("EmailAddress");
}
}
[NotMapped]
public bool IsLoggedIn
{
get { return _IsLoggedIn; }
set {
_IsLoggedIn = value;
RaisePropertyChanged("IsLoggedIn");
}
}
}
}
using System.Data.Entity;
namespace WPF.Sample
{
public partial class SampleDbContext : DbContext
{
public SampleDbContext() : base("name=MVVMSample")
{
}
<connectionStrings>
<add name="MVVMSample"
connectionString="Server=(localdb)\MSSQLLocalDB;
AttachDbFilename=|DataDirectory|MVVMSample.mdf;
Database=MVVMSample;Trusted_Connection=Yes;"
providerName="System.Data.SqlClient" />
</connectionStrings>
Set DataDirectory
If you are using a local SQL Server express database you created within the
App_Data folder of your project, you need to set the "DataDirectory" domain
property to the path of where the database is located. You do this one time when
your WPF application starts up. It is this domain property that is used to replace the
|DataDirectory| placeholder you see in the connection string in the App.config file.
Open the App.xaml.cs file and override the OnStartup event within the App class.
AppDomain.CurrentDomain.SetData("DataDirectory", path);
}
<UserControl x:Class="WPF.Sample.UserControls.UserListControl"
... XML namespaces here
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Loaded="UserControl_Loaded">
<ListView Name="lstData"
ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="User ID"
Width="Auto"
DisplayMemberBinding="{Binding Path=UserId}" />
<GridViewColumn Header="User Name"
Width="Auto"
DisplayMemberBinding="{Binding Path=UserName}" />
<GridViewColumn Header="First Name"
Width="Auto"
DisplayMemberBinding="{Binding Path=FirstName}" />
<GridViewColumn Header="Last Name"
Width="Auto"
DisplayMemberBinding="{Binding Path=LastName}" />
<GridViewColumn Header="Email"
Width="Auto"
DisplayMemberBinding="{Binding Path=EmailAddress}" />
</GridView>
</ListView.View>
</ListView>
</UserControl>
using System.Linq;
try {
db = new SampleDbContext();
lstData.DataContext = db.Users.ToList();
}
catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
The code in the LoadUsers() method uses the Entity Framework to retrieve user
data from the User table. Apply the ToList() method in order to retrieve a local copy
of the data that is bindable to any WPF control. Assign that list of users to the
DataContext property of the ListView control you named lstData.
On the ListView control, you set the ItemsSource property to {Binding}. This tells the
list box that you will be binding the data at runtime by setting the DataContext
property. Once you set the DataContext property, WPF binds each row of data to
the template you created in the ListView.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0"
IsMainMenu="True">
<MenuItem Header="_File">
<MenuItem Header="E_xit"
Click="MenuExit_Click" />
</MenuItem>
<MenuItem Header="Users"
Click="MenuUsers_Click" />
</Menu>
<!-- Content Area -->
<Grid Grid.Row="1"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Name="contentArea" />
</Grid>
using WPF.Sample.UserControls;
Try it Out
Run the application and view the users.
The Problem
That is all there is to writing the code behind and having it load data from a User
table. The problem with the above code is that to test this code, someone must run
the program and verify that this code works as it is supposed to. If you make a
change to the database or to the UI, you will then need to have someone run the
application again to ensure it still works. You must repeat this test each time a
change is made. This becomes very tedious and time consuming for the developer
and the tester.
using System;
using System.Collections.ObjectModel;
using Common.Library;
namespace WPF.Sample.ViewModels
{
public class UserListViewModel : ViewModelBase
{
public UserListViewModel() : base()
{
LoadUsers();
}
try {
db = new SampleDbContext();
The code in the UserListViewModel class is not that much more code than you
wrote in the code behind.
Open the UserListControl.xaml file and add an XML namespace (See Figure 1) as
shown in the code in bold to reference the namespace that the UserListViewModel
is created within.
<UserControl x:Class="WPF.Sample.UserControls.UserListControl"
xmlns="http://schemas.microsoft.com/..."
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/..."
xmlns:d="http://schemas.microsoft.com/..."
xmlns:vm="clr-namespace:WPF.Sample.ViewModels"
...
The XML namespace defined here is the equivalent of aliasing a .NET namespace
in C#.
using vm = WPF.Sample.ViewModels;
Once you have a .NET namespace aliased, you can now create any instance of a
class in that name using code like the following:
This type of aliasing is not done too often in .NET because you generally just
provide a using statement and you can create an instance of your class. However, if
you have two classes with the same name in different namespaces, you would have
to fully qualify one or the other to use them in the same block of code. Instead of
typing out the complete namespace for one of them, using an alias can save some
keystrokes.
Figure 1: Create an XML namespace to reference view models within a namespace in your
project.
Once you have defined this XML namespace, create an instance of the
UserListViewModel class. Add a <UserControl.Resources> element just before the
<Grid> element. Add an element that begins with the aliased namespace "vm",
followed by a colon, then the class name you wish to create an instance of. As soon
as you type the colon, Visual Studio will provide you with an IntelliSense list of
classes within that namespace (see Figure 2).
<UserControl.Resources>
<vm:UserListViewModel x:Key="viewModel" />
</UserControl.Resources>
To bind to this XAML instantiated class, you must provide a key name so you can
reference it. In this case, you gave the key name of "viewModel".
Figure 2: Create an instance of a view model in XAML using the alias of the namespace and
assigning a key name.
Loaded="UserControl_Loaded"
All controls contained within the <Grid> may now bind to any property within the
view model class. Modify the ListView control to bind to the Users property (see
Figure 3) you created in the UserListViewModel class.
In Figure 3, you see that the DataContext of the <Grid> is bound to the static
resource you created named "viewModel". This static resource is an instance of the
UserListViewModel class created by XAML when the user control is instantiated.
Finally, the ListView control's ItemsSource property is bound to the Users property
within the UserListViewModel class. Since the ListView control is a child of the
<Grid> element, it has complete access to any of the properties of the class it is
bound to.
Figure 3: Bind to a property in the view model to provide data to your UI controls.
Summary
Hopefully, this post helped you see how easy it is to move code from the code
behind your user interface and put it into a class. That is the whole key to MVVM;
simply moving code into a class. Do not worry about being 100% “code behind
free.” That is an almost impossible goal and most often requires you to write more
code. If your event procedures in your UI are simply doing UI code or making a
single call to a method in your View Model, then you have accomplished the goals
of MVVM; namely reusability, maintainability, and testability. You also get one more
benefit: having event procedures in your UI makes it easier to follow the flow of the
application.
NOTE: You can download the sample code for this article by visiting my website at
http://www.pdsa.com/downloads. Select “Fairway/PDSA Blog,” then select “Basics
of MVVM in WPF” from the dropdown list.