Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RoutedUICommand vs. ICommand in ViewModel & using InputBinding

Basic ICommand interface implementations, such as DelegateCommand, and RelayCommand, are missing the the InputGestures property contained in the RoutedCommand class. This property supports binding to a KeyGesture, and the Text property in the RoutedUICommand supports setting a control's header. For example:

<MenuItem Header="File">
  <MenuItem Command="Open" />

The result is a menu item labeled: "Open Ctrl + O" under a File menu item. For gestures, InputBindings will map an input guesture to the command, but you loose the InputGestureText support.

How can you keep the simplicity of binding to a view model's ICommands while defining KeyGestures & Text for the commands inside XAML or the view model? For example, I would like a command exposed in a context menu and in the main menu to display the same Header & InputGestureText, as suppored by the RoutedUICommand, but the command's implementation is inside the view model, not inside the Window's code behind.

like image 743
George Tsiokos Avatar asked Feb 03 '12 16:02

George Tsiokos


1 Answers

Looking at MenuItem in reflector, we can see how the MenuItem picks up the Header/InputGesture values, which is:

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;
        // Get appropriate gesture....
    }
    return value;
}

There is similar code to coerce the Header property based on the current command, but in this case it looks for a RoutedUICommand. This tells us, the commands must be an instance of RoutedCommand/RoutedUICommand in order to leverage this feature of MenuItem.

Looking at RoutedCommand in reflector, there is not an easy way to create a DelegateCommand that derives from RoutedCommand, because it's CanExecute/Execute methods are not virtual.

We could code something like:

public class DelegateCommand : RoutedCommand, ICommand
{
    bool ICommand.CanExecute(object parameter) {
        // Insert delegate can execute logic
    }
    void ICommand.Execute(object parameter) {
        // Insert delegate execute logic
    }
}

But this does not prevent the non-explicit CanExecute/Execute methods on RoutedCommand from being called. Which may or may not be an issue.

Alternatively, we can create a custom MenuItem that is smart enough to look for our DelegateCommand (or somewhere else) and use it's text/gestures.

public class MyMenuItem : MenuItem {

    static MyMenuItem() {
        InputGestureTextProperty.OverrideMetadata(typeof(MyMenuItem),
            new FrameworkPropertyMetadata(string.Empty, null, CoerceInputGestureText));
    }

    private static object CoerceInputGestureText(DependencyObject d, object value) {
        MenuItem item = (MenuItem)d;
        var command = item as DelegateCommand;
        if ((string.IsNullOrEmpty((string)value) &&
            DependencyPropertyHelper.GetValueSource(item, InputGestureTextProperty).BaseValueSource == BaseValueSource.Default &&
            command != null) {
            InputGestureCollection inputGestures = command.InputGestures;
            // Get appropriate gesture....
        }

        // Call MenuItem Coerce
        var coerce = InputGestureTextProperty.GetMetadata(typeof(MenuItem)).CoerceValueCallback;
        return coerce(d, value);
    }

}
like image 200
CodeNaked Avatar answered Oct 17 '22 06:10

CodeNaked