次の方法で共有


ICommand is like a chocolate cake

(This has been updated with some information about the origin of a routed command’s route, and of focus scopes.)

 

ICommand in WPF is a pretty simple thing at its core. But it gets more interesting and complicated as you build up functionality on top of it, and integrate it into the higher layers of the UI. So it’s either like a layer cake, or layers of an onion. But onion is an outside-in metaphor, and layer cake is a bottom-up metaphor, and ICommand is easier to think of bottom up, so I’m declaring ICommand to be a layer cake.

 

For example, commands provide a mechanism to abstract input (so “navigate back” means “navigate back”, whether it came from the keyboard’s back button or the mouse’s X1 button or anywhere else). And commands provide a mechanism for the View to update the Model in a Model/View separated application. And commands provide a way to search the element tree for a command handler, as well as a way for a command handler to say that it doesn’t want to be executed at the moment.

 

The mechanism for all those scenarios is all very similar, but the scenarios themselves seem so different that it all can get confusing. So here’s a step-by-step description of how it all fits together …

 

 

Start with ICommand

 

ICommand itself is very straightforward:

 

public interface ICommand

{

    void Execute(object parameter);

    bool CanExecute(object parameter);

    event EventHandler CanExecuteChanged;

}

 

Given an instance of an ICommand, you just call Execute, and it does whatever it’s supposed to do. Except you shouldn’t call it if it’s CanExecute is false. If you want to know when CanExecute might be willing to give you a different answer, listen to the CanExecuteChanged event.

 

For example, here’s a super simple command:

 

public class HelloWorldCommand : ICommand

{

    public void Execute(object parameter)

    {

        Debug.WriteLine("Hello, world");

    }

    public bool CanExecute(object parameter)

    {

        return true;

    }

    public event EventHandler CanExecuteChanged;

}

 

If you use that like this:

 

new HelloWorldCommand().Execute(null);

 

… you’ll see “Hello, world” in the debug output window.

 

You can make it more interesting with a parameter and by checking CanExecute. First change the command like this:

 

public class HelloWorldCommand : ICommand

{

    public void Execute(object parameter)

    {

        Debug.WriteLine(parameter);

    }

    public bool CanExecute(object parameter)

    {

        return parameter != null;

    }

    public event EventHandler CanExecuteChanged;

}

 

… and call it like this:

 

var hwc = new HelloWorldCommand();

if (hwc.CanExecute(this))

    hwc.Execute(this);

 

… and in my case I see “CommandCake.Window1” in the debugger output window.

 

Piece o’ cake.

 

 

Button (heart) ICommand

 

Once you have an ICommand instance handy, you can give it to a Button (on the Button.Command property), and Button knows what to do with it. As the simplest example, you can do this with the previous command:

 

<Grid>

  <Grid.Resources>

    <local:HelloWorldCommand x:Key="hwc"/>

  </Grid.Resources>

 

  <Button Command="{StaticResource hwc}">

    Click

  </Button>

</Grid>

 

But if you do that, you’ll notice that the Button is disabled. That’s because Button knows to call CanExecute, but we haven’t specified a parameter, and recall from above that if you pass null as an argument to CanExecute it returns false. So Button has a CommandParameter property that lets you specify what will be passed to CanExecute and Execute:

 

<Grid>

  <Grid.Resources>

    <local:HelloWorldCommand x:Key="hwc"/>

  </Grid.Resources>

 

  <Button CommandParameter="Hello, world"  Command="{StaticResource hwc}" >

    Click

  </Button>

</Grid>

 

Now the button is enabled, and if you click on it, you’ll see “Hello, world” in the debug output window.

 

This actually isn’t just a Button feature, it’s actually in the base class ButtonBase. And MenuItem is a ButtonBase. So MenuItem (heart) ICommand too.

 

 

Update the Model from the View

 

Now we’ve got enough for the classic Model/View usage of ICommand. In the Model/View practice, your View is a bunch of elements, which is data-bound to your Model, which has your actual content. The View can modify the model with two-way bindings and with commands.

 

First, before showing an example of this, let’s make it easier to implement ICommand, by introducing a helper class (a more complete DelegateCommand helper can be found in Prism). This just creates an ICommand instance that takes a delegate which will be called by ICommand.Execute:

 

public class SimpleDelegateCommand : ICommand

{

    Action<object> _executeDelegate;

    public SimpleDelegateCommand(Action<object> executeDelegate)

    {

        _executeDelegate = executeDelegate;

    }

    public void Execute(object parameter)

    {

        _executeDelegate(parameter);

    }

    public bool CanExecute(object parameter) { return true; }

