Click here to Skip to main content
Click here to Skip to main content

Aggregating WPF Commands with CommandGroup

, 4 May 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
Introduces a generic technique of chaining commands together.

Introduction

This article discusses a technique for grouping together and executing multiple commands in sequence. I accomplished this by creating a class that implements WPF’s ICommand interface, called CommandGroup. That class has no inherent behavior or meaning, aside from grouping together a set of commands and treating them as an atomic unit. This design is similar to the ValueConverterGroup class I created back in August of 2006.

Background

I recently had a conversation with Karl Shifflett, a fellow WPF Disciple, about a problem he faced in a WPF app. Suppose you have a TextBox in a window, and a ToolBar with a Save button in it. Assume the TextBox’s Text property is bound to a property on a business object, and the binding’s UpdateSourceTrigger property is set to the default value of LostFocus, meaning that the bound value is pushed back to the business object property when the TextBox loses input focus. Also, assume that the ToolBar’s Save button has its Command property set to the ApplicationCommands.Save command.

In that situation, if you edit the TextBox and click the Save button with the mouse, there is a problem. When clicking on a Button in a ToolBar, the TextBox does not lose focus. Since the TextBox’s LostFocus event does not fire, the Text property binding does not update the source property of the business object.

Obviously, you should not validate and save an object if the most recently edited value in the UI has not yet been pushed into the object. This is the exact problem Karl had worked around, by writing code in his window that manually looked for a TextBox with focus and updated the source of the data binding. His solution worked fine, but it got me thinking about a generic solution that would also be useful outside of this particular scenario. Enter CommandGroup

Introducing CommandGroup

My solution to the problem posed above is to give the ToolBar’s Save button a command that executes two commands in sequence. First, I need a command to execute that updates the TextBox’s Text property binding so that the underlying business object has the new value entered by the user. After that has occurred, the Save command can execute with the assurance that the business object to be saved has the correct values. Here is the XAML in the demo application that creates the proposed solution:

<ToolBar DockPanel.Dock="Top">
  <Button Content="Save">
    <Button.Command>
      <!-- 
      Chain together a set of commands that will sequentially 
      execute in the order they appear below. The first command
      ensures that the focused TextBox's Text is pushed into the
      source property before the Save is executed.
      -->
      <local:CommandGroup>
        <x:StaticExtension Member="local:FlushFocusedTextBoxBindingCommand.Instance" />
        <x:StaticExtension Member="ApplicationCommands.Save" />
      </local:CommandGroup>
    </Button.Command>
  </Button>
</ToolBar>

When the Save button is clicked, first my custom FlushFocusedTextBoxBindingCommand will execute, forcing the focused TextBox to update its data source. If the control with keyboard focus is not a TextBox, that command immediately finishes, and all is well. Next, the standard Save routed command will execute, allowing the application to do whatever needs to be done to validate and save the business object(s).

How CommandGroup Works

CommandGroup implements the ICommand interface, but in a rather unusual way. It merely delegates all calls to the CanExecute and Execute methods off to its child commands. Here is the code in CommandGroup that provides it with a list of child commands:

private ObservableCollection<ICommand> _commands;

/// <summary>
/// Returns the collection of child commands. They are executed
/// in the order that they exist in this collection.
/// </summary>
public ObservableCollection<ICommand> Commands
{
    get
    {
        if (_commands == null)
        {
            _commands = new ObservableCollection<ICommand>();
            _commands.CollectionChanged += this.OnCommandsCollectionChanged;
        }

        return _commands;
    }
}

void OnCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // We have a new child command so our ability to execute may have changed.
    this.OnCanExecuteChanged();

    if (e.NewItems != null && 0 < e.NewItems.Count)
    {
        foreach (ICommand cmd in e.NewItems)
            cmd.CanExecuteChanged += this.OnChildCommandCanExecuteChanged;
    }

    if (e.OldItems != null && 0 < e.OldItems.Count)
    {
        foreach (ICommand cmd in e.OldItems)
            cmd.CanExecuteChanged -= this.OnChildCommandCanExecuteChanged;
    }
}

void OnChildCommandCanExecuteChanged(object sender, EventArgs e)
{
    // Bubble up the child commands CanExecuteChanged event so that
    // it will be observed by WPF.
    this.OnCanExecuteChanged();
}

With that infrastructure in place, the ICommand implementation is actually quite straightforward. You can see how I implemented it below:

public bool CanExecute(object parameter)
{
    foreach (ICommand cmd in this.Commands)
        if (!cmd.CanExecute(parameter))
            return false;

    return true;
}

public event EventHandler CanExecuteChanged;

protected virtual void OnCanExecuteChanged()
{
    if (this.CanExecuteChanged != null)
        this.CanExecuteChanged(this, EventArgs.Empty);
}

public void Execute(object parameter)
{
    foreach (ICommand cmd in this.Commands)
        cmd.Execute(parameter);
}

As seen in the code above, the CommandGroup class is simply a command container. If any of its child commands return false from its CanExecute method, CommandGroup returns false. When told to execute, CommandGroup simply executes each of its child commands one after the next. Also, when any of its child commands raises the CanExecuteChanged event, CommandGroup bubbles that event up to WPF’s commanding system by raising its own CanExecuteChanged event.

The Demo Application

