The problem
When using the Model-View-ViewModel pattern best practice is currently to expose properties of type ICommand from the ViewModel and map them to the Command property on Buttons, MenuItems etc. in the view. Using this approach is considered better than using RoutedCommands but we loose InputBindings becuase there is no ICommand.InputBindings like there is RoutedCommand.InputBindings. Instead we have to map KeyBindings at some level to the commands, for example at the Window or UserControl level. I tried to do this in XAML like this:
<Window>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=SetViewCommand}"
CommandParameter="Right" Key="Ctrl+R" />
<KeyBinding Command="{Binding Path=SetViewCommand}"
CommandParameter="Left" Key="Ctrl+L" />
</Window.InputBindings>
</Window>
This syntax seemed so natural to me that I intuitively tried it without thinking too much but it didn’t work. There must be some other simple solution I thought and started searching. Oh boy, was I wrong… Below is my tale of researching this issue.
Why it does not work
I came across this post where this issue is discussed by people who understand WPF far better than me. It seems that the problem is two-fold:
- The KeyBinding.Command is not a DependencyProperty so it does not support data binding.
- Even if it was a DependencyProperty the KeyBinding is not part of the WPF element tree and therefore has no access to the DataContext (which is usually set to the ViewModel that exposes the ICommand we want to bind to).
The solution …err.. I mean the work-arounds
I have found two work-around solutions worth mentioning:
- This solution uses a “virtual branch“. But the XAML syntax gets very complex.
- Here is another solution that I like better. It uses a markup extension and the XAML syntax gets a litte bit less complicated.
But I didn’t like the work-arounds enough to use them. Mainly because advanced concepts such as virtual branches and markup extensions are not likely to be easily understood by all developers on a team and certainly not by the maintainers that would have to figure out how it all works later on.
RoutedCommand you have InputBinding… Why will you not be my friend?
This is where I turned back to look at RoutedCommand again because it actually has an InputBindings collection built-in. I knew that exposing RoutedCommands in ViewModel were considered bad and we should use ICommand instead but I did not fully understand why. So I read this article that explains a lot about RoutedCommand. In the section “Routed Command Challenges” it also explains why RoutedCommands only complicates things for the M-V-VM pattern.
I don’t fully understand it all but it seems like one thing that makes RoutedCommands complicate things is that they are hard-wired to trigger things according to which control that have focus in the UI and then bubble up from that control. When we have many controls working together in a Window it then becomes hard to predict which handler the RoutedCommand will invoke. In contrast, mapping a Button.Command property to an ICommand on the ViewModel we get a very clear and direct understanding of what command handler will be invoked (the bound ICommand’s Execute method of course). Also since I am having a hard time fully understanding all implications of RoutedCommands they cannot be any good ;-).
So the problem is in the framework classes? Then why don’t you rewrite the framework…
Looking further at the badness of RoutedComands I also found this interesting post where the whole idea behind commanding in WPF is being rethought. I don’t understand it all so maybe I have gotten the wrong idea but I think what is proposed is something like this:
- RoutedCommands and CommandBinding classes should be obsoleted so we work only with ICommand.
- Instead of RoutedCommand there would be VerbGesture. A VerbGesture would be an abstract representation of something that can be invoked in the application like Save, Cut, Close etc. It would be treated as the other InputGestures so we would have KeyGesture, MouseGesture, and VerbGesture. Because of this a VerbGesture would be routed like other InputGestures.
- The VerbGestures could be added to a control’s InputBindings so we would have KeyBinding, MouseBinding and VerbBinding. This way we could bind a VerbGesture to an ICommand.
- VerbGestures could be mapped to other InputGestures so we could map a KeyGesture to a VerbGesture. For example pressing a key would trigger the KeyGesture which in turn would trigger the VerbGesture.
- When a VerbGesture is triggered an associated VerbBinding is sought and when it is found it’s associated ICommand is executed. So it would work just like KeyGesture and MouseGesture.
This seems nice to me and I hope Microsoft implements something like this in the future. Anyway it seems like the consensus is that RoutedCommands complicate things so the thing I take away from all this is that I should avoid RoutedCommands and CommandBinding altogether. If I only knew this before it could have saved me a lot of reading up on how RoutedCommand works ;-).
Oh well, the framework rewrite would take me too long… Looks like it would have to be code-behind then..
But now back to present time. The way I solve this in my current project is to add the InputBindings in the code-behind file. It is not as nice as to have it in XAML but on the other hand it does not require advanced concepts like virtual branch or markup extensions. So to emulate the XAML in the beginning of this post I would have this for a view called MyView in the code-behind file MyView.xaml.vb:
Partial Public Class MyView
Public Sub New(ByVal viewModel As MyViewModel)
'This call is required by the Windows Form Designer.
InitializeComponent()
.. Other code ...
'Create KeyBindings so the menus have shortcuts
CreateKeyBinding(_viewModel.SetViewCommand, "Right",
Key.R, ModifierKeys.Control)
CreateKeyBinding(_viewModel.SetViewCommand, "Left",
Key.L, ModifierKeys.Control)
End Sub
Protected Sub CreateKeyBinding(ByVal command As ICommand,
ByVal commandParameter As Object,
ByVal gestureKey As Key, ByVal modifiers As ModifierKeys)
Dim kb As New KeyBinding(command, gestureKey, ModifierKeys.Control)
kb.CommandParameter = commandParameter
InputBindings.Add(kb)
End Sub
End Class
One thing that gets me is the fact that I have to add InputGestureText to each MenuItem like this.
<MenuItem Header="Right" Command="{Binding Path=SetViewCommand}"
CommandParameter="Right" InputGestureText="Ctrl+R" />
<MenuItem Header="Left" Command="{Binding Path=SetViewCommand}"
CommandParameter="Left" InputGestureText="Ctrl+L" />
The InputGestureText property is just a string that gets displayed to the right of the MenuItem and has nothing to do with mapping to keyboard shortcuts as it had in earlier GUI platforms such as WinForms. Now imagine what would happen if we change the KeyBinding’s KeyGesture in the code-behind file from Ctrl+R to Ctrl+W. The MenuItem.InputGestureText would still display Ctrl+R to the user but this keyboard shortcut would no longer trigger the ICommand in the MenuItem.Command property because it is now bound to the Ctrl+W KeyGesture.
RoutedCommand you are not my friend but you give InputGestureText a meaning…
When a MenuItem’s Command property is set to a RoutedCommand the InputGestureText is “automatically” set. I checked the code for this in the MenuItem class with Reflector and it actually tries to cast the Command property to RoutedCommand and if it succeds then it reads the InputBindings and uses the first KeyBinding it finds to set the InputGestureText:
private static object CoerceInputGestureText(DependencyObject d, object value)
{
RoutedCommand command;
MenuItem item = (MenuItem) d;
if ((string.IsNullOrEmpty((string) value)
&& !item.HasNonDefaultValue(InputGestureTextProperty))
&& ((command = item.Command as RoutedCommand) != null))
{
InputGestureCollection inputGestures = command.InputGestures;
if ((inputGestures == null) || (inputGestures.Count < 1))
{
return value;
}
for (int i = 0; i < inputGestures.Count; i++)
{
KeyGesture gesture = ((IList) inputGestures)[i] as KeyGesture;
if (gesture != null)
{
return gesture.GetDisplayStringForCulture(CultureInfo.CurrentCulture);
}
}
}
return value;
}
[/sourcecode]
Too bad that it casts to a concrete implementation instead of an interface. This makes the MenuItem class tightly coupled to the RoutedCommand class. If instead it would cast to an interface like IInputGestureTextProvider we could provide our own implementation that could look for a parent KeyBinding in the element tree or something like that. Now there is nothing I can do to make MenuItem sync it's InputGestureText with the KeyBindings that are mapped to my ICommands? Perhaps I could make my DelegateCommand inherit from RoutedCommand and then shadow all the methods and properties including the InputBindings collection. It does not seem like a clean solution though so for now I suppose I have to keep my InputGestureTexts in sync with the KeyBindings manually. But I hope I will have time to get back to this later and find a better solution.