Morgan Skinner, an Application Development Consultant with Microsoft UK, had a post on his blog yesterday complaining at the quantity of boilerplate code needed to create commands in ViewModels for WPF and Silverlight. I share his irk about that.
He shows a clever technique involving ICustomTypeDescriptor that avoids creating properties (and hence backing fields) for each of the commands. But I think he goes too far. Without the properties on the ViewModel, its tests are going to have a hard time knowing what commands it exposes; and accessing them through code isn’t going to be as straightforward as a property access.
So I propose an alternative technique. It boils down to this:
public class MyViewModel : ViewModelBase { public ICommand Demonstrate { get { return Commands.GetOrCreateCommand( () => this.Demonstrate, DoDemonstration, IsFeelingDemonstrative); } } private void DoDemonstration() { MessageBox.Show("Ta da!"); } private bool IsFeelingDemonstrative() { return DateTime.Today.DayOfWeek == DayOfWeek.Friday; } }
As you can see, ViewModelBase is providing a Commands property to manage all the commands. This neatly avoids having to create a field for each one. Commands is an instance of CommandHolder which is equally simple:
public class ViewModelBase : INotifyPropertyChanged { CommandHolder _commandHolder = new CommandHolder(); // snipped Property Change stuff protected CommandHolder Commands { get { return (_commandHolder ?? (_commandHolder = new CommandHolder())); } } }
public class CommandHolder { Dictionary<string, ICommand> _commands = new Dictionary<string, ICommand>(); public ICommand GetOrCreateCommand<T>( Expression<Func<T>> commandNameExpression, Action executeCommandAction, Func<bool> canExecutePredicate) { return GetOrCreateCommand( commandNameExpression, parameter => executeCommandAction(), parameter => canExecutePredicate()); } public ICommand GetOrCreateCommand<T>( Expression<Func<T>> commandNameExpression, Action<object> executeCommandAction, Func<object, bool> canExecutePredicate) { var commandName = SymbolHelpers.GetPropertyName(commandNameExpression); if (!_commands.ContainsKey(commandName)) { var command = new DelegateCommand(executeCommandAction, canExecutePredicate); _commands.Add(commandName, command); } return _commands[commandName]; } }
What do you think? Are my concerns about testa-reada-bility warranted? Does my technique eliminate enough of the boilerplate?
I’ve put a full sample on MSDN code gallery.