Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

The Nito.MVVM (WPF) Library: Commands

Rate me:
Please Sign up or sign in to vote.
5.00/5 (13 votes)
25 Jul 2009BSD8 min read 56.6K   569   34   9
Describes the ViewModel command classes in the Open-Source Nito.MVVM (WPF) library, and provides guidelines on their usage.

Introduction

The Nito.MVVM (WPF) library includes several command classes that are useful when writing ViewModel classes that are not dependency objects. Rather than provide one ICommand implementation for all scenarios, Nito.MVVM provides many command classes, each one satisfying a range of Use Cases. The needs of a particular Use Case determines which command class to use.

The Nito.MVVM command classes are for ViewModel commands, not GUI commands. WPF ships with many common GUI commands, such as Copy, Paste, and BrowseForward. Generally speaking, GUI commands operate on View objects or state, not on Model objects. GUI commands are implemented using RoutedCommand, a command object that is routed through the View element tree.

ViewModel commands operate on the Model objects, and they are exposed by the ViewModel to the View for binding. They encapsulate domain specific logic, such as SaveClient or GenerateInvoice. The command classes in the Nito.MVVM library aid writing ViewModel classes that need to expose commands. It is possible to use RoutedCommand to implement ViewModel commands, but this is inefficient and does not scale well.

Command Types and CanExecuteChanged Causes

ViewModel commands may be grouped along two axes, command type and CanExecuteChanged cause. There are three command types:

  1. Delegate command - a normal command that is independent of other commands. Conceptually, it has only code, though that code may invoke other commands.
  2. Composite command - a command composed entirely of other (unrelated) commands without additional code. Conceptually, it has an ordered list of child commands.
  3. Multicommand - a command composed of identical commands from a child object list. Conceptually, it has an unordered set of children with a command selector string. For example, a SaveAllCommand may be defined as a SaveCommand for each child ViewModel.

Most ViewModel commands are Delegate commands that invoke a method on a Model object. Composite commands are less common. Multicommands are useful in multi-selection scenarios. Both Composite commands and Multicommands may have any type of command in their child command lists.

Commands must notify listeners if their CanExecute return value may have changed. This event can be caused by different situations; there are five CanExecuteChanged sources:

  1. Static - a command that is always able to run. ICommand.CanExecute always returns true, and ICommand.CanExecuteChanged is never raised. This source is rare in practice, and is usually some form of New command.
  2. Automatic - a command whose ability to run is determined from the View State. Automatic commands tie their ICommand.CanExecuteChanged events into CommandManager.RequerySuggested, which is fired on significant user input events. Because of this, they appear to fire this event "automatically", hence the name. If the Model does not generate non-user-initiated changes, then Automatic commands may also be used for commands that depend on Model state; this does not scale well, though it is better than using RoutedCommand.
  3. ChildCanExecuteChanged - a command whose ability to run is entirely dependent on the ICommand.CanExecute results of its child commands. This is the normal source for Composite commands and Multicommands.
  4. Multisubscription - a command whose ability to run may change based on events from other objects, such as INotifyPropertyChanged.PropertyChanged and/or INotifyCollectionChanged.CollectionChanged. If the Model objects implement INotifyPropertyChanged, then this command source is useful for commands whose ability to run is determined from the Model state.
  5. Manual - a command with unusual requirements defining its ability to run.

Each command type may have different CanExecuteChanged causes:

  1. Delegate commands may be Static, Automatic, Multisubscription, or Manual. Generally, Delegate commands are Multisubscription, but may also be Automatic for simplicity (at a loss of efficiency).
  2. Composite commands are normally ChildCanExecuteChanged. However, if any of its child commands are Automatic, then the Composite command may be Automatic as an optimization.
  3. Multicommands are normally ChildCanExecuteChanged. Like Composite commands, if any of its child commands are Automatic, then the Multicommand may be Automatic as an optimization.

Command Classes in Nito.MVVM

