Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MenuItem keyboard shortcuts in 'pure' MVVM?

All menus/contextmenus/toolbars I use in wpf are declared in ViewModel code pretty much like this:

MenuService.Add( new MenuItem()
  {
    Header = "DoStuff",
    Command = new relayCommand( DoStuff, () => CanDoStuffExecute() )
    // some more properties like parent item/image/...
  } );

The MenuService provides a single binding point which is a hierarchical list of MenuItem and gets bound to the actual Menu's ItemsSource in xaml.

This works very well and now I'd like to add keyboard shortcuts in the same convenient way. Ideally MenuItem would get a property of type System.Windows.Input.KeyGesture so I can simply write

Shortcut = new KeyGesture( Key.D, ModifierKeys.Control )

which would result in the Command of the item being called upon hitting Ctrl+D in the window that owns the menu, and which would also lead to automatically display "Ctrl+D" in the menu.

However I'm lost here: I wanted to set the MenuItem.InputBindings collection via databinding but it is get-only. How can I get items into it anyway? Or is there an MVVM framework that already supports something like this? Most q&a I found on keyboard shortcuts are all about setting the shortcuts through xaml, which is of no help.

Update

Searching for 'relaycommand vs routeduicommand and 'relaycommand keygesture' etc did reveal enough information to come up with a working though hacky solution. There are definitely other and better ways out there, but at the moment this is ultra low priority for me and does the job perfectly. I added two properties to the MenuItem class like this:

//Upon setting a Gesture, the original command is replaced with a RoutedCommand
//since that automatically gives us proper display of the keyboard shortcut.
//The RoutedCommand simply calls back into the original Command.
//It also sets the CommandBinding property, which still has to be added to the
//CommandBindingCollection of either the bound control or one of it ancestors
public InputGesture Gesture
{
  set
  {
    var origCmd = Command;
    if( origCmd == null )
      return;
    var routedCmd = new RoutedCommand( Header,
      typeof( System.Windows.Controls.MenuItem ),
      new InputGestureCollection { value } );
    CommandBinding = new CommandBinding( routedCmd,
      ( sender, args ) => origCmd.Execute( args.Parameter ),
      ( sender, args ) => { args.CanExecute = origCmd.CanExecute( args.Parameter ); } );
    Command = routedCmd;
  }
}

//CommandBinding created when setting Gesture
public CommandBinding CommandBinding { get; private set; }

So this gives the functionality I asked for originally (ie adding keyboard shortcuts in code where they are easily configurable etc). All that is left is to register the commandbindings. At the moment this is done simply by adding all of them to Application.Current.MainWindow.CommandBindings.

like image 808
stijn Avatar asked Dec 23 '11 15:12

stijn


1 Answers

This doesn't actually qualify as an 'answer' (I'm not able to add a comment evidently) - but I'd suggest that what you're doing, is not the intended method in WPF. You're doing this the Windows Forms way (and as in many other toolkits) - defining your UX in code. You got as far as you did, but now you've run into a brick wall: the key gestures are purely UX, definitely not to be specified in code-behind. The appearance (as a function of the view-model), and the user's interaction with it (ways of making a given command happen) are for the XAML definition.

Property values, and Commands are for your view-model, so that you can reuse this view-model for other views, and also easily create unit-tests for it. How would implementing your keyboard shortcuts in the view-model help the testability? And for use in other views, one could argue that the actual shortcuts might not apply to a new view, so that is not where those belong. You may have other reasons of course - but I'd suggest you might consider just defining these in XAML.

-Added, in response to your comment-

You're quite right - and I've seen some rather large WPF UX projects that tried hard to avoid any code-and wound up unnecessarily obtuse. I try to just use whichever approach yields a working result, and is as simple as I can get it.

Here is a sample snippet that simply creates the MenuItem..

<Menu x:Name="miMain" DockPanel.Dock="Top">
    <MenuItem Command="{Binding Path=MyGreatCommand}" Header="DoSomething"/>

That creates the menu. Here, MyGreatCommand is an ICommand, and is simply a property on the view-model. I generally place that within a DockPanel, to handle the StatusBar, etc.

To assign the key gesture..

<Window.InputBindings>
    <KeyBinding Key="X" Modifiers="ALT" Command="{Binding Path=MyGreatCommand}"/>

However, since you mentioned that you've already searched for answers and found only XAML - I assume you've already tried this approach. I have used RoutedUICommands instead of user-defined ICommands, to get that nice right-aligned key-gesture in the header text, but I haven't found how to do both. If you insist upon creating the commands and key-gestures all in code, you may have to create RoutedUICommands.

Why are you wanting to set the key-gestures in other than your XAML?

If you want some menu-items to appear only when certain states hold sway within your view-model, then you can bind the Visibility property of a menu-item (which can contain other menu-items) to Collapsed or Visible.

like image 100
JamesWHurst Avatar answered Sep 22 '22 16:09

JamesWHurst