    public event EventHandler CanExecuteChanged;

}

 

 

Now let’s define a Model of a simple Debug writer:

 

public class DebugWriter

{

    ICommand _indentCommand = new SimpleDelegateCommand( (x) => Debug.Indent() );

    ICommand _unindentCommand = new SimpleDelegateCommand( (x) => Debug.Unindent() );

    ICommand _writeLineCommand = new SimpleDelegateCommand( (x) => Debug.WriteLine(x) );

    public ICommand IndentCommand { get { return _indentCommand; } }

    public ICommand UnindentCommand { get { return _unindentCommand; } }

    public ICommand WriteLineCommand { get { return _writeLineCommand; } }

    public int IndentSize

    {

        get { return Debug.IndentSize; }

        set { Debug.IndentSize = value; }

    }

}

 

 

… and use it from a View:

 

<StackPanel>

  <StackPanel.DataContext>

    <local:DebugWriter />

  </StackPanel.DataContext>

  <Grid>

    <Grid.ColumnDefinitions>

      <ColumnDefinition Width="Auto"/><ColumnDefinition />

    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>

      <RowDefinition/><RowDefinition/>

    </Grid.RowDefinitions>

    <TextBlock Margin="3">Indent size:</TextBlock>

    <TextBlock Margin="4" Grid.Column="1" Text="{Binding IndentSize}" />

    <TextBlock Grid.Row="1" Margin="3">Output string:</TextBlock>

    <TextBox Text="s" Grid.Row="1" Grid.Column="1" Name="OutputString" />

   

  </Grid>

 

  <Button Command="{Binding IndentCommand}">Indent</Button>

  <Button Command="{Binding UnindentCommand}">Unindent</Button>

  <Button CommandParameter="{Binding Text,ElementName=OutputString}"

          Command="{Binding WriteLineCommand}">WriteLine</Button>

   

</StackPanel>

 

Notice here that the Button bound to the WriteLine command (the third Button) has its CommandParameter bound to a TextBox.Text.

 

 

Routed commands: an ICommand.Execute implementation that searches for an execute handler

 

For the above example I used the SimpleDelegateCommand as my implementation of ICommand, which maps ICommand.Execute to a delegate call.

 

WPF similarly has a built-in ICommand implementation called RoutedCommand. You don’t give a delegate directly to the RoutedCommand, though. Instead, the RoutedCommand walks up the tree, looking for a delegate. It’s similar to a routed event, in fact it’s implemented as a routed event internally. So you can put your delegate anywhere higher in the tree, in the form of an event handler, using the CommandBinding class, and it will be called by Execute. You specify your delegate with a CommandBinding object.

 

So I add this to my Window1.xaml.cs:

 

public static RoutedCommand HelloWorldRoutedCommand = new RoutedCommand();

 

… and this to my Xaml:

 

<Window.CommandBindings>

    <CommandBinding

          Command="{x:Static local:Window1.HelloWorldRoutedCommand}"

          Executed="CommandBinding_Executed" />

</Window.CommandBindings>

<Grid>

    ...

    <Button Command="{x:Static local:Window1.HelloWorldRoutedCommand}">Hello, world </Button>

    ...

</Grid>

 

… and then when I click the Button, my CommandBinding_Executed method gets called:

 

private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)

{

    if( e.Source is Button )

        Debug.WriteLine((e.Source as Button).Content);

    else

        Debug.WriteLine("Hello, world");

}

 

… and once again I see “Hello, world” in the debug output.

 

Note that the event handler of the CommandBinding.Executed event can look at the Source property of the event args to see where the RoutedCommand started. You can also get the command itself, and the CommandParameter from the args.

 

In this example I put the CommandBinding in the tree ancestry of the Button. Alternatively, you can also register your CommandBinding globally, using the CommandManager.RegisterClassCommandBinding method. That CommandBinding’s CanExecute then gets called no matter where in the tree the routed command’s route is starting.

 

RoutedCommand also has support for CanExecute and CanExecuteChanged. That’s the most complicated part of routed commands, so I’m saving that for the end.

 

 

Mapping input to an ICommand (keyboard accelerators)

 

Beyond Button and MenuItem, there’s another way to get an ICommand to Execute, this one based on user input. As an example, with the following Xaml, pressing <Control>H on the keyboard executes our HelloWorldCommand, again showing “Hello, world” in the debugger:

 

<Grid>

    <Grid.Resources>

        <local:HelloWorldCommand x:Key="hwc"/>

    </Grid.Resources>

    <Grid.InputBindings>

        <KeyBinding Gesture="Control+H" Command="{StaticResource hwc}" CommandParameter="Hello, world"/>

    </Grid.InputBindings>

    ...

 

 

Note that this key binding only takes effect if the keyboard focus is currently somewhere under that Grid, e.g. on a TextBox; otherwise the KeyBinding doesn’t see the <Control>H.

 

RoutedCommand also has an InputGestures property on it, where you can set the default gestures for a command. So you can get this same behavior for the HelloWorldRoutedCommand we created earlier by defining it like this:

 

public static RoutedCommand HelloWorldRoutedCommand = new RoutedCommand()

    {

        InputGestures = { new KeyGesture(Key.H, ModifierKeys.Control) }

    };

 

… and then it will Execute no matter where your keyboard focus is.

 

 

Built-in routed commands in WPF

 

Anyone can create a RoutedCommand. But WPF has a set of them built-in. These are all defined as static fields in the ApplicationCommands, EditingCommands, MediaCommands, and NavigationCommands classes. E.g. NavigationCommands has the BrowseBack command, ApplicationCommands has cut/copy/paste, EditingCommands has ToggleBold, MoveRightByWord, etc. The Windows WM_APPCOMMAND commands get converted into these built-in routed commands automatically by the WPF input system.

 

So, for example, this markup:

 

<Window.CommandBindings>

    <CommandBinding Command="{x:Static NavigationCommands.BrowseBack}"

                   Executed="BrowseBackExecuted" />

</Window.CommandBindings>

 

… will cause the BrowseBackExecuted method to be called when the NavigationCommands.BrowseBack command is executed, for example if you click the mouse X1 button (this is the button that makes a web browser navigate back).

 

 

From whence the command routes

 

If you call RoutedCommand’s ICommand.Execute, the route will start from the element that currently has keyboard focus. But RoutedCommand itself has a special overload of Execute that lets you pick where the route starts:

 

public class RoutedCommand

{

    ...

    public void Execute( Object parameter, IInputElement target);

    ...

}

 

And in fact, RoutedCommand’s implementation of ICommand.Execute just calls this Execute, looking roughly like this:

 

void ICommand.Execute(object parameter)

{

    this.Execute(parameter, Keyboard.FocusedElement );

}

 

 

Button (and MenuItem) actually understands RoutedCommand in particular, in addition to ICommand in general. So if Button.Command is a RoutedCommand, Button by default calls this RoutedCommand.Execute method, passing itself as the target. You can override this behavior, though, with the Button.CommandTarget property. For example, here is a Button that sends the Cut command to a TextBox:

 

<Button Command="{x:Static ApplicationCommands.Cut}"

       CommandTarget="{Binding ElementName=TextBox1}">Click</Button>

<TextBox Name="TextBox1" />

 

This gets more complicated if you’re implementing a toolbar. Say you have a toolbar with cut/copy/paste buttons. You want that toolbar to work against whatever the currently focused text box is, so you don’t want to have to keep setting CommandTarget. E.g., in this:

 

<StackPanel>

    <ToolBar>

        <Button Command="{x:Static ApplicationCommands.Cut}">Cut</Button>

        <Button Command="{x:Static ApplicationCommands.Copy}">Copy</Button>

        <Button Command="{x:Static ApplicationCommands.Paste}">Paste</Button>

    </ToolBar>

    <TextBox />

    <TextBox />

</StackPanel>

 

… the Cut/Copy/Paste should route to whichever TextBox has focus.

 

It doesn’t look like this should work, but it actually does, and here’s why … ToolBar is a “focus scope”. Focus scopes is a whole other post, but the important thing here is what happens with a routed command that’s executing and looking for a CommandBinding. As the execute request bubbles up to the ToolBar, it sees that it’s leaving a focus scope. At that point, rather than just continuing to walk up the visual tree, it goes next to the focused element in that focus scope. In this example, that means that when you click on the Cut button, the search for a handler starts at the Button, then the ToolBar, then the currently-focused TextBox, which is what we want.

 

Menu is also a focus scope, so what works for ToolBar works for Menu as well.

 

 

CanExecute and CanExecuteChanged for routed commands

 

Just like RoutedCommand allows you to define a command that walks up the tree, looking for someone to handle Execute, you also want to find someone to handle CanExecute. So RoutedCommand’s CanExecute implementation does that, and you can listen for it on CommandBinding, as you’d expect, e.g.:

 

<CommandBinding

   Command="{x:Static local:Window1.HelloWorldRoutedCommand}"

   CanExecute="CommandBinding_CanExecute"

   Executed="CommandBinding_Executed" />

 