There are several command classes provided in Nito.MVVM. The classes with type parameters use that parameter as the type of argument passed to the command. Each type is designed to cover one of the common Use Cases:

  1. StaticCommand/StaticCommand<T> - A Static Delegate command.
  2. AutomaticCommand/AutomaticCommand<T> - An Automatic Delegate command.
  3. ManualCommand/ManualCommand<T> - A Manual Delegate command or Multisubscription Delegate command.
  4. CompositeCommand - A ChildCanExecute Composite command.
  5. AutomaticCompositeCommand - An Automatic Composite command or Automatic Multicommand.
  6. MultiCommand - A ChildCanExecute Multicommand.

API Descriptions

Delegate commands have two delegate properties, CanExecute and Execute:

C#
//// Delegate properties for commands without parameters
/// <summary>
/// Gets or sets the delegate invoked to execute this command.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Action Execute { get; set; }
/// <summary>
/// Gets or sets the delegate invoked to determine if this command may execute.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Func<bool> CanExecute { get; set; }
//// Delegate properties for commands with parameters
/// <summary>
/// Gets or sets the delegate invoked to execute this command.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Action<T> Execute { get; set; }
/// <summary>
/// Gets or sets the delegate invoked to determine if this command may execute.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Func<T, bool> CanExecute { get; set; }

Composite commands have a child list. The AutomaticCompositeCommand class exposes this as an enumerable property: public IEnumerable<ICommand> Subcommands, which may be set to the results of a LINQ query. Each time CanExecute or Execute is invoked, Subcommands is enumerated, reevaluating the query. In contrast, the CompositeCommand class needs to subscribe to its child commands' CanExecuteChanged events, so it acts as a collection of commands, implementing IList<ICommand>.

The MultiCommand class also has a child list of sorts. This class has two properties of note:

C#
/// <summary>
/// Gets or sets the source collection of this multiproperty.
/// This class is more efficient if the source collection implements
/// <see cref="IList"/> and <see cref="INotifyCollectionChanged"/>.
/// </summary>
public IEnumerable SourceCollection { get; set; } // (not the actual implementation)
/// <summary>
/// Gets or sets the property path applied to the elements in the source collection.
/// </summary>
public string Path { get; set; } // (not the actual implementation)

MultiCommand will monitor SourceCollection if it implements INotifyCollectionChanged; this is commonly set to an ObservableCollection<T> or CollectionView. The Path property is a simple property path, e.g., "ChildVM.SaveCommand". The path is evaluated for each element of the source collection, resulting in a collection of child commands. Furthermore, MultiCommand will monitor each object along the property path if it implements INotifyPropertyChanged and updates itself appropriately.

ManualCommand and ManualCommand<T> both allow Manual and Multisubscription notifications. Manual notifications use the NotifyCanExecuteChanged method, and those classes also expose a read-only delegate property CanExecuteChangedSubscription, which can be used to subscribe to ICommand.CanExecuteChanged events of other commands. (For a description of why this is necessary, see "A Warning About ICommand.CanExecuteChanged", below.) Note that this support for Multisubscription commands does not use weak events, so the lifetime of the Nito command object is tied to the lifetimes of all objects that provide state change notification.

Comparison With Other Solutions

Several existing solutions have already been developed:

  1. Josh Smith's RelayCommand - one of the most popular choices, from his landmark February 2009 MSDN article. This is an Automatic Delegate command. Nito.MVVM.AutomaticCommand<object> has only minor API differences with RelayCommand.
  2. Ocean's RelayCommand and RelayCommand<T> - from Karl Shifflett's blog. This is an Automatic Delegate command, pretty much identical to Josh Smith's RelayCommand.
  3. Marlon's SimpleCommand, from his blog post. This is also an Automatic Delegate command, with an API even more similar to Nito.MVVM.AutomaticCommand<object>.
  4. Prism's DelegateCommand<T>, from the February 2009 drop. This is a Manual Delegate command, so it is similar to Nito.MVVM.ManualCommand<T>. However, it has a bug because it does not implement CanExecuteChanged as a weak event. Encountering this bug would be rare, however, and it is only a lifetime extension bug.
  5. Prism's CompositeCommand, from the same source. This is similar to Nito.MVVM.CompositeCommand (though Prism's CompositeCommand is also intended to be used as a Multicommand). However, not only does Prism's CompositeCommand implement CanExecuteChanged as a strong event, but it also assumes that its child commands implement it as a strong event. This bug is more serious: it can result in missed events unless all child commands are Prism Delegate commands.
  6. MVVM Toolkit's DelegateCommand and DelegateCommand<T>, from the May 2009 drop. These are some heroic classes that satisfy the needs of Static Delegate, Automatic Delegate, and Manual Delegate commands. Unlike Prism's commands, these classes have a correct CanExecuteChanged implementation.
  7. Caliburn's commands, from change-set 26106 (July 2009). Caliburn builds its Commands on top of its Action system; they only become WPF commands when bound to an element. As such, they can be viewed as a type of Automatic Delegate command that can be defined using XAML. Caliburn also has the concept of composite commands.

