Reputation: 1960
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.
Upvotes: 5
Views: 3110
Reputation: 41393
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);
}
}
Upvotes: 6