To access the code discussed in this post click here. Feel free to do what you want with it. Though no warranty for you.
Commanding is a pretty amazing thing, and in WPF it is about as good as it gets. But as with all things cool, design decisions need to be made when considering their usage in an application. One such decision that I have had to make several times is how to best deal with core application commands such as the SaveCommand and CloseCommand. These global application commands are particularly tricky for a few reasons. First, you must ensure consistent behavior throughout the entire application. It is important that users do not have to relearn these commands on each screen the navigate to. Secondly and equally important, the development experience must be simple enough that the first point can be realized. These type of application commands all share three important traits.
- Accessible– Commands must be accessible in consistent way across your application
- Predictable – Commands must behave in a predictable way across the application when executed
- Contextual– Commands must be able to consider application context
Over time I have started to use the term Contextual Commands when describing these types of commands. In this post I will outline the approach that I have used with success on a few different projects as well as provide a simple reference implementation. But before that, let us outline what each of the three traits listed above mean.
Accessible
Accessibility is a primary trait that Contextual Commands must have. This simply means that commands are presented to the user and executed by the user in a similar fashion throughout the entire application. A good example of this is how Microsoft Word handles the Save command. Several consistent options exists.
- Control + S
- The Save button on the tool bar
- The Save menu item on the Ribbon application menu
In fact users have become so conditioned to this commands that many will hit control+s as part of their normal key press behavior, allowing them to save their work as they go.
To accomplish Accessibility in a developer friendly way I choose to make all contextual commands singletons. (Take a deep breath, I know, I know). Utilizing the Singleton Pattern in this case frees developers from the pain of creating instances, command bindings, and all the other error prone work that would need to be done otherwise. Making Contextual Commands singletons allows us to expose commands in multiple ways while ensuring the basic buildup of a command will remain consistent. In short all visualizations of a Contextual Command use the same instance limiting its possible variation that could hurt overall usability.
Predictable
Predictability means that aside from contextual differences, the execution of a command will behave in the same way every time it is executed. For example if you decide the Save button will be disabled until a view is dirty that choice should hold true throughout all views. If you decide that your close command is going to prompt users for unsaved changes we need to make sure it always does. There should be no variation in overall experience of using these commands. Holding to the standard of predictability will ensure that developers need not worry about such choices preventing them from mucking up your beautiful framework code.
To accomplish Predictablility base implementations of each command will be provided. Using a combination of the Tempalate Method and Provider Pattern we can open the implementation of the command enough to developers so contextual differences can be supported yet at the same time closing off its core implementation ensuring its execution is predictable every time. Textbook Open Closed Principle.
Contextual
The last and most important point is that these commands must consider the current context of the application. For example clicking the save button in Microsoft Word it saves the document that is currently in view. This may seem simple, but often times this simple concept turns into some very nasty error prone code. Think about what it means to save a document. To save the document correctly there are several things that need to be considered. Is the file readonly? Is the file on a network share? Is the file in collaboration mode? Is the file a word document? Is it a html document? All of these decisions are contextual in nature and will likely differ on each view in an application.
Gathering enough context to correctly save something can often times be a lot of work. Using the Provider Pattern helps us keep this code encapsulated and clean. Each view/control will have a chance to provide to the commands the contextual details needed to handle the difference present in each view.
Disclaimer
As usual, the reference implementation I am providing here is illustrative at best. There are many things that should be done differently in a real application.
The Commands and Provider
A few months ago I posted my flavor of the RelayCommand . RelayCommand is a simple implementation of the ICommand interface adding to it support for asynchronous processing, Predicates and Actions, as well BusyCursor support. Also for simplicity I usually group all Contextual Commands in a single class called ContextualCommands. If you prefer a more granular approach you could split this class apart into several separate classes, I will leave that up to you . Lets take a quick look at components needed to support the implementation. The image blow shows the core components of our the setup.
The IContextualCommands interface simply defines the commands I consider contextual (CloseCommand, SaveCommand). In addition to defining the commands this interface defines the SetProvider method. This method gives consumers the ability to change the command provider that is used for each of the command implementations. In the end it is the SetProvider that allows individual views to provide the contextual information needed by the commands. The SetProvider method will work with the IContextualCommandsProvider interface.
IContextualCommand Interface
The IContextualCommandsProvider interface defines the methods each provider must implement. Since I have lumped the ContextualCommands into a single class the provider must provide the details for each command. The implementation provided by the provider will be used during command execution this is the key to supplying contextual differences from view to view.
</pre> using System.Windows.Input; namespace ProviderBasedCommands.Framework { /// <summary> /// Interface for our Contextual Commands /// </summary> public interface IContextualCommands { /// <summary> /// Gets the save command. /// </summary> /// <value>The save command.</value> ICommand SaveCommand { get; } /// <summary> /// Gets the close command. /// </summary> /// <value>The close command.</value> ICommand CloseCommand { get; } /// <summary> /// Sets the provider for the contextual commands. /// </summary> /// <param name="contextualCommandsProvider">The contextual commands provider.</param> void SetProvider(IContextualCommandsProvider contextualCommandsProvider); /// <summary> /// Determines whether this instance [can close command execute] the specified param. /// </summary> /// <param name="param">The param.</param> /// <returns> /// <c>true</c> if this instance [can close command execute] the specified param; otherwise, <c>false</c>. /// </returns> bool CanCloseCommandExecute(object param); /// <summary> /// Closes the command executed. /// </summary> /// <param name="param">The param.</param> void CloseCommandExecuted(object param); /// <summary> /// Determines whether this instance [can save command execute] the specified param. /// </summary> /// <param name="param">The param.</param> /// <returns> /// <c>true</c> if this instance [can save command execute] the specified param; otherwise, <c>false</c>. /// </returns> bool CanSaveCommandExecute(object param); /// <summary> /// Saves the command executed. /// </summary> /// <param name="param">The param.</param> void SaveCommandExecuted(object param); } } <pre>
A IContextualCommandsProvider Implementation
</pre> namespace ProviderBasedCommands.Framework { public interface IContextualCommandsProvider { /// <summary> /// Determines whether this instance [can close command execute] the specified param. /// </summary> /// <param name="param">The param.</param> /// <returns> /// <c>true</c> if this instance [can close command execute] the specified param; otherwise, <c>false</c>. /// </returns> bool CanCloseCommandExecute(object param); /// <summary> /// Closes the command executed. /// </summary> /// <param name="param">The param.</param> void CloseCommandExecuted(object param); /// <summary> /// Determines whether this instance [can close command execute] the specified param. /// </summary> /// <param name="param">The param.</param> /// <returns> /// <c>true</c> if this instance [can close command execute] the specified param; otherwise, <c>false</c>. /// </returns> bool CanSaveCommandExecute(object param); /// <summary> /// Closes the command executed. /// </summary> /// <param name="param">The param.</param> void SaveCommandExecuted(object param); } } <pre>
Contextual Commands
The ContextualCommands class is the single implementation of the IContextualCommands interface and is registered with Unity as a singleton. This class exposes the Contextual Commands to the rest of the application. This class does a few important things for us.
- It creates our CloseCommand and SaveCommand instances
- It handles the registration of input bindings to these commands
- It manages the current command provider via the SetProvider method
- It delegates the command implementation to the current provider
</pre> using System.Windows; using System.Windows.Input; using ProviderBasedCommands.Command; namespace ProviderBasedCommands.Framework { public class ContextualCommands : IContextualCommands { #region Private Data Members /// <summary> /// Close Command Input Key Binding /// </summary> private readonly InputBinding _closeCommandInputBinding; /// <summary> /// Save Command Input Key Binding /// </summary> private readonly InputBinding _saveCommandInputBinding; /// <summary> /// Current provider for command implementations /// </summary> private IContextualCommandsProvider _contextualCommandsProvider; #endregion #region Constructor /// <summary> /// Initializes a new instance of the <see cref="ContextualCommands"/> class. /// </summary> public ContextualCommands() { //Create the actual command instances and related key bindings CloseCommand = new RelayCommand(CanCloseCommandExecute, CloseCommandExecuted); _closeCommandInputBinding = new KeyBinding(CloseCommand, Key.Escape, ModifierKeys.None); SaveCommand = new RelayCommand(CanSaveCommandExecute, SaveCommandExecuted); _saveCommandInputBinding = new KeyBinding(SaveCommand, Key.S, ModifierKeys.Control); } #endregion #region IContextualCommands Members /// <summary> /// Gets the save command. /// </summary> /// <value>The save command.</value> public ICommand SaveCommand { get; private set; } /// <summary> /// Gets the close command. /// </summary> /// <value>The close command.</value> public ICommand CloseCommand { get; private set; } /// <summary> /// Sets the provider for the contextual commands. /// </summary> /// <param name="contextualCommandsProvider">The contextual commands provider.</param> public void SetProvider(IContextualCommandsProvider contextualCommandsProvider) { //Set provider _contextualCommandsProvider = contextualCommandsProvider; //If null lets clear the key bindings if (contextualCommandsProvider == null) { RemoveKeyBindings(); } RegisterKeyBindings(); } /// <summary> /// Determines whether this instance [can close command execute] the specified param. /// </summary> /// <param name="param">The param.</param> /// <returns> /// <c>true</c> if this instance [can close command execute] the specified param; otherwise, <c>false</c>. /// </returns> public bool CanCloseCommandExecute(object param) { if (_contextualCommandsProvider == null) { return false; } return _contextualCommandsProvider.CanCloseCommandExecute(param); } /// <summary> /// Closes the command executed. /// </summary> /// <param name="param">The param.</param> public void CloseCommandExecuted(object param) { if (_contextualCommandsProvider != null) { _contextualCommandsProvider.CloseCommandExecuted(param); } } public bool CanSaveCommandExecute(object param) { if (_contextualCommandsProvider == null) { return false; } return _contextualCommandsProvider.CanSaveCommandExecute(param); } /// <summary> /// Saves the command executed. /// </summary> /// <param name="param">The param.</param> public void SaveCommandExecuted(object param) { if (_contextualCommandsProvider != null) { _contextualCommandsProvider.SaveCommandExecuted(param); } } #endregion #region Private Methods /// <summary> /// Registers the key bindings. /// </summary> private void RegisterKeyBindings() { if (!Application.Current.MainWindow.InputBindings.Contains(_closeCommandInputBinding)) { Application.Current.MainWindow.InputBindings.Add(_closeCommandInputBinding); } if (!Application.Current.MainWindow.InputBindings.Contains(_saveCommandInputBinding)) { Application.Current.MainWindow.InputBindings.Add(_saveCommandInputBinding); } } /// <summary> /// Removes the key bindings. /// </summary> private void RemoveKeyBindings() { Application.Current.MainWindow.InputBindings.Add(new KeyBinding(CloseCommand, Key.Escape, ModifierKeys.None)); Application.Current.MainWindow.InputBindings.Add(new KeyBinding(SaveCommand, Key.S, ModifierKeys.Control)); } #endregion } } <pre>
Usage
With the basic components built out a discussion about their usage may be helpful before you look at the example code. Since the ContextualCommands class is a singleton the only moving part is the underlying provider implementation. Generally speaking the ContextualCommands class is supplied with a new provider each time a view is presented. In the example code you will notice I include a simple Controller object. This class is responsible for presenting views into the MainRegion of our application. Essentially when the Controller class is asked to present a view the following things happen.
- The Controller first asks Unity to create our View
- The Controller looks to see if the resolved View has a ViewModel that implements the IContextualCommandsProvider interface
- If the View’s ViewModel implements the IContextualCommandsProvider interface, the Controller calls the SetProvider method on the ContextualCommands instance passing to it the the IContextualCommandsProvider implementation
</pre> using Microsoft.Practices.Unity; namespace ProviderBasedCommands.Framework { /// <summary> /// Controller used to present and close views /// </summary> public class Controller : IController { #region Dependency /// <summary> /// Gets or sets the container. /// </summary> /// <value> /// The container. /// </value> [Dependency] public IUnityContainer Container { get; set; } /// <summary> /// Gets or sets the region manager. /// </summary> /// <value> /// The region manager. /// </value> [Dependency] public IRegionManager RegionManager { get; set; } /// <summary> /// Gets or sets the contextual commands. /// </summary> /// <value>The contextual commands.</value> [Dependency] public IContextualCommands ContextualCommands { get; set; } #endregion #region IController Members /// <summary> /// Presents the dialog. /// </summary> /// <typeparam name="T"></typeparam> public void PresentDialog<T>() where T : IDialog { RegionManager.ApplyDialogSettings(null); RegionManager.DialogRegion.Content = Container.Resolve<T>(); RegionManager.DialogRegion.ShowDialog(); } /// <summary> /// Closes the view. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="view">The view.</param> /// <returns></returns> public bool CloseView<T>(T view) where T : IView<IViewModel> { RegionManager.MainRegion.Content = null; ClearCommandProviders(view); return true; } /// <summary> /// Presents the view. /// </summary> /// <typeparam name="T"></typeparam> public void PresentView<T>() where T : IView<IViewModel> { var view = Container.Resolve<T>(); SetViewAsCommandProvider(view); RegionManager.MainRegion.Content = view; } /// <summary> /// Presents the view. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dialogSettings">The dialog settings.</param> public void PresentDialog<T>(DialogSettings dialogSettings) where T : IDialog { RegionManager.ApplyDialogSettings(dialogSettings); RegionManager.DialogRegion.Content = Container.Resolve<T>(); RegionManager.DialogRegion.ShowDialog(); } #endregion #region Private Methods /// <summary> /// Changes the command providers. /// </summary> /// <param name="view">The view.</param> private void SetViewAsCommandProvider(IView<IViewModel> view) { var contextualCommandProvider = view.ViewModel as IContextualCommandsProvider; if (contextualCommandProvider != null) { ContextualCommands.SetProvider(contextualCommandProvider); } } /// <summary> /// Changes the command providers. /// </summary> /// <param name="view">The view.</param> private void ClearCommandProviders(IView<IViewModel> view) { var contextualCommandProvider = view.ViewModel as IContextualCommandsProvider; if (contextualCommandProvider != null) { ContextualCommands.SetProvider(null); } } #endregion } } <pre>
In short, every time a view is presented that view becomes the provider to the ContextualCommands.
The Null Provider
In real life not all ViewModels would implement the IContextualCommandsProvider interface. This puts us in a tricky position when the current ViewModel implements the interface, and the ViewModel we are navigating to does not. Since the new ViewModel is not a provider the Controller will not call the SetProvider as a result the last ViewModel will remain the provider. This can lead to memory leaks, and all sorts of strange things. To get around the disconnect the SetProvider method allow nulls to be passed in. This essentially disables all of the ContextualCommands. This problem would best be solved with the creation of a NullProvider type but for simplicity I have ignored that fact.
Sample Application
I created a very small sample implementation of this concept. To download it click here. This application contains the following things.
- A very brief and limited MVVM Framework, simply used to illustrate how these commands may be used
- The ContextualCommands implementation
- Unity support to manage the moving parts
- A RelayCommand implementation
- A BusyCursor implementation
Please feel free to download the code and sort through all the details yourself, my intent with this post is simply to introduce the concept of Provider Based Commands. I hope the example code is clear enough to show you the power of this approach. I can tell you from personal experience, every time I go to a new client this is one of the first things I implement. And time and time again, I am happy I did it this way.
Enjoy!