This is 3rd of my multi-part series on MVVM 101. Read part 1 here and part 2 here.
From an implementation perspective, ViewModel is a just class satisfying certain requirements and you follow a few simple steps to have a working one. Remember the key part in MVVM is the VM, the real controller, because View is just your WPF UI and Model is either an entity object or data transfer object. Let us say we want to display some very basic details about an insurer; the listing dialog will display details such as first name, last name, state and his dependent list. Let us also assume we already have domain objects for the insurer and dependent entities (I deliberately made Dependents as a complex type to show how collection properties can be dealt with in MVVM). Here is our simple model:
public class InsuranceInfo
{
public string FirstName {get; set;}
public string LastName {get; set;}
public List<FDependents> Dependents {get; set;}
public string State {get; set;}
}
public class FDependents
{
public string Relationship {get; set;}
public string DepName {get; set;}
public short Age {get; set;}
}
A simple UI we are going to build our VM against to show the above model:
Let’s go ahead and get the missing piece – ViewModel. As a convention, all the VM classes have the “ViewModel” suffix and I am calling ours as InsuranceViewModel.
Step 1: Add special properties to the VM for the UI to bind to
Any data displayed in the UI should have a corresponding property in the VM class. These properties are special in that any change to their values should be detectable by the binding engine and update the bound controls accordingly. Hence the VM should implement
INotifyPropertyChanged
interface and with collection properties wrapped by
ObservableCollection<>
.
public class InsuranceViewModel : INotifyPropertyChanged
{
private string _fn;
private string _ln;
private string _state;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<FDependents> Dependents {get; private set;}
public string FirstName {
get {return _fn;}
set {
if (_fn != value) {
_fn = value;
RaisePropertyChanged (“FirstName”);
}
}
}
public string LastName {
get {return _ln;}
set {
if (_ln != value) {
_ln = value;
RaisePropertyChanged (“LastName”);
}
}
}
public string State {
get {return _state;}
set {
if (_state != value) {
_state = value;
RaisePropertyChanged (“State”);
}
}
}
public EditCommand EditCmd {private set;get;}
private void RaisePropertyChanged (string p) {
if (PropertyChanged != null) {
PropertyChanged (this, new PropertyChangedEventArgs (p));
}
}
}
Note that I have implemented the properties as property methods instead of fields because we should raise property change notification whenever the property values are changed.
Step 2: Provide ICommand properties for applicable control events
Since our UI has save option, a command property,
EditCmd
of type
EditCommand
is also included in the above code. The command class looks like this:
public class EditCommand : ICommand {
private InsuranceViewModel _vm = null;
public event EventHandler CanExecuteChanged {
add {CommandManager.RequerySuggested += value;}
remove {CommandManager.RequerySuggested -= value;}
}
public bool CanExecute (object parameter) {
if (parameter != null) {
var cp = parameter as InsuranceViewModel;
return !(cp.FirstName == “” || cp.LastName == “” || cp.State == “”);
}
else
return true;
}
public void Execute (object parameter) {
if (parameter != null) {
var cp = parameter as InsuranceViewModel;
MessageBox.Show (String.Format(“{0}, {1}, {2}”,
cp.FirstName, cp.LastName, cp.State));
}
}
public EditCommand (InsuranceViewModel vm) {_vm = vm;}
}
It is very important that the command classes derive from
ICommand
. The
CanExecute
method tells if the action is available at any moment so that the control can disable itself or take appropriate action. This method will be called at appropriate times by the commanding infrastructure. The above code states that if at least one of first name, last name or state is empty, the Edit command should not be available.
Execute
method is called as a result of the control event (the event handler!). It is a common design that commands are initialized with a view model instance to delegate data service operations back to the view model itself.
Step 3: Hook up a ViewModel instance with UI markup
<Window x:Class=”MVVMDemo.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Title=”MVVM Demo” Height=”352″ Width=”525″ Loaded=”Window_Loaded”>
<Grid Height=”Auto”>
<ListView Margin=”12,100,0,58″ Name=”lvwInsuranceInfo”
SelectionMode=”Single” ItemsSource=”{Binding Path=Dependents}”
HorizontalAlignment=”Left” Width=”352″>
<ListView.View>
<GridView AllowsColumnReorder=”False”>
<GridViewColumn Header=”Relationship”
DisplayMemberBinding=”{Binding Path=Relationship}” />
<GridViewColumn Header=”Name”
DisplayMemberBinding=”{Binding Path=DepName}” />
<GridViewColumn Header=”Age”
DisplayMemberBinding=”{Binding Path=Age}” />
</GridView>
</ListView.View>
</ListView>
<Button Content=”_Close” Margin=”0,0,12,12″ Name=”btnClose”
Click=”btnClose_Click” HorizontalAlignment=”Right” Width=”105″
Height=”24″ VerticalAlignment=”Bottom” />
<Label Content=”First Name:” Height=”24″ HorizontalAlignment=”Left”
Margin=”10,10,0,0″ Name=”lblFN” Padding=”0″ VerticalAlignment=”Top”
VerticalContentAlignment=”Center” />
<Label Content=”Last Name:” Height=”24″ HorizontalAlignment=”Left”
Margin=”10,40,0,0″ Name=”label2″ Padding=”0″ VerticalAlignment=”Top”
VerticalContentAlignment=”Center” />
<Label Content=”State:” Height=”24″ HorizontalAlignment=”Left” Margin=”10,70,0,0″
Name=”label3″ Padding=”0″ VerticalAlignment=”Top”
VerticalContentAlignment=”Center” />
<Button Command=”{Binding Path=EditCmd}” CommandParameter=”{Binding}”
Content=”Save” Height=”24″ HorizontalAlignment=”Left” Margin=”123,0,0,12″
Name=”btnSave” VerticalAlignment=”Bottom” Width=”105″>
</Button>
<TextBox Height=”24″ Margin=”93,10,139,0″ Name=”txtFn” VerticalAlignment=”Top”
Text=”{Binding Path=FirstName}” />
<TextBox Height=”24″ Margin=”93,40,139,0″ Name=”txtLn” VerticalAlignment=”Top”
Text=”{Binding Path=LastName}” />
<TextBox Height=”24″ Margin=”93,71,139,0″ Name=”txtCity” VerticalAlignment=”Top”
Text=”{Binding Path=State}” />
<Label Content=”Relationship:” Height=”24″ HorizontalAlignment=”Left” Margin=”370,100,0,0″
Name=”label1″ Padding=”0″ VerticalAlignment=”Top” VerticalContentAlignment=”Center” />
<TextBox Height=”24″ Margin=”370,124,12,0″ Name=”txtRelationshipName”
Text=”{Binding ElementName=lvwInsuranceInfo, Path=SelectedItem.Relationship}”
VerticalAlignment=”Top” />
<Label Content=”Name:” Height=”24″ HorizontalAlignment=”Left”
Margin=”370,149,0,0″ Name=”label4″ Padding=”0″ VerticalAlignment=”Top”
VerticalContentAlignment=”Center” />
<TextBox Height=”24″ Margin=”370,171,12,0″ Name=”txtName”
Text=”{Binding ElementName=lvwInsuranceInfo, Path=SelectedItem.DepName}”
VerticalAlignment=”Top” />
<Label Content=”Age:” Height=”24″ HorizontalAlignment=”Left” Margin=”370,201,0,0″
Name=”label5″ Padding=”0″ VerticalAlignment=”Top” VerticalContentAlignment=”Center” />
<TextBox Height=”24″ Margin=”370,231,12,0″ Name=”txtAge”
Text=”{Binding ElementName=lvwInsuranceInfo, Path=SelectedItem.Age}”
VerticalAlignment=”Top” />
</Grid>
</Window>
As shown above, the UI controls are bound to the corresponding properties of the ViewModel instance. Note also how the Save button’s command property is wired to handle its click event. Finally, the code to set the main window’s data context looks like this: public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent ();
}
private void btnClose_Click (object sender, RoutedEventArgs e) {
this.Close ();
}
private void Window_Loaded (object sender, RoutedEventArgs e) {
this.DataContext = new InsuranceViewModel ();
}
}
In fact, this is also the entire code-behind for our demo application! All it does is just set a ViewModel instance to the window’s data context. In fact, this can also be moved to XAML markup to do it declaratively and get rid of
Window_Loaded
handler altogether.
Just for the demo purpose, the ViewModel class fills itself with some sample data when instantiated as shown below.
public InsuranceViewModel () {
FirstName = “Dave”;
LastName = “Watson”;
State = “NJ”;
Dependents = new ObservableCollection<FDependents> ();
Dependents.Add (new FDependents () {
Relationship = “Spouse”,
DepName = “Milla Watson”,
Age = 33 });
Dependents.Add (new FDependents () {
Relationship = “Son”,
DepName = “John Watson”,
Age = 10 });
Dependents.Add (new FDependents () {
Relationship = “Mother”,
DepName = “Marie Gold”,
Age = 65 });
EditCmd = new EditCommand (this);
}
If everything works fine whether you set the data context via code or markup, the output screen will be:
Making any of the top three text boxes empty, automatically disables the Save button:
The text boxes on the right are hooked to the selected item in the list view to let the user edit the selected dependent. WPF binding engine provides certain validation logic out of the box. In our demo, the Age property is numeric and in the screenshot below, you get a red outline around the text box when it is empty or have non-numeric values without requiring any extra code or configuration. By the way, this is nothing to do with MVVM, but thought would highlight some of the tiny benefits WPF gives free.
That’s it! Here is the recap of what we have discussed so far:
- MVVM is all about separation of business logic & UI separation in WPF and promoting loose coupling
- The code-behind contains no business logic (other than hooking the ViewModel if you choose to) with proper MVVM in place
- WPF’s Binding and Commands are the backbones for MVVM. Without them, it is very difficult to implement the pattern in a useful way.
-
You follow simple steps to create a working ViewModel class:
- Let the ViewModel class expose properties based on what the UI shows
- Implement
INotifyPropertyChanged
- Create custom commands for applicable user actions/control events by implementing
ICommand
- Bind its properties/commands to control properties in the XAML markup
- ViewModel classes are easily testable now
- Changing UI in future is as simple as designing a new one and consuming the existing VM. In fact, the UX team can try many prototypes and get feedback from the business with real data
- Since WPF and Silverlight front-ends can share almost the same ViewModel the effort required to support one front-end in addition to the above is lesser than having to building from the scratch. The bottom-line is reusability.
In some cases your code-behind may end up with extra code around MVVM hook up and other plumbing but that’s ok as long as you do not write any code that otherwise can be moved to ViewModel.
Alright, another claim is that MVVM enables independent (without UI intervention) testing of VM and downstream business logic easily. Restating one of the attributes of VM, it is purely UI independent and hence you can test the VM like any other class. For example, below is a simple NUnit test fixture for the
InsuranceViewModel
class. Like the UI, the test fixture in all respect is yet another consumer of VM.
[TestFixture]
public class InsuranceInfoVMUnitTest
{
[Test]
public void TestVMInit ()
{
var vm = new InsuranceViewModel ();
vm.PropertyChanged +=
(s, e) => {Debug.WriteLine (String.Format(@”Property {0} value changed.”, e.PropertyName));};
Assert.IsNotNull (vm.Dependents, @”Dependents collection not initialized.”);
Assert.IsNotNull (vm.FirstName, @”First name failed to initialize.”);
Assert.IsNotNull (vm.LastName, @”Last name failed to initialize.”);
vm.State = “PA”; // Should write out a debug message; check Debug window
vm.FirstName = “”;
Assert.IsFalse (vm.EditCmd.CanExecute(vm), @”EditCmd failed to check empty FirstName.”);
vm.FirstName = “Bill”;
vm.LastName = “”;
Assert.IsFalse (vm.EditCmd.CanExecute(vm), @”EditCmd failed to check empty LastName.”);
vm.LastName = “Rogers”;
vm.State = “”;
Assert.IsFalse (vm.EditCmd.CanExecute(vm), @”EditCmd failed to check empty State.”);
vm.EditCmd.Execute(vm);
vm.State = “CA”;
Assert.IsTrue (vm.EditCmd.CanExecute(vm), @”EditCmd failed to check valid InsuranceInfo properties.”);
vm.EditCmd.Execute(vm); // Save button click
// Load current insurance info afresh & check for updated values
}
}
Now that we have discussed MVVM at a reasonable level, lets us see the flip aspects of MVVM in WPF:
- MVVM requires extra code to be written; you have to implement a few interfaces and extend few base classes and all these would most probably take more time but the end advantages are many. It is better to invest upfront to save in future.
- For more sophisticated applications, you may have to write value converters to handle custom types in XAML markup.
- Some argue that ViewModel is the new code-behind and not a value adder when it comes to separation of concerns. This is motivated by the View (UI) delegating event handling to ViewModel class through command binding (
ICommand.Execute
method). This in a sense gives a feeler of having event handler methods in the ViewModel. Nevertheless, I am not against or favor of this point as it depends on the individual’s viewpoint.
- Out of the box, commands are available only to
ButtonBase
-derived controls,
MenuItem
, and
HyperLink
. For other controls, esp. selection controls such as combo box, list view, you have to resort to other ways (may be, I will cover this in a future post).