WPF Behavior: Global Application Shortcut Keys

WPF Support

WPF provides excellent support for shortcut keys:

  • The InputBinding class supports Key, Mouse, and recently Touch gestures.
  • An InputBinding can be associated with any UIElement using the InputBindings property.
  • Custom controls that handle RoutedCommands can associate InputBindings using the CommandManager.RegisterClassInputBinding.
  • The  CommandManager provides tunneling and bubbling of the commands allowing any control with a CommandBinding to handle the command.
  • The static ApplicationCommands, ComponentCommands, and NavigationCommands provides commands that can be reused to implement common functionality.  These come with a converter allowing you to just name the command as a string.
  • Controls like Button expose Command and CommandParameter dependency properties that make commands a first-class citizen in WPF.

Pretty great stuff.

Some problems

All is well – right up to the point where you want to have a shortcut key that works across your application, regardless of input focus.  You will quickly find that everything about the CommandManager wants to route starting at input focus and the entire tunneling/bubbling strategy will fight you tooth-and-nail.

I found some examples on the web where folks added Window.InputBindings on their main window and implement the commands on the main window.  This works because the main window is pretty much always in the routed command chain.  This is great for a trivial application, but not an application where the main window contains several different views that may be shown/hidden.  It is especially difficult when the UI is composed from a set of custom control libraries, or composed at run-time.

Solution Goals

I wanted the following from a global shortcut key solution:

  • Focus independent – Key gestures to work regardless of where the focus is.
  • IsVisible aware – If a control is hidden, then the shortcut key doesn’t invoke that command.
  • Follows standards – The command and input binding infrastructure of WPF is leveraged.
  • Distributed bindings – Each control associates their commands with application-wide shortcut key gestures.  This is similar to how a button binds to a command, or a textbox binds Ctrl+X, Ctrl+C, Ctrl+V to global commands.
  • Low impact – Because examining keystrokes for application-wide shortcut keys likely looks at every keystroke, the key down handler needs to be considerate of how much time and memory it consumes.
  • Thread-safe – Key gestures and commands should work when multiple windows are open.
  • Support declarative and imperative bindings – Key gestures can be bound in XAML or from code.

A Solution: RegisterAppShortcutKeysBehavior

RegisterAppShortKeysBehavior is a standard System.Windows.Interactivity.Behavior<T> that can be attached to any control or control template. The behavior encapsulates all functionality required to support global shortcut keys.

In the following example the command is a static RoutedCommand property on the ChangeColorControl.

<local:RegisterAppShortcutKeysBehavior>
  <local:RegisterAppShortcutKeysBehavior.InputBindings>
    <KeyBinding Command="{x:Static local:ChangeColorControl.ChangeColor}" Gesture="CTRL+g"/>
  </local:RegisterAppShortcutKeysBehavior.InputBindings>
</local:RegisterAppShortcutKeysBehavior>

As well, controls can call static Register and Unregister methods in code.  When a control is unloaded, it is automatically unregistered.

public static void Register(InputBindingCollection inputBindings, FrameworkElement commandTarget)
public static void Unregister(FrameworkElement commandTarget)

Running the Behavior

You can download the behavior as part of a sample at: http://www.bellacode.com/code/GlobalShortcutsSample.zip.

Open the solution in VS 2010 and run it (F5).

In this sample, there is a custom control that contains a command to randomly change its background color.  The generic style for the control contains a button mapped to this command that you can click and see the color change.  As well, the style uses the behavior to map a global shortcut key of Ctrl+G.

On the main window there are two instances of the control.  You can show/hide each individual control to see how the command is only executed when the control is visible.

How it works

1.  In the OnAttached overridden  method:

  • The InputBindings are added to a static  list and the control’s parent window is added to a static list.
  • The AssociatedObject of the Behavior base class is set as the command target for each InputBinding.  This allows the right control’s command to be invoked when the global shortcut  is pressed.
  • The behavior subscribes to each window’s PreviewKeyDown event.

2. In the PreviewKeyDown event handler:

  • In this event, the KeyEventArgs and keyboard state are matched against the static list of input bindings.  For each match,  the command is invoked.
  • The command is only invoked  when there is a modifier key down (Ctrl, Alt, or Shift).  This prevent performance issues from doing too much work on every keystroke the user types.
  • The command is only invoked when the control is visible.  This allows applications that show/hide views to automatically target the active view without having to complicate their CanExecute logic.
  • If a command is invoked, then e.Handled is set to true.

3.  In the OnDetaching overridden method:

  • During OnDetaching, the InputBindings and parent window are removed from their lists.

Some other details of this behavior:

  • The static lists are locked before they are read or modified to prevent windows running different message pumps on different threads to have an access violation.  Most UI is single-threaded, but I followed the practices of the CommandManager class here.
  • To improve performance of the PreviewKeyDown handler, I keep a copy of the InputBindings in an array that I acquire before looping.  This holds the lock for the shortest period of time and limits memory pressure to a single copy.
  • The parent window list keeps track of each control in order to “reference count”.  This prevents a window from being registered twice or removed too  early when multiple on controls in the same window register global shortcut keys.
  • In the case of using the static Register method:  If the window for control doesn’t exist, then the window registration is delayed until the control is loaded.



3 Comments

  • Hi Geoff,

    Currently I am developing an application using WPF + MVVM and I am new in this technologies. I need to bind input keys (Ctrl + N, Ctrl + E and Del) to all user controls which are loaded in “TabMDI” interface. For implementing input binding with user control I found your solution, It is excellent. But in my case I am using “DelegateCommand – ICommand” in place of “RoutedCommand” so is it necessary to use “RoutedCommand” for implementing input binding using “RegisterAppShortcutKeysBehavior” and registering command using “CommandManager”. Please guide me how can I implement “RegisterAppShortcutKeysBehavior” class in WPF + MVVM pattern.

    Thanks for your help 🙂

  • dave says:

    Hi, the Link for the example and source is broken. Can you re-upload the exmaple ?
    Thanks!

  • tj says:

    The Behavior can be found in his GitHub at:

    https://github.com/BellaCode/MVVM

    Unfortunately it is not the same sample as in this article, but there is a sample included.

Leave a Reply