Thursday, February 6, 2014

Routed Commands

When I first came across Routed Commands I thought it was cool but didn't really make my life any easier. I was wrong - they are a good way to achieve very desirable functionality.

Basically a Routed Command is a way to define executable code and then bind controls to the command instead of directly to the executable code. This gives us two features. We can consistently control whether a command is available or not and also consistently define how the code is executed.

WPF predefines several Routed Commands. You should ignore them and define your own. It's easy.

Let's assume we want to create an Edit Command. It's on a search screen and it's only available if the user has exactly one search result highlighted. We want to edit the currently selected document if the user double-clicks a row in the search results grid or if they click an [Edit] button.

Start by defining our new EditCommand. Under the window resources add the new command.

<Window.Resources>
    <RoutedUICommand x:Key="EditCommand" Text="Edit">
</Window.Resources>

Now we define how we decide whether the command is available or not and how to execute it.
<Window.CommandBindings>
    <CommandBinding Command="{StaticResource EditCommand}" CanExecute="EditCanExecute" Executed="EditExecuted"/>
</Window.CommandBindings>

Now we need to define two methods. The first is called EditCanExecute and returns CanExecute=true if the Edit Command should be available to the user. If it is not, the [Edit] button will automatically be disabled and the double-click on the search results will do nothing.

Protected Sub EditCanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
    If SearchResultsDataGrid.HasItems AndAlso SearchResultsDataGrid.SelectedItems.Count = 1 Then
        e.CanExecute = True
    Else
        e.CanExecute = False
    End If
    e.Handled = True
End Sub

Protected Sub EditExecuted(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
    ' Do whatever is needed to start editing the currently selected item

    DoEdit(SearchResultsDataGrid.SelectedItem)
    e.Handled = True
End Sub

Finally we need to attach the command to the [Edit] button and the double-click gesture on SearchResultsDataGrid. The button is easiest, you just add a Command attribute in the XAML. Note that a Click attribute is no longer needed.

<Button Name="EditButton" Width="100" Command="{StaticResource EditCommand}" Content="Edit"/>
The DataGrid can have a set of input bindings that are declared in XAML like this...
<DataGrid.InputBindings>
    <MouseBinding Gesture="LeftDoubleClick" Command="{StaticResource EditCommand}"/>
</DataGrid.InputBindings>

When EditCanExecute returns e.CanExecute=False all the attached controls are disabled (Gestures cannot be disabled, obviously). When an attached control is clicked or gesture is executed, the EditExecuted method is called. Routed command handlers always receive the same set of parameters which makes writing the event handlers simpler.

One more thing RoutedCommands gives us is the ability to consistently label the control that use them. If you notice, the declaration of the RoutedCommand has a Text attribute. This can be used to populate the content of buttons that use the command as shown below or you can style it to avoid all the repetition.

<Button Name="EditButton" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>


The advantage to this approach is that I don't have to find the 20 different ways that the availability of the edit command can be changed. Also, if the requirements change so that another control can execute the edit function, I just set its Command parameter to {StaticResource EditCommand} and everything else is taken care of.

The downside is that the CanExecute method is polled frequently (pretty much whenever the user does something) so it has to be fast. If this is an expensive decision to make, perhaps requiring a slow WCF call, this might not be the ideal solution.

No comments:

Post a Comment