Commands

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>
      <Button Command="Cut" Content="Cut" />
      <Button Command="Copy" Content="Copy" />
      <Button Command="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.

Command Objects

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

ApplicationCommands

Commands common to almost all applications. Includes clipboard commands, undo and redo, and document-level operations (open, close, print, etc.).

ComponentCommands

Operations for moving through information, such as scroll up and down, move to end, and text selection.

EditingCommands

Text editing commands such as bold, italic, center, and justify.

MediaCommands

Media-playing operations such as transport (play, pause, etc.), volume control, and track selection.

NavigationCommands

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.

Defining 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.

Using commands in XAML

Example 4-18 shows a Button associated with the standard Copy command.

Example 4-18. Invoking a command with a Button

<Button Command="Copy">Copy</Button>

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

<Window xmlns:m="clr-namespace:MyNamespace;assembly=MyLib" ...>
    ...
    <Button Command="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.

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.

Command Source

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.

Command Bindings

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(  );
    }
    ...
}

Enabling and disabling commands

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.

Command routing

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.

Command targets and focus

Figure 4-4. Command targets and focus

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.

Commands disabled due to missing focus scope

Figure 4-5. Commands disabled due to missing focus scope

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.

Command enabled thanks to focus scope

Figure 4-6. Command enabled thanks to focus scope

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.