Chapter 4
IN THIS CHAPTER
Understanding the command types
Using built-in commands
Using custom commands
Using routed commands
Even though WPF still supports the direct event-handler wire-up (for example, through the OnClick
event), WPF introduces a much better mechanism for responding to user events. It significantly reduces the amount of code you have to write and adds testability to your application. Traditional event handling is all contained in the code-behind for your form, which is extremely difficult to test in an automated fashion.
Software patterns have been around for a long time, first brought to the forefront by the classic tome Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley) by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides — commonly referred to as the “Gang of Four.” Software has evolved, and many new patterns have been developed over the years. One of the most effective user interface patterns developed for WPF is the Model-View-View Model (MVVM) pattern (commonly referred to as ViewModel, see https://docs.microsoft.com/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern
). Using the ViewModel pattern in your WPF applications will improve software reuse, testability, readability, maintainability, and most of the other “-ilities” as well. (You run across other design patterns throughout this whole book.)
This chapter concentrates on yet another pattern, the Command Pattern, which can provide an effective middle ground between directly wiring things up and using an intermediary to make the connection between data and the view. Using the command pattern brings order to directly wiring things up, but is less difficult to implement than MVVM. The chapter begins by comparing the Command Pattern to traditional event handling. It then discusses three command types: built-in, custom, and routed.
The Command Pattern has been around since, well, forever, and you most likely use it every day. Copy and Paste commands are example implementations of the pattern built into Windows and most Windows applications. WPF provides a significant number of built-in commands and also allows for completely customized commands! The following sections provide you with additional information about how commands can work in WPF.
Traditional handling of user events (and still supported in WPF) is through an event handler. When the button on the Window is clicked, the code in the event handler (which has to be in the code-behind file) will execute. Here is an example of the WPF for using event handling (also found in the TraditionalEventHandling example in the downloadable source):
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TextBox Name="Message" Grid.Row="0" HorizontalAlignment="Left"
Width="200" Height="20"/>
<Button Name="ClickMe" Grid.Row="1" HorizontalAlignment="Center"
Content="Click Me" Click="ClickMe_Click"/>
</Grid>
private void ClickMe_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Message.Text);
}
By placing this code in the code-behind event handler, the business logic is now mixed with the user interface code, mixing concerns. To be fair, nothing in the framework makes one put the code in the code-behind; it just seems to always end up there. (You can, of course, separate your business logic into classes outside of your code-behind.)
This problem gets compounded when additional UIElements are added to the window that need to execute the same code. The common fix for this situation is to refactor the code in the original event handler into a separate method and have the event handlers for the related UIElements call the new method. The new method can even be moved into the Business Layer, separating concerns and allowing for testability.
The other issue is one of user experience. Often, menus and buttons need to be actively enabled or disabled based on the condition of the data (or some other condition/user action) in the window.
In this example, the ClickMe
button remains enabled all the time because the application doesn't provide any code to enable or disable it based on the content of the Message
text box. Realistically, the user interface should disable ClickMe
automatically whenever Message
is blank. Users tend to click active items repeatedly when given the option, wondering why nothing is happening. The command pattern as implemented in WPF cleanly and easily resolves both issues.
The ICommand
interface provides the contract for a command that a particular class will execute. It specifies the conditions required so that the command can execute. When those conditions are met, a class implementing ICommand
executes the command. ICommand
(which is the base interface for all commands discussed here) defines two event handlers and one event, as shown here.
bool CanExecute(object parameter);
void Execute(object parameter);
event EventHandler CanExecuteChanged;
Perhaps the most powerful feature of WPF commands is the capability to determine at runtime whether the bound controls can execute. (See the next section for a detailed discussion.) The following list describes the ICommand
elements in more detail.
CanExecute
is run by the CommandManager
whenever focus changes, the PropertyChanged
or CollectionChanged
events are raised, or on demand through code. If the event handler returns false
, all UIElements bound to that command are disabled.Execute
contains the code that executes when the user action is processed or the command is executed through code.CanExecuteChanged
accesses a mechanism supplied by INotifyCollectionChanged
and INotifyPropertyChanged
that determines when CanExecute
is required.The ICommand
interface doesn't provide the entire goodness of commands in WPF. The RoutedCommand
class (and its first descendant, RoutedUICommand
) takes advantage of event routing to provide additional power.
The CanExecute
event handler raises the PreviewCanExecute
event, and the Execute
event handler raises the PreviewExecuted
event. These events are raised just prior to the CanExecute
and Execute
handlers and bubble up the element tree until an element with the correct command binding is located. This approach is useful to allow the control of commands at a higher level, while the fine-grained elements still control the CanExecute
and the Execute
event handlers.
Routed commands also expose a collection of InputGestures
— keystrokes or other gestures that fire the Execute
event handler. You use InputGestures
to assign hot-key combinations to commands, such as Ctrl+S for saving.
WPF provides a number of built-in commands that you can use with little or no code (see the list at https://docs.microsoft.com/dotnet/api/system.windows.input
). The most common set used by line-of-business-developers is wrapped up in the ApplicationCommands
library. The advantage of using the built-in commands is that all the plumbing is taken care of for you. For example, the CanExecute
and Execute
event handlers are already implemented. All you have to do is bind them to UIElements through XAML, as shown in the BuiltInCommands example.
Clipboard.Clear();
The sample shown in the following code and Figure 4-1 uses the ApplicationCommands.Copy
and ApplicationCommands.Paste
commands to facilitate Clipboard manipulation in your application. As a side note, WPF allows you to abbreviate the built-in commands by dropping the container name (ApplicationCommands
), so Copy
and Paste
are legitimate abbreviations for the command bindings ApplicationCommands.Copy
and ApplicationCommands.Paste
.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" HorizontalAlignment="Left" Name="menu1">
<MenuItem Command="Copy">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Command="Paste">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</Menu>
<TextBox Grid.Row="1" HorizontalAlignment="Left" Width="200"/>
<TextBox Grid.Row="2" HorizontalAlignment="Left" Width="200"/>
</Grid>
When the UIElement with focus supports the Clipboard Copy
action and there are items selected that can be copied to the Clipboard, any elements bound to the Copy
command are enabled.
When the UIElement with focus supports the Clipboard Paste
action and there is data in the Clipboard that is supportable by the element with focus, any elements bound to the Paste
command are enabled.
You can use custom commands in all sorts of ways. For example, you might decide to implement the ApplicationCommands.Copy
command by using a Button
instead of a MenuItem
. When you try using just the Command
property, as you do with a MenuItem
, it doesn't work. You need to create the connectivity that a MenuItem
possesses, but a Button
doesn't, by using a custom command. The following sections show the simplest method for creating a custom command. You can also see this code in the CustomCommands example in the downloadable source.
This example uses two custom buttons, one labeled Copy and another labeled Paste. It also has two text boxes, one to hold the source text and another to hold the destination text. Destination is set for read-only use because the example copies from the source to the destination to keep things simple. You use the buttons as you did the menu items in the previous example. However, the connectivity isn’t nearly as automated this time. The following code shows the interface for this example:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal"
HorizontalAlignment="Left">
<Button Content="Copy" Command="ApplicationCommands.Copy"/>
<Button Content="Paste" Command="ApplicationCommands.Paste"/>
</StackPanel>
<TextBox Name="Source" Grid.Row="1" HorizontalAlignment="Left"
Width="200"/>
<TextBox Name="Destination" Grid.Row="2" HorizontalAlignment="Left"
Width="200" IsReadOnly="True"/>
</Grid>
To make the example code work, you must create a binding between the controls and the underlying code-behind. You have a number of ways in which to create a binding, and this example shows the most common. In the following code, you define the Window.CommandBindings
as a series of CommandBinding
elements. Note that the Command
attributes here match the Command
attributes used for the two buttons in the previous section:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy"
Executed="CommandBinding_Executed"
CanExecute="CommandBinding_CanExecute"/>
<CommandBinding Command="ApplicationCommands.Paste"
Executed="CommandBinding_Executed"
CanExecute="CommandBinding_CanExecute"/>
</Window.CommandBindings>
When you type certain attribute names, such as Executed
, the IDE automatically asks whether you want to create the associated event handler. If you allow the automatic creation, the event handler will appear in the code-behind for you. The temptation is to create a unique event handler for each of the command entries, but this would be a mistake because doing so makes maintenance harder. Instead, you want to use a single event handler for everything, as shown in the next section.
Now that you have controls and binding from the controls to an event handler, you need to create the event-handler code. In this case, you first need to determine whether executing the command is even possible. The buttons will automatically enable or disable as needed based on the value of e.CanExecute
in the following code:
private void CommandBinding_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
string Name = ((RoutedCommand)e.Command).Name;
if (Name == "Copy")
{
if (Source == null)
{
e.CanExecute = false;
return;
}
if (Source.SelectedText.Length > 0)
e.CanExecute = true;
else
e.CanExecute = false;
}
else if (Name == "Paste")
{
if (Clipboard.ContainsText() == true)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
}
The determiner for execution differs in each case. The Copy
button won't work if the user hasn’t selected some text. Likewise, it’s not possible to paste text to the destination when the Clipboard
lacks data of the correct type. Note that you must use Clipboard.ContainsText()
to ensure that the user hasn't copied an image or some other type of data.
Now that you have all the connections defined between the window and the code-behind, you can begin coding the actual task. First you must add the Clipboard.Clear();
call after InitializeComponent();
in the MainWindow()
method, as you did for the previous example.
The next step is to handle the Executed
event. A single event handler works with both buttons, as shown here:
private void CommandBinding_Executed(object sender,
ExecutedRoutedEventArgs e)
{
String Name = ((RoutedCommand)e.Command).Name;
if (Name == "Copy")
{
Clipboard.SetText(Source.SelectedText);
}
else if (Name == "Paste")
{
Destination.Text = Destination.Text + Clipboard.GetText();
}
}
As with the CommandBinding_CanExecute()
method in the previous section, this code begins by obtaining the name of the button that the user clicked. It uses this information to select a course of action. The goal is to copy text from Source
and place it on Clipboard
, or to copy text from Clipboard
and place it in Destination
.
Typing some text and then selecting some of it enables the Copy button, as shown in Figure 4-2. Note that the button doesn’t become enabled when you type the text; it’s the act of selecting the text that enables it. This is the same action that occurs when you use menu controls in the previous example.
When you click Copy, both the Copy and the Paste buttons are enabled. This action occurs because the text remains highlighted in the source text box. When you click Paste, the Copy button remains enabled. Normally, an application would place the focus on the pasted location, disabling the Copy button. As previously mentioned, this example does things simply so that you can see how the connections work. Clicking in the source text box (so that the text is no longer highlighted) does disable the Copy button.
Routed commands require a little more effort, but they also allow you to perform custom actions that ICommand
doesn't support. This example does something simple: displays a message box. However, the technique shown works for complex applications as well. The following sections demonstrate how you can use routed commands for both buttons and menus. You can also see this code in the RoutedCommands example in the downloadable source.
The center of the exercise is the RoutedUICommand
object, cmdSayHello
. It provides the resource needed to make the command available. However, to make this object work properly, it must appear within its own class within the RoutedCommands
namespace (rather than as part of the MainWindow
class).
public static class Command
{
public static readonly RoutedUICommand cmdSayHello =
new RoutedUICommand("Say Hello Menu", "DoSayHello",
typeof(MainWindow));
}
Modern versions of Visual Studio automatically add the code required to make your namespace accessible as local
. The actual entry appears as xmlns:local="clr-namespace:RoutedCommands"
in this case. Here is the full code so that you see where to look for the namespace declaration:
<Window x:Class="RoutedCommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RoutedCommands"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="400">
If you don't have such an entry already in your XAML code, you must create one. Otherwise, your application won’t be able to find your custom commands.
As with the ApplicationCommands
entries used with buttons, you must provide command bindings in order to access your custom commands. The following code shows the command bindings used for this example:
<Window.CommandBindings>
<CommandBinding Command="local:Command.cmdSayHello"
CanExecute="CommandBinding_CanExecute"
Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
You can use custom commands with any control that normally supports a click-type event. This example uses both a menu and a button to access the custom command, but you have many other options. The following code creates the application interface, which looks much like the other examples in this chapter:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" HorizontalAlignment="Left" Name="menu1">
<MenuItem Command="local:Command.cmdSayHello"/>
</Menu>
<TextBox Name="NameStr" Grid.Row="1" HorizontalAlignment="Left"
Width="200"/>
<Button Grid.Row="2" Content="Say Hello"
HorizontalAlignment="Right"
Command="local:Command.cmdSayHello"/>
</Grid>
Creating code for your custom command is almost an anticlimax because it works the same as when working with ICommand
. You still need to define code for the CanExecute
and Executed
event handlers, such as the code shown here:
private void CommandBinding_CanExecute(object sender,
CanExecuteRoutedEventArgs e)
{
if ((NameStr != null) && (NameStr.Text.Length > 0))
e.CanExecute = true;
else
e.CanExecute = false;
}
private void CommandBinding_Executed(object sender,
ExecutedRoutedEventArgs e)
{
MessageBox.Show("Hello " + NameStr.Text);
}
Obviously, this is a really simple example, but the technique works fine for complex setups as well. Note that you don't need to create different code for menus or buttons (or other controls, for that matter). Everyone uses the same code. Also note that you don’t need to perform any special wire-ups to make the application work.
When you first start the application, the menu and button are both disabled. However, when you type text into the text box, both controls become enabled, as shown in Figure 4-3. Clicking either control produces a message box similar to the one shown in Figure 4-4.