The input events we've examined give us a detailed view of user input directed at individual elements. However, it is often helpful to focus on what the user wants our application to do, rather than how she asked us to do it. WPF supports this through the command abstraction—a command is an action the application performs at the user's request.
The way in which a command is invoked isn't usually important. Whether the user presses Ctrl-C, selects the Edit → Copy menu item, or clicks the Copy button on the toolbar, the application's response should be the same in each case: it should copy the current selection to the clipboard. The event system we examined earlier in this chapter regards these three types of input as being unrelated, but WPF's command system lets you treat them as different expressions of the same command.
The command system lets a UI element provide a single handler for
a command, reducing clutter and improving the clarity of your code. It
enables a more declarative style for UI elements; by associating a
MenuItem
or Button
with a particular command, you are
making a clearer statement of the intended behavior than you would by
wiring up Click
event handlers. Example 4-15 illustrates how commands
can simplify things.
Example 4-15. Commands with a menu and text box
<DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="_Edit"> <MenuItem Header="Cu_t"Command="ApplicationCommands.Cut"
/> <MenuItem Header="_Copy"Command="ApplicationCommands.Copy"
/> <MenuItem Header="_Paste"Command="ApplicationCommands.Paste"
/> </MenuItem> </Menu> <ToolBarTray DockPanel.Dock="Top"> <ToolBar> <ButtonCommand="Cut"
Content="Cut" /> <ButtonCommand="Copy"
Content="Copy" /> <ButtonCommand="Paste"
Content="Paste" /> </ToolBar> </ToolBarTray> <TextBox /> </DockPanel>
Each menu item is associated with a command. This is all that's
required to invoke these clipboard operations on the text box; we don't
need any code or event handlers because the TextBox
class has built-in handling for these
commands. More subtly, keyboard shortcuts also work in this example: the
built-in cut, copy, and paste commands are automatically associated with
their standard keyboard shortcuts, so these work wherever you use a text
box. WPF's command system ensures that when commands are invoked, they
are delivered to the appropriate target, which in this case is the text
box.
Tip
You are not obliged to use commands. You may already have classes to represent this idea in your own frameworks, and if WPF's command abstraction does not suit your needs, you can just handle the routed events offered by menu items, buttons, and toolbars instead. But for most applications, commands simplify the way your application deals with user input.
There are five concepts at the heart of the command system:
- Command object
An object identifying a particular command, such as copy or paste
- Input binding
An association between a particular input (e.g., Ctrl-C) and a command (e.g., Copy)
- Command source
The object that invoked the command, such as a
Button
, or an input binding- Command target
The UI element that will be asked to execute the command—typically the control that had the keyboard focus when the command was invoked
- Command binding
A declaration that a particular UI element knows how to handle a particular command
Not all of these features are explicitly visible in Example 4-15—the command bindings are buried inside the text box's implementation, and although input bindings are in use (Ctrl-C will work just fine, for example), they've been set up implicitly by WPF. To make it a bit easier to see all of the pieces, let's look at a slightly more complex example that uses all five concepts explicitly (see Example 4-16).
Example 4-16. Basic command handling
<!— XAML —>
<Window ...>
<Grid>
<Button Command="ApplicationCommands.Properties"
Content="_Properties"/>
</Grid>
</Window>
// Codebehind
public partial class Window1 : Window {
public Window1( ) {
InitializeComponent( );
InputBinding ib = new InputBinding(
ApplicationCommands.Properties,
new KeyGesture(Key.Enter, ModifierKeys.Alt));
this.InputBindings.Add(ib);
CommandBinding cb = new CommandBinding(ApplicationCommands.Properties);
cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);
this.CommandBindings.Add(cb);
}
void cb_Executed(object sender, ExecutedRoutedEventArgs e) {
MessageBox.Show("Properties");
}
}
This example uses the standard ApplicationCommands.Properties
command object. Applications that support
this command would typically open a property panel or window for the
selected item. The XAML in this example associates a button with this
command object; clicking the button will invoke the command. The code
behind establishes an input binding
so that the Alt-Enter shortcut may also be used to invoke the command.
Our example, therefore, has two potential command sources: the button and the input
binding. The command target in this
particular example will be the button; this is true even if the command
is invoked with a keyboard shortcut, because the button is the only
element in the window capable of having the keyboard focus. However, the
button doesn't know how to handle this command, so it will bubble up to
the window, much like an input event. The window does know how to handle
the command; it has declared this by creating a command binding with a handler attached to
the binding's Executed
event. This
handler will be called when the user invokes the command.
Now that we've seen all five features in use, we'll examine each one in more detail.
A command object identifies a particular command. It does not
know how to handle a command—as we've seen, that's the job of a
command binding. Command objects are typically made available through
static properties, such as ApplicationCommands.Properties
.
There are several places from which you can get hold of a
command object. Some controls define commands. For example, the
ScrollBar
control defines one for
each of its actions, and makes these available in static fields, such
as LineUpCommand
and PageDownCommand
. However, most commands are
not unique to a particular control. Some correspond to
application-level actions such as "new file" or "open." Others
represent actions that could be implemented by several different
controls. For example, TextBox
and
RichTextBox
can both handle
clipboard operations.
WPF provides a set of classes that define standard commands. These classes are shown in Table 4-4. This means you don't need to create your own command objects to represent the most common operations. Moreover, built-in controls understand many of these commands.
Table 4-4. Standard command classes
Class | Command types |
---|---|
| Commands common to almost all applications. Includes clipboard commands, undo and redo, and document-level operations (open, close, print, etc.). |
| Operations for moving through information, such as scroll up and down, move to end, and text selection. |
| Text editing commands such as bold, italic, center, and justify. |
| Media-playing operations such as transport (play, pause, etc.), volume control, and track selection. |
| Browser-like navigation commands such as Back, Forward, and Refresh. |
Although the standard commands cover a lot of the common features found in many applications, applications usually have functionality of their own not addressed by the standard commands. You can use the command system for application-specific actions by defining custom commands.
Example 4-17 shows how to define a custom command. WPF uses object instances to establish the identity of commands—if you were to create a second command of the same name, it would not be treated as the same command. Because commands are identified by their command objects rather than their names, commands are usually put in public static fields or properties.
Example 4-17. Creating a custom command
... using System.Windows.Input; namespace MyNamespace { public class MyAppCommands { public static RoutedUICommand AddToBasketCommand; static MyAppCommands( ) { InputGestureCollection addToBasketInputs = new InputGestureCollection( ); addToBasketInputs.Add(new KeyGesture( Key.B, ModifierKeys.Control|ModifierKeys.Shift)); AddToBasketCommand = new RoutedUICommand( "Add to Basket", "AddToBasket", typeof(MyAppCommands), addToBasketInputs); } } }
The first RoutedUICommand
constructor parameter is the name as it should appear in the user
interface. In a localizable application, you would use a mechanism
such as the .NET class library's ResourceManager
to retrieve a localized
string rather than hardcoding it. The second constructor parameter
is the internal name of the command as used from code—this should
match the name of the field in which the command is stored, with the
command suffix removed.
As with the built-in commands, your application command doesn't do anything on its own. It's just an identifier. You will need to supply command bindings to implement the functionality. You will also typically want to associate the command with menu items or buttons.
Example 4-18 shows a
Button
associated with the
standard Copy
command.
Because this example uses a standard command from the ApplicationCommands
class, we can use this
short form syntax, specifying nothing but the command name. However,
for commands not defined by the classes in Table 4-4, a little more information is
required. The full syntax for a command attribute in XAML is:
[[xmlNamespacePrefix
:]ClassName
.]EventName
If only the event name is present, the event is presumed to be
one of the standard ones. For example, Undo
is shorthand for ApplicationCommands.Undo
. Otherwise, you
must also supply a class name and possibly a namespace prefix. The
namespace prefix is required if you are using either custom
commands, or commands defined by some third-party component. This is
used in conjunction with a suitable XML namespace declaration to
make external types available in a XAML file. (See Appendix A for more information on clr-namespace
XML namespaces.)
Example 4-19 shows the
use of the command-name syntax with all the parts present. The value
of m:MyAppCommands.AddToBasketCommand
means
that the command in question is defined in the MyNamespace.MyAppCommands
class in the
MyLib
component, and is stored in
a field called AddToBasketCommand
.
Example 4-19. Using a custom command in XAML
<Windowxmlns:m="clr-namespace:MyNamespace;assembly=MyLib"
...> ... <ButtonCommand="m:MyAppCommands.AddToBasketCommand"
>Add to Basket</Button> ...
Because commands represent the actions performed at the user's request, it's likely that some commands will be invoked very frequently. It is helpful to provide keyboard shortcuts for these commands in order to streamline your application for expert users. For this, we turn to input bindings.
An input binding associates a particular form of input gesture, such as a keyboard shortcut,
with a command. Two input gesture types are currently supported: a
MouseGesture
is a particular mouse
input such as a Shift-left-click, or a right-double-click; a KeyGesture
, as used in Example 4-16, is a particular keyboard
shortcut. Many of the built-in commands are associated with standard
gestures. For example, ApplicationCommands.Copy
is associated with
the standard keyboard shortcut for copying (Ctrl-C in most
locales).
Although a command can be associated with a set of gestures when
it is created, as Example 4-17 showed,
you may wish to assign additional shortcuts for the command in the
context of a particular window or element. To allow this, user
interface elements have an InputBindings
property. This collection
contains InputBinding
objects that
associate input gestures with commands. These augment the default
gestures associated with the command. Example 4-16 illustrated this technique—it
bound the Alt-Enter shortcut to the built-in Properties
command.
Tip
Occasionally, it can be useful to disable the default input bindings. A common reason for doing this is that a particular application may have a history of using certain nonstandard keyboard shortcuts, and you wish to continue this to avoid disorienting users. For example, email software has traditionally used Ctrl-F to mean "Forward," even though this is more commonly associated with "Find" in other applications.
In most cases, you can just add a new input binding to your
window, and that will override the existing binding. But what if you
simply want to disassociate a particular shortcut from any command?
You can do this by binding it to the special ApplicationCommands.NotACommand
object.
Establishing an input binding to this pseudocommand effectively
disables the binding.
The command source is the object that was used to invoke the
command. It might be a user interface element, such as a button,
hyperlink, or menu item. But it can also be an input gesture. Command
sources all implement the ICommandSource
interface, as shown in Example 4-20.
Example 4-20. ICommandSource
public interface ICommandSource { ICommand Command { get; } object CommandParameter { get; } IInputElement CommandTarget { get; } }
If you set the Command
property to a command object, the source will invoke this command when
clicked, or in the case of an input gesture, when the user performs
the relevant gesture.
The CommandParameter
property
allows us to pass information to a command when it is invoked. For
example, we could tell our hypothetical AddToBasket
command what we would like to
add to the basket, as shown in Example 4-21.
Example 4-21. Passing a command parameter
<MenuItem Command="m:MyAppCommands.AddToBasketCommand"
CommandParameter="productId4823"
Header="Add to basket" />
The command handler can retrieve the parameter from the Parameter
property of the ExecutedRoutedEventArgs
, as Example 4-22 shows. (This example is a
command handler for our hypothetical AddToBasketCommand
. The handler would be
attached with a command binding as was shown in Example 4-16.)
Example 4-22. Retrieving a command parameter
void AddToBasketHandler(object sender, ExecutedRoutedEventArgs e) { string productId = (string) e.Parameter; ... }
Command parameters are slightly less useful if you plan to
associate commands with keyboard shortcuts. Input bindings are command
sources, so they also offer a CommandParameter
property, but Example 4-23 shows the problem
with this.
Example 4-23. Associating a command parameter with a shortcut
public Window1( ) { InitializeComponent( ); KeyBinding kb = new KeyBinding(MyAppCommands.AddToBasketCommand, Key.B, ModifierKeys.Shift|ModifierKeys.Control); kb.CommandParameter = "productId4299"; this.InputBindings.Add(kb); }
This adds an input binding, associating the Ctrl-Shift-B
shortcut with our AddToBasketCommand
. The CommandParameter
property of the binding
will be passed to the command handler just as it is when the input
source is a button or menu item. But of course, it will pass the same
parameter every time, which limits the utility—you might just as well
hardcode the value into the command handler. So in practice, you would
normally use command parameters only for commands without a keyboard
shortcut.
If you were building a real application with shopping-basket
functionality, it would probably make more sense to use data binding
rather than command parameters. If you arrange for the control that
invokes the command to have its data context set to the data you
require, the command handler can retrieve the DataContext
of the command target, as Example 4-24 shows.
Example 4-24. Commands and data
void AddToBasketHandler(object sender, ExecutedRoutedEventArgs e) { FrameworkElement source = (FrameworkElement) e.Source; ProductInfo product = (ProductInfo) source.DataContext; ... }
This technique has the benefit of working even when a keyboard shortcut is used. Chapter 6 explains data contexts.
The ICommandSource
interface
also offers a CommandTarget
property. Although the interface defines this as a read-only property,
all of the classes that implement this interface in WPF add a setter,
enabling you to set the target explicitly. If you don't set this, the
command target will typically be the element with the input focus
(although, as we'll see later, there are some subtle exceptions).
CommandTarget
lets you ensure that
a particular command source directs the command to a specific target,
regardless of where the input focus may be. As an example of where you
might use this, consider an application that uses a RichTextBox
as part of a data template
(introduced in Chapter 1)—you might use this
to allow the user to add annotations to data items in a list. If you
provided a set of buttons right next to the RichTextBox
to invoke commands such as
ToggleBold
or ToggleItalic
, you would want these to be
applicable only to the RichTextBox
they are next to. It would be confusing to the user if she clicked on
one of these while the focus happened to be elsewhere in her
application. By specifying a command target, you ensure that the
command only ever goes where it is meant to go.
For a command to be of any use, something must respond when it
is invoked. Some controls automatically handle certain commands—the
TextBox
and RichTextBox
handle the copy and paste
commands for us, for example. But what if we want to provide our own
logic to handle a particular command?
Command handling is slightly more involved than simply attaching
a CLR event handler to a UI element. The classes in Table 4-4 define 144 commands, so if
FrameworkElement
defined CLR events
for each distinct command, that would require 288 events once you
include previews. Besides being unwieldy, this wouldn't even be a
complete solution—many applications define their own custom commands
as well as using standard ones.
The obvious alternative would be for the command object itself
to raise events. However, each command is a singleton—there is only
one ApplicationCommands.Copy
object, for example. If you were able to add a handler to a command
object directly, that handler would run anytime the command was
invoked anywhere in your application. What if you want to handle the
command only if it is executed in a particular window or within a
particular element?
The CommandBinding
class
solves these problems. A CommandBinding
object associates a specific
command object with a handler function in the scope of a particular
user interface element. This CommandBinding
class offers PreviewExecuted
and Executed
events, which are raised as the
command tunnels and bubbles through the UI.
Command bindings are held in the CommandBindings
collection property defined
by UIElement
. Example 4-25 shows how to handle the ApplicationCommands.New
command in the code
behind for a window.
Example 4-25. Handling a command
public partial class Window1 : Window { public Window1( ) { InitializeComponent( ); CommandBinding cmdBindingNew = new CommandBinding(ApplicationCommands.New); cmdBindingNew.Executed += NewCommandHandler; CommandBindings.Add(cmdBindingNew); } void NewCommandHandler(object sender, ExecutedRoutedEventArgs e) { if (unsavedChanges) { MessageBoxResult result = MessageBox.Show(this, "Save changes to existing document?", "New", MessageBoxButton.YesNoCancel); if (result == MessageBoxResult.Cancel) { return; } if (result == MessageBoxResult.Yes) { SaveChanges( ); } } // Reset text box contents inputBox.Clear( ); } ... }
As well as supporting execution of commands, CommandBinding
objects can be used to
determine whether a particular command is currently enabled. The
binding raises a PreviewCanExecute
and CanExecute
pair of events, which tunnel
and bubble in the same way as the PreviewExecuted
and Executed
events. Example 4-26 shows how to handle this event
for the system-defined Redo
command.
Example 4-26. Handling QueryEnabled
public Window1( ) { InitializeComponent( ); CommandBinding redoCommandBinding = new CommandBinding(ApplicationCommands.Redo); redoCommandBinding.CanExecute += RedoCommandCanExecute; CommandBindings.Add(redoCommandBinding); } void RedoCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = myCustomUndoManager.CanRedo; }
Command bindings rely on the bubbling nature of command
routing—the top-level Window
element is unlikely to be the target of the command, as the focus
will usually belong to some child element inside the window.
However, the command will bubble up to the top. This routing makes
it easy to put the handling for commands in just one place. For the
most part, command routing is pretty straightforward—it usually
targets the element with the keyboard focus, and uses tunneling and
bubbling much like normal events. However, there are certain
scenarios where the behavior is a little more complex, so we will
finish off with a more detailed look at how command routing works
under the covers.
All of the built-in command objects use a class called
RoutedUICommand
, and you will
normally use this if you define application-specific
commands.[22] RoutedUICommand
provides the mechanism for finding the right command binding when
the command is invoked. This often needs to be determined by
context. Consider Example 4-27.
Example 4-27. Multiple command targets
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Menu Grid.Row="0"> <MenuItem Header="_Edit"> <MenuItem Header="Cu_t" Command="ApplicationCommands.Cut" /> <MenuItem Header="_Copy" Command="ApplicationCommands.Copy" /> <MenuItem Header="_Paste" Command="ApplicationCommands.Paste" /> </MenuItem> </Menu> <TextBox Grid.Row="1" AcceptsReturn="True" /> <ListBox Grid.Row="2"> <TextBlock Text="One" /> <TextBlock Text="Two" /> </ListBox> </Grid>
If the focus is in the text box when the Copy
command is invoked, the text box
handles the command itself as you would expect, copying the
currently selected text to the clipboard. But not all controls have
an obvious default Copy
behavior.
If the command were invoked while the focus was in the listbox, you
would need to supply application-specific code in order for the
command to do anything. RoutedUICommand
supports this by providing
a mechanism for identifying the command's target and locating the
correct handler.
The target of the RoutedUICommand
is determined by the way
in which the command was invoked. Typically, the target will be
whichever element currently has the focus, unless the command
source's CommandTarget
has been
set. Figure 4-4 shows the controls
and menu from Example 4-27. As you
can see from the selection highlight, the TextBox
at the top had the focus when the
menu was opened, so you would expect it to be the target of the
commands. This is indeed what happens, but it's not quite as
straightforward as you might expect.
RoutedUICommand
tries to
locate a handler using a tunneling and bubbling system similar to
the one used by the event system. However, command routing has an
additional feature not present in normal event routing: if bubbling
fails to find a handler, RoutedUICommand
may try to retarget the
command. This is designed for the scenario where commands are
invoked by user interface elements such as menu or toolbar items
because these present an interesting challenge.
Example 4-27 is an example of
this very scenario. It has a subtle potential problem. While the
menu is open, it steals the input focus away from the TextBox
. It's unlikely that the menu item
itself is the intended target for a command—it's merely the means of
invoking the command. Users will expect the Copy
menu item to copy whatever was
selected in the TextBox
, rather
than copying the contents of the menu item. The menu deals with this
by relinquishing the focus when the command is executed. This causes
the focus to return to the TextBox
, and so the command target is the
one we expect. However, there's a problem regarding disabled
commands.
A command target can choose whether the commands it supports
are enabled. A TextBox
enables
copying only if there is some selected text. It enables pasting only
if the item on the clipboard is text, or can be converted to text.
Menus gray out disabled commands, as Figure 4-4 shows. To do this, a menu
item must locate the command target. The problem is that the menu is
in possession of the keyboard focus at the point at which it needs
to discover whether the command is enabled; the appropriate command
target is therefore not the focused item in this case.
The RoutedUICommand
class
relies on focus scopes to
handle this situation. If a RoutedUICommand
fails to find a command
binding, it checks to see whether the initial target was in a nested
focus scope. If it was, WPF finds the parent focus scope, which will
typically be the window. It then retargets the command, choosing the
element in the parent scope that has the
logical focus (i.e., the last element to have
the focus before the menu grabbed it). This causes a second
tunneling and bubbling phase to occur. The upshot is that the
command's target is whichever element had the focus before the menu
was opened, or the toolbar button clicked.
If you are using menus or toolbars, you don't need to do
anything to make this work, because Menu
and ToolBar
elements both introduce nested
focus scopes automatically. However, if you want to invoke commands
from other elements, such as buttons, you'll need to define the
focus scope explicitly. Consider Example 4-28.
Example 4-28. Without focus scope
<StackPanel> <Button Command="ApplicationCommands.Copy" Content="_Copy" /> <Button Command="ApplicationCommands.Paste" Content="_Paste" /> <TextBox /> </StackPanel>
This associates two buttons with commands supported by a
TextBox
. And yet, as Figure 4-5 shows, the
buttons remain disabled even when the TextBox
should be able to process at least
one of the commands.
We can fix this by introducing a focus scope around the buttons, as Example 4-29 shows.
Example 4-29. Focus scope
<StackPanel> <StackPanel FocusManager.IsFocusScope="True"> <Button Command="ApplicationCommands.Copy" Content="Copy" /> <Button Command="ApplicationCommands.Paste" Content="Paste" /> </StackPanel> <TextBox /> </StackPanel>
Now when the buttons attempt to locate a handler in order to
choose whether they are enabled, the presence of the focus scope
will cause the command routing to look for the element with the
focus. If the TextBox
has the
logical focus, it will become the command target. As Figure 4-6 shows, this
causes the buttons to reflect the availability of the commands
correctly, and it means they invoke the command on the correct
target when clicked.
We don't have to use focus scopes to solve the problem in this particular example. You can use the more explicit, though slightly cumbersome, approach shown in Example 4-30.
Example 4-30. Explicit command targets
<StackPanel> <Button Command="Copy" Content="Copy" CommandTarget="{Binding ElementName=targetControl}" /> <Button Command="Paste" Content="Paste" CommandTarget="{Binding ElementName=targetControl}" /> <TextBox x:Name="targetControl" /> </StackPanel>
Here, each button specifies its command target explicitly. This makes it absolutely clear what the target will be. However, it is more verbose, so the automatic command routing is often more convenient. And even if the thought of manually specifying the command target for every item in a menu doesn't strike you as unbearable, command routing has the added benefit of working well when there are multiple potential command targets (e.g., multiple text boxes on a form) and you want the command to go to whichever one last had the focus.
[22] * It is technically possible to
provide a different class if you have special requirements.
Command sources are happy to use any implementation of the
ICommand
interface, so you
are not obliged to use the normal command routing mechanism. But
most applications will use RoutedUICommand
.
Get Programming WPF, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.