Welcome to MSDN Blogs Sign in | Join | Help

Data See, Data Do

Mike Hillberg's Blog on Wpf (.Net and Silverlight)

Syndication

News

All postings are provided "As Is" with no warranties, and confers no rights. Opinions and views expressed here are not necessarily those of Microsoft Corporation.


ICommand is like a chocolate cake

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 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 outputter:

 

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.

 

 

 

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 RoutedUICommand.  You don’t give a delegate directly to the RoutedUICommand, though.  Instead, the RoutedUICommand walks up the tree, looking for a delegate.  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.

 

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

 

public static RoutedUICommand HelloWorldRoutedCommand = new RoutedUICommand();

 

… 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 for the command can look at the Source property of the event args to see where the RoutedUICommand started.  You can also get the command itself, and the CommandParameter from the args.

 

In this example, the search for a CommandBinding started at the same Button on which the Command was set (the “Hello, world” Button).  But you can make the command routing start from a different place by setting the Button.CommandTarget property.  You can also register your CommandBinding globally, instead of in an element’s CommandBindings property like I did here, 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.

 

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

 

RoutedUICommand 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 RoutedUICommand HelloWorldRoutedCommand = new RoutedUICommand()

    {

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

    };

 

… and then it will Execute no matter where your keyboard focus is.  Note, though, that since this is a routed command that’s being executed, it will still route from the element currently with keyboard focus. 

 

 

Built-in routed commands in WPF

 

Anyone can create RoutedUICommand’s.  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).

 

 

CanExecute and CanExecuteChanged for routed commands

 

Just like RoutedUICommand 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:  RoutedUICommand is 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 RoutedUICommand 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.

 

 

 

Published Friday, March 20, 2009 3:48 PM by MikeHillberg

Filed under:

Comments

# infoblog &raquo; ICommand is like a chocolate cake @ Friday, March 20, 2009 8:21 PM

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

infoblog &raquo; ICommand is like a chocolate cake

# ICommand is like a chocolate cake - Mike Hillberg @ Friday, March 20, 2009 9:48 PM

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout

# Interesting Finds: 2009-03-21 @ Saturday, March 21, 2009 11:10 AM

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

Bolik

Anonymous comments are disabled
Page view tracker