Model-View-ViewModel is a variation of Model View Controller that is tailored for modern UI development platforms like WPF/ Silverlight where the View is the responsibility of a designer rather than a classic developer. John Gossman, currently an Architect at Microsoft, introduced MVVM as a standardized way to leverage core features of WPF to simplify the creation of user interfaces. The most important aspect of WPF that makes MVVM a great pattern is the data binding mechanism.
Use of MVM allows the View to bind to properties in the ViewModel, supports input validation by transmitting validation errors to the view and expose commands in the ViewModel to the View.
The View consists of the visual elements like buttons, textboxes, labels etc
The Model contains the data and does the processing of the problem domain.
The ViewModel can be thought as an abstraction of the view, but it also provides a specialization of the Model that the View can use for data-binding. It contains translator code that converts Model types into View types, and it contains Commands the View can use to interact with the Model.
In this post I'll show how to create a simple M-V-VM application sample in WPF.
My model is created as
public class Employee : ModelBase
{
public int Id { get; set; }
private string m_FirstName;
public string FirstName
{
get { return m_FirstName; }
set
{
if (m_FirstName != value)
{
m_FirstName = value;
OnPropertyChanged("FirstName");
}
}
}
private string m_LastName;
public string LastName
{
get { return m_LastName; }
set
{
if (m_LastName != value)
{
m_LastName = value;
OnPropertyChanged("LastName");
}
}
}
private bool m_IsContractor;
public bool IsContractor
{
get { return m_IsContractor; }
set
{
if (m_IsContractor != value)
{
m_IsContractor = value;
OnPropertyChanged("IsContractor");
}
}
}
private DateTime m_DateOfJoining;
public DateTime DateOfJoining
{
get { return m_DateOfJoining; }
set
{
if (m_DateOfJoining != value)
{
m_DateOfJoining = value;
OnPropertyChanged("DateOfJoining");
}
}
}
}
The ModelBase class contains code that implement the INotifyPropertyChanged interface method.
public abstract class ModelBase : INotifyPropertyChanged
{
public bool ThrowOnInvalidPropertyName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
var __Handler = PropertyChanged;
if (__Handler != null) __Handler(this, new PropertyChangedEventArgs(propertyName));
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new ArgumentException(msg);
else
Debug.Fail(msg);
}
}
}
I have the view which contains the a DataGrid to show the employee details and a menu that contains items to refresh and clear the employee data in the Grid.
<Window x:Class="MVVMDemo.Views.EmployeeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CommandRef="clr-namespace:MVVMDemo.Commands"
xmlns:WPF="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:ViewModel="clr-namespace:MVVMDemo.ViewModels"
xmlns:Converters="clr-namespace:MVVMDemo.Converters"
Title="Main Window" Height="400" Width="800">
<Window.Resources>
<ObjectDataProvider x:Key="_employeeProvider" ObjectType="{x:Type ViewModel:EmployeeViewModel}" />
<Converters:DateTimeConverter x:Key="_dateTimeConverter" />
Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="{Binding ClearCommand}" Header="_Clear" InputGestureText="Ctrl-L" />
<MenuItem Command="{Binding ReloadCommand}" CommandParameter="true" Header="_Reload" InputGestureText="Ctrl-R" />
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" InputGestureText="Ctrl-X" />
MenuItem>
Menu>
<Grid DataContext="{Binding EmployeeCollection}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
Grid.RowDefinitions>
<TextBlock Text="Model View ViewModel implementation" FontFamily="Calibri" FontSize="20" FontWeight="Bold" />
<WPF:DataGrid Name="_employeeDataGrid"
DockPanel.Dock="Top"
Grid.Row="2" Margin="12 0 12 0"
VerticalAlignment="Top"
ItemsSource="{Binding}"
AutoGenerateColumns="False" >
<WPF:DataGrid.Columns>
<WPF:DataGridTextColumn Binding="{Binding Path=FirstName}" Header="FirstName" Width="100" />
<WPF:DataGridTextColumn Binding="{Binding Path=LastName}" Header="LastName" Width="100" />
<WPF:DataGridCheckBoxColumn IsThreeState="False" Binding="{Binding Path=IsContracter, Mode=TwoWay}" SortMemberPath="IsContracter" Header="Is Contractor" />
<WPF:DataGridTemplateColumn Header="Date Of Joining" SortMemberPath="DateOfJoining">
<WPF:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<WPF:DatePicker SelectedDate="{Binding Path=DateOfJoining, Mode=TwoWay}" BorderThickness="0" />
DataTemplate>
WPF:DataGridTemplateColumn.CellEditingTemplate>
<WPF:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DateOfJoining, Converter={StaticResource _dateTimeConverter}}" />
DataTemplate>
WPF:DataGridTemplateColumn.CellTemplate>
WPF:DataGridTemplateColumn>
WPF:DataGrid.Columns>
WPF:DataGrid>
Grid>
DockPanel>
Window>
I have my ViewModel as
public class EmployeeViewModel
{
private DelegateCommand m_ExitCommand;
private DelegateCommand m_ClearCommand;
private DelegateCommand<string> m_ReloadCommand;
private static ObservableCollection<Employee> m_EmployeeCollection = new ObservableCollection<Employee>();
public static ObservableCollection<Employee> EmployeeCollection
{
get { return m_EmployeeCollection; }
}
public ICommand ExitCommand
{
get
{
if (m_ExitCommand == null) m_ExitCommand = new DelegateCommand(() => Application.Current.Shutdown());
return m_ExitCommand;
}
}
public ICommand ClearCommand
{
get
{
if (m_ClearCommand == null) m_ClearCommand = new DelegateCommand
(
() => m_EmployeeCollection.Clear(),
() => m_EmployeeCollection.Count > 0
);
return m_ClearCommand;
}
}
public ICommand ReloadCommand
{
get
{
if (m_ReloadCommand == null) m_ReloadCommand = new DelegateCommand<string>
(
x => LoadEmployees(string.Compare(x, "true", true) == 0),
y => m_EmployeeCollection.Count == 0
);
return m_ReloadCommand;
}
}
public Action<bool> LoadEmployees = x =>
{
var __EmployeeCollection = EmployeeFactory.GetEmployees(x);
__EmployeeCollection.ForEach(y => m_EmployeeCollection.Add(y));
};
}
As you can see from the code, I have used the DelegateCommand object to expose commands to the View. The DelegateCommand implements the ICommand interface that has the Execute and CanExecute method signatures.
public class DelegateCommand
{
private readonly Action
private readonly Func
private bool m_IsAutomaticRequeryDisabled = false;
private List<WeakReference> m_CanExecuteChangedHandlers;
public DelegateCommand(Action
: this(executeMethod, null, false) {}
public DelegateCommand(Action
: this(executeMethod, canExecuteMethod, false) { }
public DelegateCommand(Action
{
if (executeMethod == null) throw new ArgumentNullException("executeMethod");
m_ExecuteMethod = executeMethod;
m_CanExecuteMethod = canExecuteMethod;
m_IsAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
public bool CanExecute(T parameter)
{
if (m_CanExecuteMethod != null) return m_CanExecuteMethod(parameter);
return true;
}
public void Execute(T parameter)
{
if (m_ExecuteMethod != null) m_ExecuteMethod(parameter);
}
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(m_CanExecuteChangedHandlers);
}
public bool IsAutomaticRequeryDisabled
{
get{ return m_IsAutomaticRequeryDisabled; }
set
{
if (m_IsAutomaticRequeryDisabled != value)
{
if (value) CommandManagerHelper.RemoveHandlersFromRequerySuggested(m_CanExecuteChangedHandlers);
else CommandManagerHelper.AddHandlersToRequerySuggested(m_CanExecuteChangedHandlers);
m_IsAutomaticRequeryDisabled = value;
}
}
}
public event EventHandler CanExecuteChanged
{
add
{
if (!m_IsAutomaticRequeryDisabled) CommandManager.RequerySuggested += value;
CommandManagerHelper.AddWeakReferenceHandler(ref m_CanExecuteChangedHandlers, value, 2);
}
remove
{
if (!m_IsAutomaticRequeryDisabled) CommandManager.RequerySuggested -= value;
CommandManagerHelper.RemoveWeakReferenceHandler(m_CanExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
// if T is of value type and the parameter is not
// set yet, then return false if CanExecute delegate
// exists, else return true
if (parameter == null && typeof(T).IsValueType) return (m_CanExecuteMethod == null);
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
}
The MVVM pattern allows you to create a strong separation between data, behavior, and presentation, making it easier to control the chaos that is software development