But here’s the trick: RoutedCommand similarly needs to raise CanExecuteChanged. But how does it know when to raise that event, when it doesn’t know what you’re going to do in CommandBinding_CanExecute?

 

The solution in WPF is a global event named CommandManager.RequerySuggested. This event is fired whenever the state of the routed command world might have changed. In fact, the implementation of ICommand.CanExecuteChanged in RoutedCommand is just a forwarder:

 

public event EventHandler CanExecuteChanged

{

    add { CommandManager.RequerySuggested += value; }

    remove { CommandManager.RequerySuggested -= value; }

}

 

Next problem: When should the RequerySuggested event fire? You make that happen by calling CommandManager.InvalidateRequerySuggested. But usually you don’t have to call it, it’s called automatically in several places, mostly during keyboard/mouse input.

 

That all creates a couple of interesting implications.

 

First of all, if you’re using routed commands, you’ve got some work taking place on every keystroke. On the one hand, even for a fast typist (I clocked in the other day at 88 words/minute on the high-difficulty test!), user input is very infrequent in CPU timeframes. On the other hand, it still takes time to find those CanExecute handlers, and if those handlers do anything non-trivial, and you have a lot of them, it can add up. We’ve seen that be a problem in some larger applications. You can mitigate that by reducing use of routed commands (just use ICommand instead), and by keeping the CanExecute implementations fast.

 

The second interesting implication is that CommandManager.RequerySuggested is a static (global) event. Usually such events can lead to leaks, because they hold the event handler delegate forever. But RequerySuggested instead only keeps weak references to its handler delegates. But now you’ve got a new problem; now the delegate can be garbage collected. You probably don’t have to worry about that, because most applications don’t listen to RequerySuggested; it’s really the Button doing this on your behalf when you set Button.Command. But Button (and MenuItem) deal with this problem by keeping their own strong reference on that delegate, so that it doesn’t get collected.

 

 

In summary

 

So the key points to remember here:

· ICommand is a simple definition with Execute, CanExecute, and CanExecuteChanged.

· You can point a Button or MenuItem at any ICommand with the Command property. The Button/MouseItem will then Execute that command if you click it, will disable itself if CanExecute is false, and will automatically listen for CanExecuteChanged.

· Routed commands are an ICommand implementation that searches the tree (usually starting with the focused element) for a CommandBinding that provides Execute/CanExecute handlers.

· You can also invoke an ICommand from input, e.g. using a KeyBinding.

· WPF pre-defines a set of routed commands in ApplicationCommands, EditingCommands, NavigationCommands, and MediaCommands.

· Be aware that routed commands can impact perf if you use a lot of them and/or your CanExecute handlers do non-trivial work.

Comments

  • Anonymous
    March 20, 2009
    PingBack from http://blog.a-foton.ru/index.php/2009/03/21/icommand-is-like-a-chocolate-cake/

  • Anonymous
    March 20, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Anonymous
    March 21, 2009
    Mix: Mobile Web Sites with ASP.NET MVC and the Mobile Browser Definition File [译]一种简单,快速,精准的sin/cos函数模拟

  • Anonymous
    March 22, 2009
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    March 23, 2009
    The comment has been removed

  • Anonymous
    August 19, 2010
    I want to convert public event EventHandler CanExecuteChanged {    add { CommandManager.RequerySuggested += value; }    remove { CommandManager.RequerySuggested -= value; } } in VB.NET. Is this possible? Thanks

  • Anonymous
    August 19, 2010
    I get the answer just a moment later on community.visual-basic.it/.../30571.aspx

  • Anonymous
    November 23, 2010
    Public Custom Event CanExecuteChanged As EventHandler AddHandler(ByVal value As EventHandler) CommandManager.RequestSuggested += value End AddHandler RemoveHandler(ByVal value As EventHandler) CommandManager.RequestSuggested -= value End RemoveHandler End Event

  • Anonymous
    September 27, 2012
    Nice one.

  • Anonymous
    February 01, 2013
    System.Diagnostics Debug in Silverlight System.dll does not contain any of the Ident.. commands or properties. I am using VS2012 - Any help appreciated. Thanks in advance,

  • Anonymous
    August 24, 2013
    Very HELPFUL, THANKS

  • Anonymous
    August 26, 2014
    This is a great article. If I wanted to do anything besides return true in SimpleDelegateCommand's CanExecute, how could I do this? I have several buttons using different instances of SimpleDelegateCommand, so each one would need its own CanExecute logic. Thanks in advance

  • Anonymous
    August 06, 2015
    This is by far the best explenation I have ever found on commands. Thank you so much.