Decoupled Communication with Prism (Commanding)
WPF provides RoutedCommand which is great at connecting command invokers such as menu items and buttons with command handlers that are associated with the current item in the visual tree that has keyboard focus.
However we had several scenarios that Prism needed to support where the command handler lived in a controller that had no associated elements in the visual tree, or was not necessarily the focused element. To provide this functionality we developed CompositeCommand and DelegateCommand which have a direct routing mechanism, compared to RoutedCommand which uses tunneling and bubbling.
The StockTraderRICommands class provides several static composite commands.
public static class StockTraderRICommands
{
public static ActiveAwareCompositeCommand SubmitOrderCommand = new ActiveAwareCompositeCommand();
public static ActiveAwareCompositeCommand CancelOrderCommand = new ActiveAwareCompositeCommand();
public static CompositeCommand SubmitAllOrdersCommand = new CompositeCommand();
public static CompositeCommand CancelAllOrdersCommand = new CompositeCommand();
}
The ActiveAwareCompositeCommand and DelegateCommand will be discussed in a separate blog post.
This CompositeCommand is an implementation of ICommand so that it can be bound to invokers. CompositeCommands can be connected to many "child" commands and when the CompositeCommand is invoked, the child commands will also be invoked.
Let's take a look at the SubmitAllOrdersCommand. This command is defined as a static field on a static class which makes it pretty easy to wire up to an invoker in XAML.
OrdersView.xaml:
<Button Name="SubmitAllButton" Command="{x:Static inf:StockTraderRICommands.SubmitAllOrdersCommand}">Submit All</Button>
It is also pretty easy to connect child commands to this CompositeCommand.
OrdersController.cs:
commandProxy.SubmitAllOrdersCommand.RegisterCommand(orderCompositePresenter.SubmitCommand);
The commandProxy is a proxy to the static StockTraderRICommands class and is used to unit test the OrdersController.
The OrdersController registers the SubmitCommand provided by the orderCompositePresenter with the global SubmitAllOrdersCommand. By doing this, we are saying that every order instance has its own SubmitCommand that will handle processing of the order submission. Also since each order's SubmitCommand is registered with the SubmitAllOrdersCommand, when the global SubmitAllOrdersCommand is invoked, each order's SubmitCommand is also invoked.
An important differentiation between commands and events are that commands convey the notion of enablement. If the handler of a command reports that validation has failed and that the command should not be invoked, this information makes its way back to the invoker resulting in the disabling of the invoker.
CompositeCommands support this notion of enablement. CompositeCommands listen to the CanExecuteChanged event of each of its child commands. It then raises this event notifying its invoker(s). The invoker(s) reacts to this event by calling CanExecute on the CompositeCommand. The CompositeCommand then repolls all child commands by calling CanExecute on each. If any call to CanExecute returns false, the CompositeCommand will return false, thus disabling the invoker(s).
More specifically, the SubmitAllOrdersCommand responses to each one of its child SubmitCommands and returns false to CanExecute unless ALL of its child commands CanExecute equals true. If all orders can be submitted, the invoker will be enabled and clicking/invoking the invoker calls Execute on the SubmitAllOrdersCommand which calls Execute on each one of the child SubmitCommands.
How does this help me with cross module communication? I expect Prism apps to have global CompositeCommands that are defined in the shell that have meaning cross modules such as "Save", "Save All", "Cancel". Modules can then register their local commands with these global commands and participate in their execution.
Comments
Anonymous
June 03, 2008
A new drop of Prism has been published last Thursday. Among other things, in this drop we refactoredAnonymous
June 03, 2008
Francis has been a busy man lately on his blog with a series of posts on the various loosely coupledAnonymous
June 03, 2008
Francis has been a busy man lately on his blog with a series of posts on the various loosely coupledAnonymous
June 03, 2008
Francis has been a busy man lately on his blog with a series of posts on the various loosely coupledAnonymous
October 04, 2009
The comment has been removedAnonymous
October 04, 2009
Sorry, but the Command class didn't paste completely, here it is: public class GlobalCommands : IGlobalCommands { public virtual CompositeCommand SetTitlesCommand { get; protected set; } public virtual IGlobalCommands InitializeCommands() { SetTitlesCommand = new CompositeCommand(); return this; } } and the corresponding Interface: public interface IGlobalCommands { CompositeCommand SetTitlesCommand { get; } } Cheers ...