In a MVVM architecture ICommands are the primary abstractions that formalize the exposing and consumption of ViewModel defined functionality for use by its View. However, unlike WPF, Silverlight features no build-in ICommand implementations, supporting infrastructure or out-of-box integration with Controls. So, with this post I'll cover the ICommand-related infrastructure present in nRoute.Toolkit and go-over various aspects related to exposing, consumption and using ICommands in Silverlight.
1. Defining ICommands
Included in the nRoute.Toolkit are two ICommand implementations called ActionCommand<T> and ActionableCommand<T>, they both basically take in a delegate that handles the execution of the command. However, the difference between an ActionCommand<T> and the ActionableCommand<T> is simply that the latter allows you to define a delegated-based predicate that can determine as to if the command is currently executable. Also for convenience sake included are two non-generic versions of the same commands, that specifically relieve you of handling a command parameter.
Also as shown above, the featured ICommand implementations provides you with an IsActive property using which you can enable or disable the command. Further, the RequeryCanExecute method allows you to re-evaluate as to if the current command is executable for its consumers. Further, to demonstrate a somewhat realistic use of an ICommand, we'll go-through a scenario from my Web Xcel demo app (shown below), whose code you can actually download from Codeplex.
For our use-case we are going to focus on a "Save Worksheet" functionality that is exposed as an ICommand via a property on the ViewModel. And the basic use-context is that we have this Worksheet Object (visualized as a Grid), which when changed can be saved by calling the "SaveWorksheet" Command, through the "Save Worksheet" Hyperlink Button (shown in the "Document Helper" panel above). Now, in the ViewModel code the SaveWorksheetCommand is declared and instanciated as follows:
1: // Declare Command
2: public ActionableCommand SaveWorksheetCommand { get; private set; }
3:
4: // Define Command
5: SaveWorksheetCommand = new ActionableCommand(() =>
6: {
7: // basic check
8: if (this.Worksheet == null || !IsDirty) return;
9:
10: // update the status
11: var _token = StatusViewService.UpdateStatus(SAVING_WORKSHEET_STATUS);
12: SaveWorksheet();
13: _token.Dispose();
14:
15: }, () => this.Worksheet != null && this.IsDirty)
16: .RequeryOnCommandExecuted(OpenWorksheetCommand)
17: .RequeryOnCommandExecuted(NewWorksheetCommand)
18: .RequeryOnPropertyChanged(this, () => IsDirty);
In line 2, we declare a SaveWorksheetCommand property of type ActionableCommand, note it is the non-generic version and thus takes in no ICommand parameter. On initialization of the ViewModel, the ICommand implementation is instantiated as shown in Line 5. The constructor for the ActionableCommand takes in two parameters, one to define the execute functionality and second to define a predicated as to if the command is currently executable. So with Line 6-to-14 the execution handler basically updates the Status-Bar message (Line 11), then saves the active Worksheet via a helper method called "SaveWorksheet" (Line 12) and then retract the Status-Bar update (Line 13) by disposing the given token. Internally, the SaveWorksheet method serializes the Worksheet Object to a xml file and allows the user to save by showing a Save File Dialog.
Also you can see in Line 8, we pre-check that the Active Worksheet to save is not null and that it has one or more pending changes (i.e. is dirty). That pre-check logic is basically the same logic that defines as to if we can execute the Save Worksheet command, and so in Line 15 the lambda statement defines the exactly same logic as the second parameter to the ActionableCommand constructor.
2. Defining ICommand Dependencies
As I hope is apparent, creating ICommands in your ViewModel is very straightforward but there is one aspect to that is rather cumbersome - the problem is that a command's execution is subject to a number of direct and indirect dependencies, and so whenever a dependency changes we must re-evaluated the current execution status of the command. In practical terms this means that dependencies should use the "RaiseQueryCommand" method to effect a CanExecuteChanged event, which tells an ICommand's consumers to check the executable status vis-a-vie the CanExecute method on the ICommand.
As you can image this is not only cumbersome but it also leads you to implicitly-defined ICommand dependencies throughout your code. So, with the nRoute.Toolkit's ICommand implementations we have a set of extension methods that you can use to explicitly define the dependencies, and using the same it also automatically updates any changes to the executable state of the ICommand. If this sounds fancy, it is not - lets look at our SaveWorksheetCommand example. In Lines 16-18 we explicitly define three cases that say re-query the executable status of the command whenever either of the OpenWorksheetCommand or NewWorksheetCommand commands are executed (because if we have just created or opened a Worksheet then there is nothing to save). And the third statement says that also re-query whenever the IsDirty property on "this" ViewModel changes (if Worksheet is Dirty, then the save status must have changed). So, now with these three extension methods the command's execution status is automatically re-evaluated, whenever either of the three explicit conditions occur.
Below is the list of the five included extension methods that help define dependencies on ICommands - however, do note you can write your own similar extension methods too (for more information, please see this post).
- RequeryWhenExecuted - this methods basically says that whenever the command itself executes then re-query it's execution state.
- RequeryOnCommandExecuted - as shown above, this one re-queries the command whenever some other command is executed
- RequeryOnCommandCanExecuteChanged - this extension method says whenever the executable status of a another specified command changes then do a re-query
- RequeryOnCollectionChanged - this pegs the re-query on a given collection changing, wherein the collection implements INotifyCollectionChanged
- RequeryOnPropertyChanged - this extension as we have seen, begets a re-query when a INotifyPropertyChanged implementing object's property changes
3. Consuming ICommands
As far as consuming ICommands is concerned, we are thoroughly-thoroughly covered by a so-named ExecuteCommandAction behaviour in the nRoute.Toolkit. And if you are wondering as why it is double-so-thorough, well because of three reasons besides the simple fact that it can execute commands. Reason one, even though the Silverlight framework itself does not support binding to Dependency Properties, with the ExecuteCommandBehavior you can bind to commands defined in your ViewModel - just remember you have to use the Binding post-fixed properties as shown in the Blend screenshot above. To know more about this so-called dual-property binding for Silverlight, please see my earlier post introducing nRoute.Toolkit. Similarly, you can also bind the parameter by using ParameterBinding property or alternatively statistically specify the parameter using the Parameter property.
Reason two, the execute command behaviour is implemented as a TriggerAction - what does that mean, it simply means you can pair it with any kind of Trigger (note, the Trigger and TriggerAction are from the Blend SDK). So the pairing can be done with an Event Trigger or a MouseWheel Trigger or a Gesture Trigger, in fact you can create your own trigger - like say one that responds to Voice Input. The benefit to all this Trigger-TriggerAction separation is that your use of a command is not limited to certain a set of pre-defined use-cases, which I think helps with both reusability and flexibility. So for example, with the SaveWorksheetCommand I've used the ExecuteCommandAction with two types of triggers.
1: <!-- ICOMMAND WITH KEY STROKES -->
2: <i:Interaction.Triggers>
3: <nTriggers:KeyTrigger WithControlModifier="True" Key="S" WithShiftModifier="True">
4: <nBehaviors:ExecuteCommandAction CommandBinding="{Binding SaveWorksheetCommand}"/>
5: </nTriggers:KeyTrigger>
6: </i:Interaction.Triggers>
Above, we are pairing the KeyTrigger (for key-set Ctrl+Shift+S) with the ExecuteCommandAction behaviour that triggers the SaveWorksheetCommand in our ViewModel. And below we are pairing the same command behaviour with an EventTrigger to raise the command on a click event of a Hyperlink Button. Now, the XAML might look dreadful, but don't worry all this can be done visually in Blend - which will spit out the same XAML you see below.
1: <!-- ICOMMAND WITH Click EVENT -->
2: <HyperlinkButton Content="Save Worksheet">
3: <i:Interaction.Triggers>
4: <i:EventTrigger EventName="Click">
5: <nBehaviors:ExecuteCommandAction ManageEnableState="True"
6: CommandBinding="{Binding SaveWorksheetCommand, Mode=OneWay}" />
7: </i:EventTrigger>
8: </i:Interaction.Triggers>
9: </HyperlinkButton>
4. Managing ICommand Control States
The third reason as to why the ExecuteCommandAction is thorough is that it allows you to control the enabled/interactivity state of the control it is applied on - by using the "ManageEnabledState" option (see the Blend screenshot above). What does that mean, it simply means that if the command's not executable it will disable the control for you - so for example in our SavewWorksheetCommand example, if there is no Active Worksheet or if the Active Worksheet is not dirty the Hyperlink Button will be disabled (see Line 5 in the XAML snippet above that enables this scenario by setting the ManageEnabledState to true).
The picture on the left shows the Hyperlink Button disabled when there are no pending changes to the Active Worksheet, whereas the picture on the right shows an enabled Hyperlink Button when there are pending changes - the same is also indicated by the asterisks in the title. The enabling/disabling is done for you automatically (once you opt in the ManageEnabledState option), as the behaviour listens for changes in command's executable status and evaluates its use status against the command. Further, the ExecuteCommandAction behaviour also also plays nice with controls that don't have an IsEnabled (ie. non-Control type derived controls) property by taking away their interactivity. In the screenshot below, the Border Control doesn't support an IsEnabled property, however when the attached command is not executable the Border Control is dimed down and its interactivity is taken away.
5. Relaying ICommands
Now one of the other common problem you will find with ICommands is related to consuming it with templated controls like ItemsControl or ListBox Controls in Silverlight. The underlying issue is that from within a DataTemplate you can't access the ViewModel and hence the ICommands are not addressable. The reason is simple, because a DataTemplate's DataContext is set to the item being enumerated - so if a customer item is being enumerated, then that would be set as the active DataContext. So how do we solve this problem, the answer is simple - we first create a command relay as a resource in our View and then bridge the relay with an actual implementation from our ViewModel. Note, the command relay will be a static resource and thus can be "binded" as such.
Lets consider a simple example, lets say we have a ItemsControl that lists Customers collection from our ViewModel - it uses a DataTemplate that needs to consume an EMailCustomerCommand which is also defined in our ViewModel. So here is what we do, we first define an EMailCustomerCommandRelay in our View's resources collection and then use a BridgeCommand behaviour to source the relay from our ViewModel via Binding. Below is the relevant XAML:
1: <!-- DECLARE RELAY -->
2: <UserControl.Resources>
3: <nComponents:CommandRelay x:Key="EMailCustomerCommandRelay" />
4: </UserControl.Resources>
5:
6: <!-- BRIDGE THE RELAY FROM THE VIEWMODEL -->
7: <i:Interaction.Behaviors>
8: <nBehaviors:BridgeCommandBehavior
9: CommandRelay="{StaticResource EMailCustomerCommandRelay}"
10: CommandSourceBinding="{Binding EMailCustomerCommand, Mode=OneWay}"/>
11: </i:Interaction.Behaviors>
Once we have done this, all we need to do is to use the CommandRelay in our DataTemplate as a StaticResource (see below). Also note we are passing the Active DataContext (which would be the Customer Object) as the Parameter to the ICommand:
1: <DataTemplate x:Key="CustomerDataTemplate">
2: <StackPanel>
3: <TextBlock Text="{Binding CustomerName, Mode=OneWay}"/>
4: <Button Content="E-Mail Customer">
5: <i:Interaction.Triggers>
6: <i:EventTrigger EventName="Click">
7: <nBehaviors:ExecuteCommandAction
8: Command="{StaticResource EMailCustomerCommandRelay}"
9: ParameterBinding="{Binding Mode=OneWay}"/>
10: </i:EventTrigger>
11: </i:Interaction.Triggers>
12: </Button>
13: </StackPanel>
14: </DataTemplate>
Now, remember this is all doable in Blend, so it's really quite simple once you get past declaring the relay and bridging the ICommand steps. For our sample scenario discussed above, you can download the Blend Project including the ViewModel part that is consumed by the CommandRelay.
Summary
In summary, I've show how the ICommand related infrastructure in nRoute.Toolkit helps you to define, consume, manage and use ICommands in Silverlight. Further, given how central ICommands are to MVVM we have a feature-rich implementation that helps with both View and ViewModel sides of the divide, and also overcomes most of the platform-level shortcomings in Silverlight.
Download the nRoute.Toolkit from Codeplex
Download the Relay Command Project Source (note, require the toolkit dll)