Title: Easily bind gestures to controls in WPF and C#
After spending yet another frustrating few hours trying to get WPF command bindings to work, I decided to write a method to make the process a bit easier.
You can see the basic technique in the post Use custom command bindings in WPF and C#.
The program first defines this class to define command bindings.
// Define a gesture's keys and gesture string.
private class GestureInfo
{
internal Key Key;
internal ModifierKeys Modifier;
internal string Gesture;
internal GestureInfo(Key key, ModifierKeys modifier, string gesture)
{
Key = key;
Modifier = modifier;
Gesture = gesture;
}
}
The following DefineCommand method then uses GestureInfo objects to bind gestures to commands.
// Define one command's gesture(s).
private void DefineCommand(string name, string text,
UIElement owner, GestureInfo[] gestures,
CanExecuteRoutedEventHandler can_execute,
ExecutedRoutedEventHandler executed,
object[] controls)
{
// Create the gesture.
InputGestureCollection gesture_collection = new InputGestureCollection();
foreach (GestureInfo info in gestures)
{
gesture_collection.Add(new KeyGesture(info.Key, info.Modifier, info.Gesture));
}
// Create the command.
RoutedUICommand command = new RoutedUICommand(text, name,
owner.GetType(), gesture_collection);
// Bind the command to its event handlers.
CommandBinding binding = new CommandBinding(command);
binding.CanExecute += can_execute;
binding.Executed += executed;
owner.CommandBindings.Add(binding);
// Set Command properties for the command control(s).
foreach (object control in controls)
{
PropertyInfo property_info = control.GetType().GetProperty("Command");
property_info.SetValue(control, command, null);
}
}
The method first creates an InputGestureCollection. It then adds each of the gestures definitions to the collection.
The method then creates a RoutedUICommand object. It makes a CommandBinding for the command and assigns the binding's CanExecute and Executed event handlers. It then adds the binding to the owner object's CommandBindings collection. (Normally the owner object is the Window.)
The code finishes by looping through the menus, buttons, and other controls that should trigger the command. Unfortunately, the kinds of objects that trigger commands do not have a common root (at least not that I can figure out), so they are declared as generic objects and thus don't have a Command property. To work around that, the code uses reflection to get the object's Command property info and then sets that property's value.
If this all seems mysterious and complicated, you're right. It's very much in keeping with WPF's unofficial slogan, "Twice as flexible and only five times as hard!"
Fortunately, once you've dfined the GestureInfo class and the DefineCommand method, using them is relatively easy.
Here's the progam's XAML code.
<DockPanel>
<Menu Height="22" Margin="2" DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="Say Hi" Name="mnuFileSayHi"/>
<Separator />
<MenuItem Name="mnuFileExit" Header="_Exit"
Click="mnuFileExit_Click" />
</MenuItem>
</Menu>
<StackPanel>
<CheckBox Name="chkEnabled" Content="Enable Command" Margin="10"/>
<Button Name="btnSayHi" Content="Say Hi" Width="70" Height="30"/>
</StackPanel>
</DockPanel>
The key controls are:
- mnuFileSayHi - A menu item that will trigger our command.
- btnSayHi - A button that will trigger the same command.
- chkEnabled - A check box that enables and disables the command.
When the program starts, the following code performs the command binding.
// Bind Ctrl+H and Ctrl+B to the Say Hi menu item and button.
// Note that a menu item displays only the first gesture as a shortcut.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
GestureInfo[] gesture_list =
{
new GestureInfo(Key.H, ModifierKeys.Control, "Ctrl+H"),
new GestureInfo(Key.B, ModifierKeys.Control, "Ctrl+B"),
};
object[] controls = { mnuFileSayHi, btnSayHi, };
DefineCommand("SayHiCommand", "Display a message box that says Hi.",
this, gesture_list, SayHiCommand_CanExecute, SayHiCommand_Executed,
controls);
}
This code creates two GestureInfo objects that bind the gestures "Ctrl+H" and "Ctrl+B". Next, it creates an array holding the menu item and button controls that will use the command.
This code finishes by calling DefineCommand to bind the two gestures to a command named SayHiCommand.
Note that the menu item only displays first gesture as a shortcut but both gestures will trigger the command.
The command uses the SayHiCommand_CanExecute method (shown shortly) to determine whether the command is enabled. When the command fires, it executes the SayHiCommand_Executed method (also shown shortly).
The last argument to DefineCommand is the array holding the menu item and button that should use the command.
The following code shows the SayHiCommand_CanExecute and SayHiCommand_Executed methods.
private void SayHiCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Allow execution if IsChecked is neither false nor indeterminate.
e.CanExecute = !(chkEnabled.IsChecked ?? false);
}
private void SayHiCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Hi");
}
After all that setup, these callbacks are relatively straightforward.
Download the example to experiment with it and to see additional details.
|