A Warning About ICommand.CanExecuteChanged

There is one word of warning for those who try to make their own ICommand-derived types: ICommand.CanExecuteChanged must be implemented as a weak event. Nathan Nesbit has the best description I could find of this problem on his blog, though he approaches his solution from the perspective of writing a custom control, not a custom command.

Reusable Components

All source in the Nito.MVVM library is FxCop and StyleCop clean, and the download includes a suite of Unit Tests. There are a number of types written to support the command classes; some of the more interesting ones are these:

  • SimplePropertyPath - Provides a very simple quasi-binding. Unlike System.Windows.Data.Binding, SimplePropertyPath does not require dependency properties. It merely propagates an existing INotifyPropertyChanged implementation "forward" to other classes, and also propagates writes "back" to the original property. Check out the Unit Tests for some examples.
  • ProjectedCollection - Takes a source collection along with a simple property path, and projects each element of the source collection along that path. MultiCommand uses ProjectedCollection to treat a sequence of child ViewModel objects as though they were a collection of ICommand objects. Any collection or property changes are detected and propagated.
  • WeakCollection<T> - Implements a collection of weak references that purges itself when it is iterated. This is used by CanExecuteChangedCore to implement ICommand.CanExecuteChanged correctly.
  • A number of classes work together to provide "compile-time Reflection" for INotifyPropertyChanged, removing the need for literal strings of property names. Classes of note include NotifyPropertyChangedBase<T> and PropertyChangedEventHandlerExtensions. Further references to alternate solutions to this problem are in the code.

Official Location

The code attached to this article is a pre-release version of the Nito.MVVM library. Official releases are done through the Nito.MVVM CodePlex homepage.

Thanks!

Votes and comments are welcome!

Article History

  • 2009-07-21 - Initial revision.
  • 2009-07-24 - Removed Onyx's RelayCommand from the "Comparison With Other Solutions" section, since it is not actually a part of the Onyx framework, and will be removed in a future release.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer (Senior)
United States United States
Stephen Cleary is a Christian, husband, father, and programmer living in Northern Michigan.

Personal home page (including blog): http://www.stephencleary.com/

Comments and Discussions

 
GeneralCode Downloads Pin
Member 456543320-Sep-09 23:58
Member 456543320-Sep-09 23:58 
GeneralGood job, but a minor clarification Pin
Pete O'Hanlon24-Jul-09 2:29
subeditorPete O'Hanlon24-Jul-09 2:29 
GeneralRe: Good job, but a minor clarification Pin
Stephen Cleary24-Jul-09 5:24
Stephen Cleary24-Jul-09 5:24 
GeneralDitto Pin
Josh Smith23-Jul-09 5:40
Josh Smith23-Jul-09 5:40 
Great article, and I'm looking forward to the examples. Got my 5! Smile | :)

:josh:
Try Crack![^]
Sleep is overrated.

GeneralGreat article Pin
FantasticFiasco22-Jul-09 19:52
FantasticFiasco22-Jul-09 19:52 
GeneralGreat Job Pin
Dr. WPF22-Jul-09 13:33
Dr. WPF22-Jul-09 13:33 
QuestionExample? Pin
MP3Observer22-Jul-09 0:32
MP3Observer22-Jul-09 0:32 
AnswerRe: Example? Pin
Stephen Cleary22-Jul-09 2:20
Stephen Cleary22-Jul-09 2:20 
GeneralRe: Example? Pin
Stephen Cleary24-Jul-09 5:28
Stephen Cleary24-Jul-09 5:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.