Home > Commanding > Commands and InputGestures

Commands and InputGestures


When developing applications with the MVVM design pattern, usually the actions a user can take are expressed through commands, objects that implement the ICommand interface, that are exposed as properties on the ViewModel. WPF supports binding to commands in MenuItems and Buttons, so this seems like a convenient and clean way to connect UI and ViewModel.

However, in WPF, commands can do more than just call a method on a ViewModel. WPF supports command routing, i.e. the ability to redirect a command to a specific implementation that might be connected to the control that has keyboard focus at the moment. Also, WPF supports binding InputGestures like mouse clicks or keyboard shortcuts to commands. Finally, WPF defines a set of standard application commands, which can  be used to define standard behaviour of controls independent of the final application which uses them. E.g. there is a Paste command that is mapped by default to the CTRL+V keyboard shortcut, and can be handled by TextBoxes and RichTextBoxes with built-in behaviour, so that an application using a RichTextBox offers this behaviour by default.

In order to take advantage of these WPF features, the following piece of WPFGlue defines a class that maps an ICommand implementation from a ViewModel to a WPF RoutedUICommand, thus being able to use command routing and default KeyGesture bindings:

Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.Register("Command", GetType(RoutedCommand), GetType(CommandSetCommandBase))
Public Property Command() As RoutedCommand
    Get
        Return GetValue(CommandProperty)
    End Get
    Set(ByVal value As RoutedCommand)
        SetValue(CommandProperty, value)
    End Set
End Property

Public Shared ReadOnly ImplementationProperty As DependencyProperty = DependencyProperty.Register("Implementation", GetType(ICommand), GetType(CommandSetCommand))
Public Property Implementation() As ICommand
    Get
        Return GetValue(ImplementationProperty)
    End Get
    Set(ByVal value As ICommand)
        SetValue(ImplementationProperty, value)
    End Set
End Property

Protected Overrides Sub OnCanExecute(ByVal sender As Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
    If Implementation Is Nothing Then
        e.Handled = False
    Else
        e.CanExecute = Implementation.CanExecute(e.Parameter)
        e.Handled = True
    End If
End Sub

Protected Overrides Sub OnExecuted(ByVal sender As Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
    If Implementation Is Nothing Then
        e.Handled = False
    Else
        Implementation.Execute(e.Parameter)
        e.Handled = True
    End If
End Sub

Private Sub OnCanExecuteChanged(ByVal sender As Object, ByVal e As EventArgs)
    If sender Is Implementation Then
        CommandManager.InvalidateRequerySuggested()
    End If
End Sub

Public Overrides Sub Attach(ByVal target As System.Windows.FrameworkElement)
    SetCommandProperties()
    If Implementation IsNot Nothing Then
        MyBase.Attach(target)
    End If
End Sub

 

This code is supposed to be used with the Sticky Command pattern. I will publish the complete class and the ViewModelKit it belongs to at a later stage. For now, it may be enough to see that the handlers for the RoutedCommand’s events delegate to the ICommand’s implementation.

In XAML, the class would be used like this:

<Window.Resources>
    <c:CommandSetCommand x:Key="OpenCommand" Command="Open" Implementation="{Binding Source={StaticResource FileManager}, Path=OpenCommand}"/>
</Window.Resources>

What is the advantage? Now, the ViewModel’s OpenCommand is connected to the semantics of the ApplicationCommands.Open command, it is available through this command’s keyboard shortcut, and it can participate in command routing, which basically means that every input element on the page that can receive keyboard input or has a Command property can trigger this command without more bindings, like the following MenuItem:

<MenuItem Command="Open"/>

 

Because ApplicationCommands.Open is a RoutedUICommand and has default values for its Text and InputGestures properties, this will display like this (on a computer with German Windows):

OpenMenuItemScreenShot

But this is not all.

Changing Default Keyboard Shortcuts

In the Attach method of the CommandSetCommand, a SetCommandProperties method is called. It is the job of this method to change properties on the RoutedCommand which can be configured through more properties of the CommandSetCommand:

Protected Overridable Sub SetCommandProperties()
    If Command IsNot Nothing Then
        If KeyGesture IsNot Nothing Then
            Command.InputGestures.Clear()
            Command.InputGestures.Add(KeyGesture)
        End If
        If TypeOf Command Is RoutedUICommand And Not String.IsNullOrEmpty(Text) Then
            DirectCast(Command, RoutedUICommand).Text = Text
        End If
    End If
End Sub

 

This is enough to enable overriding of the default keyboard shortcuts of the commands defined in ApplicationCommands at load time, when the command is bound to the Window or other Framework element. If you want to be able to change the keyboard shortcut at runtime, you need to do something when KeyGesture is changed:

Public Shared ReadOnly KeyGestureProperty As DependencyProperty = DependencyProperty.Register("KeyGesture", GetType(KeyGesture), GetType(CommandSetCommandBase), New PropertyMetadata(AddressOf onkeygesturechanged))
Public Property KeyGesture() As KeyGesture
    Get
        Return GetValue(KeyGestureProperty)
    End Get
    Set(ByVal value As KeyGesture)
        SetValue(KeyGestureProperty, value)
    End Set
End Property
Private Shared Sub OnKeyGestureChanged(ByVal d As CommandSetCommandBase, ByVal e As DependencyPropertyChangedEventArgs)
    Dim c As RoutedCommand = TryCast(d.Command, RoutedCommand)
    If c IsNot Nothing Then
        c.InputGestures.Clear()
        If e.NewValue IsNot Nothing Then
            c.InputGestures.Add(e.NewValue)
        End If
        d.SetValue(CommandProperty, Nothing)
        d.SetValue(CommandProperty, c)
    End If
End Sub

This code will update the RoutedCommand’s InputGestures and will connect it to the new keyboard shortcut whenever KeyGesture is changed, which allows it to redefine keyboard shortcuts at runtime, both for standard application commands and for custom RoutedCommands.

This would be the right place to say that explicit KeyBindings on elements within a page should be avoided, because their scope is always local to the element on which they are defined. If this is not explicitly intended, it is easier to maintain consistent keyboard bindings throughout the application if the RoutedCommands InputGestures are used.

However, there is one problem: MenuItems use their command’s InputGestures collection to display a keyboard shortcut when they first load, but they don’t monitor it for changes. Therefore, the OnKeyGestureChanged method sets the Command property to nothing and then back to its old value. This causes MenuItems which are binding their Command property to the CommandSetCommand’s Command property to update both their Header and their InputGestureText properties. The MenuItem would look like this to support this functionality:

<MenuItem Command="{Binding Source={StaticResource
    PasteCommand}, Path=Command}"/>

 

