Title: Use pre-defined command bindings in WPF and C#
Command bindings provide yet another demonstration of the unofficial WPF slogan: twice as flexible and only 10 times as hard. The idea is a good one: Connect all of the program's methods for invoking a command to a single point of control. That point of control can then enable and disable all of the user interface elements that should perform the action.
For example, the Allow New check box controls the New command. The New Document and Make New buttons and the New menu item are all enabled and disabled when the command is enabled or disabled.
In theory a central point of control for each command makes it easier to manage any number of buttons, menu items, keyboard shortcuts, and other methods for invoking the command. In practice it's all pretty confusing.
To control a command, you need to associate it with a RoutedUICommand object. That object basically serves as the name of the command.
You can define your own objects to create new commands or you can use commands defined by the System.Windows.Input.ApplicationCommands class. That class defines the following commands.
- CancelPrint
- Close
- ContextMenu
- Copy
- CorrectionList
- Cut
- Delete
- Find
- Help
- New
- NotACommand
- Open
- Paste
- Print
- PrintPreview
- Properties
- Redo
- Replace
- Save
- SaveAs
- SelectAll
- Stop
- Undo
This example demonstrates two pre-defined commands: New and Open. Each is associated with two buttons, a menu item, and a keyboard shortcut (Ctrl+N and Ctrl+O). The check boxes enable and disable the commands.
To make a command work, you need to associate it with two event handlers: CanExecute and Executed. The command uses the CanExecute event handler to determine whether it should enable or disable the user interface elements associated with it. In this example, that determines whether the buttons and menu items are enabled.
Aside: By default WPF doesn't make it very obvious that a button or menu item is disabled. If you look very closely, you can see that the text is slightly grayer, but I didn't think that was a big enough cue, so I changed the styles of the example's buttons and menu items to make it more obvious. The example uses the following code XAML code to define the styles.
<DockPanel.Resources>
<Style TargetType="MenuItem">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Width" Value="120"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5"/>
</Trigger>
</Style.Triggers>
</Style>
</DockPanel.Resources>
The two Triggers sections shown in blue are what changes the way the program displays disabled buttons and menu items. When the IsEnabled property is False, the triggers set the controls' Opacity properties to 0.5 so they are half transparent. That makes them look significantly grayer than the enabled controls so it's more obvious that they are disabled.
|
Okay, back to commands. There are two ways you can associate a command object with its CanExecute and Executed event handlers: XAML code and C# code.
This example uses the following XAML code to make the association for the Open command.
<Window.CommandBindings>
<CommandBinding
Command="ApplicationCommands.Open"
Executed="OpenBinding_Executed"
CanExecute="OpenBinding_CanExecute"/>
</Window.CommandBindings>
This code belongs to the Window so it goes after the opening Window tag before the main DockPanel control.
The program uses the following C# code to make the association for the New command.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Bind the New command to its event handlers.
CommandBinding new_binding =
new CommandBinding(ApplicationCommands.New);
new_binding.Executed += NewBinding_Executed;
new_binding.CanExecute += NewBinding_CanExecute;
this.CommandBindings.Add(new_binding);
}
This code creates a new ApplicationCommands.New object to represent the command. It then uses the += syntax to attach the event handlers. When it's finished, it adds the ApplicationCommands.New object to the window's CommandBindings collection.
The following code shows the New command's event handlers.
private void NewBinding_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
if (!IsLoaded) return;
e.CanExecute = chkAllowNew.IsChecked.Value;
}
private void NewBinding_Executed(object sender,
ExecutedRoutedEventArgs e)
{
MessageBox.Show("Create a new document", "New");
}
The CanExecute event handler first checks IsLoaded and returns if the window isn't yet loaded. That prevents it from trying to access the Allow New check box before it is ready. If the window is loaded, the event handler returns true if the Allow New check box is checked.
The Executed event handler should take whatever action is appropriate for the command. In this example it simply displays a message box.
The CanExecute and Executed event handlers for the Open command are similar so they aren't shown here.
The last thing you need to do is associate the buttons, menu items, and other user interface elements with the commands. The following code shows how the program associates the New menu item with the New command.
<MenuItem Header="New" Command="ApplicationCommands.New"/>
The following code shows how the program associates the New Document button with the New command.
<Button Grid.Row="0" Grid.Column="0"
Content="New Document" Command="ApplicationCommands.New"/>
In summary, to use one of WPF's pre-defined commands you:
- Create the command object.
- Associate the object with its CanExecute and Executed event handlers.
- Define the CanExecute and Executed event handlers.
- Associate the buttons, menu items, and other user interface elements with the commands by setting their Command properties in XAML code.
You can perform the first two steps either in XAML code (by using Window.CommandBindings) or in C# code. (You can probably do the last step in C# code, too, but it's a lot easier in XAML code so why bother?)
You may have noticed that I never mentioned how to create the keyboard shortcuts Ctrl+N and Ctrl+O. Those are assigned to the pre-defined New and Open commands by default so you don't need to do anything to make them work.
In my next post I'll explain how you can use custom commands in case you need to do something that isn't covered by the pre-defined ApplicationCommands objects.
Download the example to experiment with it and to see additional details.
|