Thursday, October 8, 2009

WPF MVVM pattern

MVVM1 I’m using the WPF Model-View-ViewModel(MVVM) pattern in one of my projects. I will tell a few words about the implementation.

The first step is to create an entity class, the Model. This class stores only data. To support binding and data validation in WPF, it also implements the IDataErrorInfo interface. Entity classes can be generated by object-relational mapping (ORM) tools like NHibernate or Microsoft Entity Framework. Or you can implement them yourself using e.g. Active Record, Data Mapper, Row Data Gateway, or Table Data Gateway patterns. The entity class don’t have to implement INotifyPropertyChanged interface, this functionality will be provided by the ViewModel.

Next you create the ViewModel. This class will be the link between the Model and the View – between the data and the user interface elements. It will store and update the Model data, and it will transform the data to be displayed on the user interface. ViewModel has the same properties as the Model (or a subset of it). ViewModel can provide calculated fields to be displayed as well. All properties signal their change, so ViewModel implements INotifyProperyChanged interface and fires the appropriate event in case of property value change.
ViewModel can react on commands. It can save its content into the database (using ORM functionality or something like that. ) While you should implement every single commands if you implemented ICommand on its own, you can introduce the RelayCommand class.

   1: public class RelayCommand : ICommand
   2:     {
   3:         readonly Action<object> _execute;
   4:         readonly Predicate<object> _canExecute;
   5:  
   6:         /// <summary>
   7:         /// Creates a new command that can always execute.
   8:         /// </summary>
   9:         /// <param name="execute">The execution logic.</param>
  10:         public RelayCommand(Action<object> execute)
  11:             : this(execute, null)
  12:         {
  13:         }
  14:  
  15:         /// <summary>
  16:         /// Creates a new command.
  17:         /// </summary>
  18:         /// <param name="execute">The execution logic.</param>
  19:         /// <param name="canExecute">The execution status logic.</param>
  20:         public RelayCommand(Action<object> execute, Predicate<object> canExecute)
  21:         {
  22:             if (execute == null)
  23:                 throw new ArgumentNullException("execute");
  24:  
  25:             _execute = execute;
  26:             _canExecute = canExecute;
  27:         }
  28:  
  29:  
  30:         #region ICommand Members
  31:  
  32:         /// <summary>
  33:         /// Defines the method that determines whether the command can execute in its current state.
  34:         /// </summary>
  35:         /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
  36:         /// <returns>
  37:         /// true if this command can be executed; otherwise, false.
  38:         /// </returns>
  39:         [DebuggerStepThrough]
  40:         public bool CanExecute(object parameter)
  41:         {
  42:             return _canExecute == null ? true : _canExecute(parameter);
  43:         }
  44:  
  45:         /// <summary>
  46:         /// Occurs when changes occur that affect whether or not the command should execute.
  47:         /// </summary>
  48:         public event EventHandler CanExecuteChanged
  49:         {
  50:             add { CommandManager.RequerySuggested += value; }
  51:             remove { CommandManager.RequerySuggested -= value; }
  52:         }
  53:  
  54:         /// <summary>
  55:         /// Defines the method to be called when the command is invoked.
  56:         /// </summary>
  57:         /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
  58:         public void Execute(object parameter)
  59:         {
  60:             _execute(parameter);
  61:         }
  62:  
  63:         #endregion
  64:     }

RelayCommand simplifies the ICommand implementation to the following few lines:

public ICommand SaveCommand
{
get
{
if(_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => Save(),
param => CanSave);
}
return _saveCommand;
}
}

View is simply a user interface component, like a DataGrid. You can bind your ViewModel to the view and the view will update itself in case of data modification (or can update data in case of two-way binding). You can bind a control to the command of ViewModel and the control will enable/disable itself if the command behind the control cannot be executed (e.g. you can’t save the data because the model data contains errors).

MVVM pattern is complicated at first sight and you will have to create much more complicated architecture and classes, but it worths to understand and implement.

No comments:

Post a Comment