Overriding Class CommandBindings

Have you ever tried to override the default paste command in a RichTextBox? It’s not so easy to get around this behaviour if you want to use the keyboard shortcut CTRL+V for something else, or want to do checks on the clipboard contents before you paste them. Clearing the InputGestures collection of ApplicationCommands.Paste does the trick, without resorting to dirty tricks like intercepting keystrokes in the PreviewKeyDown event. Once you have got the class CommandBinding out of the way, you can define a custom RoutedCommand, its CommandBindings, and InputBindings with the original Ctrl+V keystroke wherever you need them.

To Be Continued…

In posts to come, I will introduce the CommandSet ViewModelKit, which will allow it to define the complete set of commands for an application in one place, including the functionality introduced here, and other things like connecting RoutedCommands directly to method calls in the ViewModel.

Advertisements
  1. July 3, 2012 at 22:59

    I stumbled across your blog while looking for WPF internationalization resources and I’m quite impressed. But I do have one question that you don’t seem to have covered: what’s a good way to have localized input bindings for commands in a WPF application? I can think of a few ways to do this, but it seems like you’ve been doing this a bit longer than me and I was wondering if you had any feedback on this topic.

    • July 5, 2012 at 14:48

      Hi,

      generally, the solution would involve a localized string which defines the shortcut, and an object which creates a KeyGesture from the string and binds it either to a control or to the RoutedCommand’s InputGestures collection. I’d probably go for the InputGestures collection, since that gives you the same InputGesture across the whole application. I’ve experimented with this kind of thing, but I haven’t decided for a particular solution, yet. First I tried to create a CommandDefinition tag which you could use in Resources, but that gave me trouble with making sure that I had always the same instance of a RoutedCommand. Also, in the Sticky Command Pattern, you find a way to set an InputBinding for a Command on a Control, but I’d prefer a solution which allows to configure the InputGestures in one place. Probably the easiest way would be to extend the Command class from the Commanding posts by a member that internally manages the InputGestures collection of the associated RoutedCommand, the same way it is done in the post on the Sticky Command pattern.

      Hope that helps…

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: