Keyboard shortcuts in code using WPF MVVM and Caliburn Micro

Recently I had to replace a keyboard-shortcut solution in a WPF application where the existing solution used a global hook using unmanaged code. My first tryout was using the ComponentDispatcher. At first, it seemed ok, but sometimes commands wasn’t intercepted and sometimes modifier-keys wasn’t cleared from the buffer. Lets just say “there were problems”. I then turned to simple InputBindings. And since the application used Caliburn Micro, it was quite easy:

Step 1 – Hook up

Extend Screen in a custom ViewModelBase and override OnViewLoaded, extract a Window from the view being loaded and hook up KeyBindings to its InputBindings-collection.

protected override void OnViewLoaded(object view)
{
	base.OnViewLoaded(view);

	var window = (view as FrameworkElement).GetWindow();
	_inputBindings = new InputBindings(window);
	_inputBindings.RegisterCommands(GetInputBindingCommands());
}

In the same ViewModelBase, add a method to return InputBindingCommands that each view model should implement.

protected virtual IEnumerable<InputBindingCommand> GetInputBindingCommands()
{
	yield break;
}

To get hold of the Window (which is where we wan’t to add our input bindings) we just use some traditional recursion to traverse the tree:

{
	public static Window GetWindow(this FrameworkElement element)
	{
		if (element == null)
			return null;

		if (element is Window)
			return (Window)element;

		if (element.Parent == null)
			return null;

		return GetWindow(element.Parent as FrameworkElement);
	}
}

To support overriding previously registered bindings we need to keep track of the input bindings associated with the current view, and to reverse the registration process we just have to stash them away in a stack.

public class InputBindings
{
	private readonly InputBindingCollection _inputBindings;
	private readonly Stack<KeyBinding> _stash;

	public InputBindings(Window bindingsOwner)
	{
		_inputBindings = bindingsOwner.InputBindings;
		_stash = new Stack<KeyBinding>();
	}

	public void RegisterCommands(IEnumerable<InputBindingCommand> inputBindingCommands)
	{
		foreach (var inputBindingCommand in inputBindingCommands)
		{
			var binding = new KeyBinding(inputBindingCommand, inputBindingCommand.GestureKey, inputBindingCommand.GestureModifier);

			_stash.Push(binding);
			_inputBindings.Add(binding);
		}
	}

	public void DeregisterCommands()
	{
		if (_inputBindings == null)
			return;

		foreach (var keyBinding in _stash)
			_inputBindings.Remove(keyBinding);
	}
}

Step 2 – Specify commands

Easy, just override the GetInputBindingCommands method in each ViewModel you want to provide key-commands:

protected override IEnumerable<InputBindingCommand> GetInputBindingCommands()
{
	yield return new InputBindingCommand(NewWindow)
	{
		GestureModifier = ModifierKeys.Control,
		GestureKey = Key.N
	};
}

Step 3 – Clean up

The last step is to cleanup when a view is being deactivated. In the ViewModelBase, just add:

protected override void OnDeactivate(bool close)
{
	base.OnDeactivate(close);

	_inputBindings.DeregisterCommands();
}

The DeregisterCommands method will remove the inputbindings that are associated with the view and added to the window.

Wrap up

Well, I will not show more code. There’s a simple sample app available here: https://github.com/danielwertheim/InputBindingCommand-Lab

//Daniel