Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Aggregating WPF Commands with CommandGroup

Rate me:
Please Sign up or sign in to vote.
4.95/5 (26 votes)
4 May 2008CPOL4 min read 122.4K   1.2K   53   33
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:

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

C#
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:

C#
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:

C#
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:

C#
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! :)

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)


Written By
Software Developer (Senior)
United States United States
Josh creates software, for iOS and Windows.

He works at Black Pixel as a Senior 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[^].

See his website Josh Smith Digital[^].

Comments and Discussions

 
QuestionHow should `ICommand.CanExecuteChanged` be implemented? Pin
wakazula28-Sep-17 7:56
wakazula28-Sep-17 7:56 
BugDoes not work if focus changes Pin
Scorpionb9-Jan-15 5:52
Scorpionb9-Jan-15 5:52 
GeneralMy vote of 5 Pin
Member 401696524-May-12 2:17
Member 401696524-May-12 2:17 
QuestionHow to reference a command defined within a bound ViewModel instance? Pin
kainhart3-Nov-10 5:39
kainhart3-Nov-10 5:39 
AnswerRe: How to reference a command defined within a bound ViewModel instance? Pin
Josh Smith3-Nov-10 6:26
Josh Smith3-Nov-10 6:26 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pin
kainhart3-Nov-10 9:34
kainhart3-Nov-10 9:34 
AnswerRe: How to reference a command defined within a bound ViewModel instance? Pin
Sean A. Hanley5-Apr-12 12:12
Sean A. Hanley5-Apr-12 12:12 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pin
Gene Sivorot2-May-12 8:57
Gene Sivorot2-May-12 8:57 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pin
phatoni16-May-12 4:05
phatoni16-May-12 4:05 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pin
Tobias_H4-Feb-13 22:59
Tobias_H4-Feb-13 22:59 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pin
boohooo10-Feb-16 23:08
boohooo10-Feb-16 23:08 
GeneralRe: How to reference a command defined within a bound ViewModel instance? Pin
boohooo10-Feb-16 21:50
boohooo10-Feb-16 21:50 
GeneralIn regards to IsAsync Pin
nedruod1-Oct-08 3:22
nedruod1-Oct-08 3:22 
QuestionWhat about controls other than TextBox Pin
marmot824-Sep-08 4:14
marmot824-Sep-08 4:14 
AnswerRe: What about controls other than TextBox Pin
Josh Smith24-Sep-08 4:22
Josh Smith24-Sep-08 4:22 
AnswerRe: What about controls other than TextBox Pin
JvdP27-May-09 22:41
JvdP27-May-09 22:41 
GeneralCommandParameter Pin
cechode30-Aug-08 9:46
cechode30-Aug-08 9:46 
GeneralRe: CommandParameter Pin
Josh Smith30-Aug-08 10:37
Josh Smith30-Aug-08 10:37 
GeneralRe: CommandParameter Pin
cechode30-Aug-08 10:53
cechode30-Aug-08 10:53 
GeneralRe: CommandParameter Pin
Josh Smith30-Aug-08 10:55
Josh Smith30-Aug-08 10:55 
cechode wrote:
different parameters


You could pass in as the CommandGroup's parameter an array of objects, each of which is a parameter for a command in the group. Then have CommandGroup pull out the correct parameter before executing each child command, and pass the parameter into it. I haven't tested this out, but I'm sure there's a way to do it.

:josh:
My WPF Blog[^]
Sleep is overrated.

GeneralRe: CommandParameter Pin
cechode30-Aug-08 11:02
cechode30-Aug-08 11:02 
QuestionWhat a nice solution! Pin
moonie17-Jun-08 2:32
moonie17-Jun-08 2:32 
AnswerRe: What a nice solution! Pin
Josh Smith17-Jun-08 2:42
Josh Smith17-Jun-08 2:42 
GeneralThank you. Pin
Pete O'Hanlon5-May-08 9:58
subeditorPete O'Hanlon5-May-08 9:58 
GeneralRe: Thank you. Pin
Josh Smith5-May-08 10:06
Josh Smith5-May-08 10:06 

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

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