When you run the demo application associated with this article, you will be see a ToolBar with a Save button, and two TextBox controls beneath it. Those TextBoxs are bound to a Foo object, which has Name and Age properties. After you edit one of the TextBoxes, click on the Save button to see a MessageBox that displays the current values in the Foo object.

commandgroup-screenshot.png

In the screenshot above, I added an exclamation mark to the Name field and then clicked Save. As seen in the MessageBox, the Foo object’s Name property is updated before the Save command is executed. Here is the Save command execution logic:

private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Foo f = this.DataContext as Foo;
    string msg = String.Format(
        "Foo values:\r\nName={0}\r\nAge={1}", 
        f.Name, 
        f.Age);

    MessageBox.Show(msg);
}

Here is the command class I wrote that finds the focused TextBox and updates the source of the bound Text property. This class could be extended to support finding various types of focused input controls, such as ComboBox, if necessary:

public class FlushFocusedTextBoxBindingCommand : ICommand
{
    #region Creation

    public static readonly FlushFocusedTextBoxBindingCommand Instance;

    static FlushFocusedTextBoxBindingCommand()
    {
        Instance = new FlushFocusedTextBoxBindingCommand();

        // We need to know when any element gains keyboard focus
        // so that we can raise the CanExecuteChanged event.
        EventManager.RegisterClassHandler(
            typeof(UIElement),
            Keyboard.PreviewGotKeyboardFocusEvent,
            (KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
    }

    static void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        Instance.OnCanExecuteChanged();
    }

    protected FlushFocusedTextBoxBindingCommand()
    {
    }

    #endregion // Creation

    #region ICommand Members

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

    public event EventHandler CanExecuteChanged;

    protected virtual void OnCanExecuteChanged()
    {
        if (this.CanExecuteChanged != null)
            this.CanExecuteChanged(this, EventArgs.Empty);
    }

    public void Execute(object parameter)
    {
        TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
        if (focusedTextBox == null)
            return;

        BindingExpression textBindingExpr = 
          focusedTextBox.GetBindingExpression(TextBox.TextProperty);
        if (textBindingExpr == null)
            return;

        textBindingExpr.UpdateSource();
    }

    #endregion // ICommand Members
}

Possible Improvements

You could enhance CommandGroup in many ways, but I leave that as an exercise for the reader. One cool feature that pops into my mind is to have an IsAsync property that, when set to true, makes the CommandGroup execute all child commands concurrently, on worker threads. This would be useful when the side-effects of running one command are irrelevant to the other commands.

Another useful thing to do is subclass CommandGroup and give it child commands baked in by default. Suppose you want to have a custom logging command execute before any command executes, you could have a LoggingCommandGroup that always executes a logging command first.

The possibilities are endless! Smile | :)

Revision History

  • May 4, 2008 – Created the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMember 401696524-May-12 2:17 
QuestionHow to reference a command defined within a bound ViewModel instance? Pinmemberkainhart3-Nov-10 5:39 
AnswerRe: How to reference a command defined within a bound ViewModel instance? PinmvpJosh Smith3-Nov-10 6:26 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pinmemberkainhart3-Nov-10 9:34 
AnswerRe: How to reference a command defined within a bound ViewModel instance? PinmemberSean A. Hanley5-Apr-12 12:12 
GeneralRe: How to reference a command defined within a bound ViewModel instance? PinmemberGene Sivorot2-May-12 8:57 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pinmemberphatoni16-May-12 4:05 
GeneralRe: How to reference a command defined within a bound ViewModel instance? PinmemberTobias_H4-Feb-13 22:59 
GeneralIn regards to IsAsync Pinmembernedruod1-Oct-08 3:22 
QuestionWhat about controls other than TextBox Pinmembermarmot824-Sep-08 4:14 
Is there any way to extend your solution so that it would work for controls other than TextBox? What about a ComboBox, Date Field, custom controls etc?
AnswerRe: What about controls other than TextBox PinmvpJosh Smith24-Sep-08 4:22 
AnswerRe: What about controls other than TextBox PinmemberJvdP27-May-09 22:41 
GeneralCommandParameter Pinmembercechode30-Aug-08 9:46 
GeneralRe: CommandParameter PinmvpJosh Smith30-Aug-08 10:37 
GeneralRe: CommandParameter Pinmembercechode30-Aug-08 10:53 
GeneralRe: CommandParameter PinmvpJosh Smith30-Aug-08 10:55 
GeneralRe: CommandParameter Pinmembercechode30-Aug-08 11:02 
QuestionWhat a nice solution! Pinmembermoonie17-Jun-08 2:32 
AnswerRe: What a nice solution! PinmvpJosh Smith17-Jun-08 2:42 
GeneralThank you. PinmvpPete O'Hanlon5-May-08 9:58 
GeneralRe: Thank you. PinmvpJosh Smith5-May-08 10:06 
GeneralVery inventive ! PinmemberPrzemyslaw Celej5-May-08 8:36 
GeneralRe: Very inventive ! PinmvpJosh Smith5-May-08 10:06 
GeneralVery awesome... PinmemberRyan Cromwell5-May-08 1:58 
GeneralRe: Very awesome... PinmvpJosh Smith5-May-08 1:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141030.1 | Last Updated 4 May 2008
Article Copyright 2008 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid