Custom commands in WPF

We’ve looked at using built-in commands and providing bindings for built-in commands, so it’s time to have a look at creating a custom command from scratch. Conventionally, a Command implements the singleton design pattern, which means that in any running program, there should be at most one instance of the Command object in existence. The rationale behind this is that a Command represents a particular set of instructions which is fixed, but which can be called from a number of different sources or actions, such as the copy command which could be called from a menu, a toolbar button or by typing Ctrl+C.  All these actions should do the same thing, so they should all refer to the same Command object.

As such, we should create a Command object as a singleton. There are various ways this can be done in code, but we’ll look at just one here: declaring the new custom Command as a static object that is created in a static constructor (which is called once when the program starts, and that’s it). We create a new class for our custom Command, and its code looks like this:

  class CustomCommands
    public static RoutedUICommand AquaButtonCommand;

    static CustomCommands()
      InputGestureCollection aquaButtonInputs = new InputGestureCollection();
      aquaButtonInputs.Add(new KeyGesture(Key.A, ModifierKeys.Alt));
      aquaButtonInputs.Add(new MouseGesture(MouseAction.WheelClick, ModifierKeys.Control));
      AquaButtonCommand = new RoutedUICommand("Aqua", "AquaButton", typeof(CustomCommands), aquaButtonInputs);

The RoutedUICommand class is the WPF class for creating a routed command connected to a user interface object. Remember that a routed command follows the same routing rules as an ordinary event: starting at the root of the interface tree (typically the Window), each event first tunnels through the tree until it reaches the control that generated the event, and then bubbles back up the tree, visiting each control in the tree along the way in both directions. A ‘preview’ handler for the event can be placed at any point on the path, and it will be fired as the Command tunnels down from the root; a regular handler will be fired as the Command bubbles back up to the root.

We’ve called the custom Command AquaButtonCommand, since it has the frivolous effect of changing part of the UI to the colour aqua. This seemed a slighly more picturesque way of illustrating where the events were handled than the usual method of printing a message to the console.

This example shows how to attach a KeyGesture and a MouseGesture to the Command. (We’ll attach the Command to the ‘Aqua’ button later.) The code here causes the Command to be executed when the Alt+A key combination is pressed, or when the Ctrl key is held down and the mouse wheel rotated (somewhat confusingly, the WheelClick mouse action isn’t a press of the wheel; it’s a rotation of the wheel).

In the RoutedUICommand constructor call on the last line, the first argument is a string that can be used elsewhere as a description of the command (for example, as text on a Button). The second argument is a string that is used in the internal representation of the Command (as in serialization). The third argument is the data type of the enclosing class, and the last argument is the InputGestureCollection we’ve just created.

So much for creating the custom command. Once you have the new command, it can be used in the same way as any of the built-in commands, so there are no surprises here. However, one thing we haven’t yet considered is how to enable and disable a command from within the code.

In our first example, where we attached cut, copy and paste commands to a TextBox, the TextBox did all the work of deciding when a command was enabled or disabled. If there was some text on the clipboard, the paste command was enabled; if some text was selected in the TextBox, cut and copy were enabled and so on. Most commands don’t have this functionality built in so it’s up to the programmer to decide when to enable and disable a command.

We’ll attach our custom AquaButtonCommand to the Button labelled ‘Aqua’, and then do a few other things with the command. The code looks like this:

      customButton.Command = CustomCommands.AquaButtonCommand;
      customButton.Content = CustomCommands.AquaButtonCommand.Text;
      CommandBinding customBinding = new CommandBinding(CustomCommands.AquaButtonCommand);
      customBinding.CanExecute += new CanExecuteRoutedEventHandler(aquaBinding_CanExecute);
      customBinding.Executed += new ExecutedRoutedEventHandler(aquaBinding_Executed);

On line 1, we attach the command to the button. On line 2, we use the descriptive text we specified when creating the command as the label for the Button. Line 3 creates a binding for the command in the usual way. We’ll look at line 4 in a minute. Line 5 specifies a handler for the Executed event, and line 6 attaches the binding to the Window object containing the whole UI. Line 7 is commented out for now.

The handler looks like this:

    void aquaBinding_Executed(object sender, ExecutedRoutedEventArgs e)
      if (sender == customButton)
        ((Button)sender).Background = new SolidColorBrush(Colors.Aqua);
      else if (sender == this)
        ((Window)sender).Background = new SolidColorBrush(Colors.Aqua);

The handler attempts to discover what the sender of the event is (either the Aqua button or the underlying window) and then sets the background colour of the sender to Aqua.

Now back to the CanExecute event, for which we specified a handler above. The handler looks like this:

    void aquaBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
      // WriteLine for testing when CanExecute is called...

      // Custom button becomes enabled when text is correct
      if (textBox1.Text.Equals("Enable"))
        e.CanExecute = true;

The CanExecute event is generated whenever there is any interaction with any part of the UI. By default, if no handler is specified for this event, CanExecute is ‘true’, so the Aqua button would be enabled. However, as soon as we attach a handler for the event, the default value is ‘false’, so the Aqua button starts off disabled here.

Since it’s difficult to know when the event happens, we’ve written a WriteLine() statement that prints a message each time the handler is called. If you run the project as a console application, you’ll see this message printed every time you click anywhere in the window with the mouse, or any time you type on the keyboard. The handler basically gives you the chance to enable or disable its associated command in response to any interaction with the program. To illustrate this, we enable the command whenever textBox1 contains the text “Enable”. If you start up the program, you’ll note that the Aqua button starts off disabled. If you delete the existing text in textBox1 and then type Enable, you’ll see the button become enabled after you type in the last letter “e”. At this point, the keyboard and mouse shortcuts also kick in, and you can run the command either by pressing the Aqua button, typing Alt+A, or Ctrl+MouseWheel. Since the handler was attached to the Window, it is the Window’s background (visible as the unsightly gap between the two TextBoxes) that turns aqua.

If we now disconnect the binding from the Window and attach it to customButton instead (comment out line 6 and uncomment line 7 in the code dealing with the binding above), then the command can be fired only if the button is pressed directly or else it has the keyboard focus (you can tab through the controls until it gets the focus). Pressing the button now causes the button’s background to become aqua, rather than the Window’s, since the button is now the sender of the command. (Curiously, when the button has focus, the Alt+A key combination will fire the command, but the Ctrl+MouseWheel gesture won’t. Not sure why…)

If you attach the binding to both the Window and the button, then the sender of the command is the Window if the button doesn’t have focus, and the button if it does have focus, as you might expect.

Code available here.

Post a comment or leave a trackback: Trackback URL.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: