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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With