Recently I have been working on a Windows Presentation Foundation (WPF) application designed to manage tasks. It is not the first time I developed an application in WPF. However, I was struggling with the same problem I was struggling with before:
The problem: Coupling my views (WPF) to my domain model (C#).
The solution I came up with back then is to make the domain model aware of the need to bind and implement specific functionality like [System.ComponentModel.INotifyPropertyChanged]. By doing this I cluttered my domain model with non domain model code, which made the model difficult to understand and maintain, basically: No Go!
The solution: Model-View-ViewModel pattern
I went out looking for pattern that could help me to eliminate this code cluttering. After doing some research using my favorite search engine I discovered that there was a pattern for this problem: Model – View – ViewModel (MVVM or M-V-VM).
This pattern basically says: just wrap your domain model in another model, the view model, and implement view specific logic (like: INotifyPropertyChanged) on that view model.
I also implemented my commands on this view model class, I could have factored it out to view controllers but I didn’t see the need for that right now.
For the implementation of the commands I choose the solutions suggested by [Karl on WPF] and [Josh Smith]: the RelayCommand class. The purpose of this class is to drastically reduce the number of command needed by providing a single class which takes a delegate to execute the actual command. It also contains some logic to hook into the WPF command infrastructure.
Here is my implementation of the RelayCommand in C#:
public class RelayCommand: ICommand
{
private readonly Predicate<object> canExecuteMethod;
private readonly Action</object><object> executeMethod;
public RelayCommand(Action</object><object> executeMethod): this(executeMethod, null) {}
public RelayCommand(Action</object><object> executeMethod, Predicate</object><object> canExecuteMethod)
{
// validate argument
if (executeMethod == null)
throw new ArgumentNullException("executeMethod");
// set values
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
#region ICommand Members
///<summary>
///Occurs when changes occur that affect whether or not the command can execute.
///</summary>
public event EventHandler CanExecuteChanged
{
add
{
if (canExecuteMethod != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (canExecuteMethod != null)
CommandManager.RequerySuggested -= value;
}
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <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>
public void Execute(object parameter)
{
executeMethod(parameter);
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
/// <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>
public bool CanExecute(object parameter)
{
return canExecuteMethod == null ? true : canExecuteMethod(parameter);
}
#endregion
public void RaiseCanExecuteChanged(EventArgs e)
{
CommandManager.InvalidateRequerySuggested();
}
}
public class RelayCommand<t>: ICommand
{
private readonly Predicate</t><t> canExecuteMethod;
private readonly Action</t><t> executeMethod;
public RelayCommand(Action</t><t> executeMethod): this(executeMethod, null) {}
public RelayCommand(Action</t><t> executeMethod, Predicate</t><t> canExecuteMethod)
{
// validate argument
if (executeMethod == null)
throw new ArgumentNullException("executeMethod");
// set values
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
#region ICommand Members
///<summary>
///Occurs when changes occur that affect whether or not the command can execute.
///</summary>
public event EventHandler CanExecuteChanged
{
add
{
if (canExecuteMethod != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (canExecuteMethod != null)
CommandManager.RequerySuggested -= value;
}
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <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>
public void Execute(object parameter)
{
executeMethod((T)parameter);
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
/// <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>
public bool CanExecute(object parameter)
{
return canExecuteMethod == null ? true : canExecuteMethod((T)parameter);
}
#endregion
public void RaiseCanExecuteChanged(EventArgs e)
{
CommandManager.InvalidateRequerySuggested();
}
}
Conclusion
The MVVM pattern is a great pattern which allows you to separate the WPF view from your domain model, preventing code cluttering. I recommend every WPF or Silverlight developer to look into this pattern and use it when you encounter the same problem I had.
References: