Click here to Skip to main content
Click here to Skip to main content
Go to top

WPF Command Pattern Applied

, 23 Apr 2012
Rate this:
Please Sign up or sign in to vote.
Applying of the Command pattern in a WPF application.

WPF Command Pattern

Introduction 

WPF Commanding offers the base for integration of the GoF Command pattern into an application. This article describes an approach with the following goals:

Program Architecture

  • Creation of functional units in hierarchies
  • Reduction of complexity in large applications
  • Automated testing
  • Reusability of the pattern and the commands

Command Description

  • Description of a command should be neutral to the GUI in use
  • Commands should be easy to localize

Command Runtime Behaviour

  • Static parameter (per command instance)
  • Context sensitive runtime data
  • Source provided runtime parameters (ICommandSource.CommandParameter)

UI Integration

  • Images and ToolTips with Gestures for existing command sources (Button and MenuItem)
  • Visualization of a Disabled state for Button controls
  • Selectable binding via XAML or Code-Behind
  • Integration of existing WPF commands as for example the System.Windows.Documents.EditingCommands

Command Repository

  • Centralizing the management of the commands
  • Dynamic registration of commands
  • Access to all commands
  • Tracking of command execution

The attached example demonstrates how to use the Command pattern in an RTF editor:

WPF Command Pattern Editor

The test project also shows how to unit test a command. The control CommandComboBox shows how to implement a custom ICommandSource.

Command

The class Command, itself derived from RoutedUICommand, provides the foundation for all commands. It serves as a common ancestor for developing individual command structures. The following overview shows some command classes of the RTF editor:

Note: This class hierarchy serves only for demonstrating command structures, and is not intended for production level usage of an RTF editor.

The implementation of a command class is compact and descriptive, and offers a well defined entry point for analysis in future extension steps:

// ------------------------------------------------------------------------
public class EditUndo : EditorCommand
{

    // ----------------------------------------------------------------------
    public EditUndo( Type ownerType, CommandDescription description ) :
           base( "EditUndo", ownerType, description )
    {
    } // EditUndo

    // ----------------------------------------------------------------------
    protected override bool OnCanExecute( EditorCommandContext commandContext )
    {
      if ( !base.OnCanExecute( commandContext ) )
      {
        return false;
      }
      return commandContext.TextBox.CanUndo;
    } // OnCanExecute

    // ----------------------------------------------------------------------
    protected override void OnExecute( EditorCommandContext commandContext )
    {
      commandContext.TextBox.Undo();
    } // OnExecute

} // class EditUndo

The class WrapperCommand allows to integrate existing WPF commands. The following example shows how to reuse the system command EditingCommands.IncreaseIndentation:

// ------------------------------------------------------------------------
public class IncreaseIndentation : WrapperCommand
{

    // ----------------------------------------------------------------------
    public IncreaseIndentation( Type ownerType, CommandDescription description ) :
      base( EditingCommands.IncreaseIndentation, ownerType, description )
    {
    } // IncreaseIndentation

    // ----------------------------------------------------------------------
    protected override bool OnCanExecute( CommandContext commandContext )
    {
      if ( !base.OnCanExecute( commandContext ) )
      {
        return false;
      }

      // add some additional enabling conditions

      return true;
    } // OnCanExecute

} // class IncreaseIndentation

Command Runtime Behaviour

During the execution of a command, a command context is created which encapsulates all the runtime parameters. The following diagram documents the runtime behavior:

WPF Command Pattern Runtime

Creation of a CommandContext instance is delegated to the Command class itself, to support individual context types. The RTF editor example uses the classes ApplicationCommandContext (error handling) and EditorCommandContext (access to the RichTextBox) for this purpose.

Note: Combining all the runtime values into the class CommandContext enhances flexibility for future modifications: CommandContext can be extended without having to update the method signatures of all existing commands.

Further down it will be shown how to use the CommandContext for Unit Testing.

The property Command.Target offers a way to set a parameter per command instance. Because the life cycle of a command is static, this Command.Target property corresponds to a static parameter.

UI Integration

Using the property Command.HasRequirements (Default=True) allows to specify whether Enabling/Disabling is relevant for the command. If there are no requirements, the command is always active and Command.CanExecute() will never be executed.

Button Command

The class ButtonCommandProvider can be used, to attach a Command to a Button control:

XAML

<Button cmd:ButtonCommandProvider.Command="{x:Static rtf:RichTextEditorCommands.EditUndo}" />

Code-Behind

Button undoButton = new Button();
ButtonCommandProvider.SetCommand( undoButton, RichTextEditorCommands.EditUndo );

ButtonCommandProvider offers various functionality aspects which can be activated/deactivated on an optional basis:

Button Functionality RoutedCommand Command
Image Insert a button image (preview in Visual Studio XAML Designer) Using WrapperCommand Yes
Enabling Visualization of the Disabled state Button-Content Button-Content
ToolTip ToolTip according to the command description Using WrapperCommand Yes
ToolTip Gesture ToolTip gesture according to the command description  Yes Yes

Display of the image can be controlled using the class ButtonImage. The property ButtonCommandProvider.DisabledOpacity determines how the content of an inactive button will be displayed.

The source of an image is determined by the class CommandImageService. In addition to that, the property Command.ImageUri offers a way to set another source for images. By default, button images are taken as:

  • In PNG format
  • Embedded resource in the assembly of the command
  • Located in the 'Images' folder

The utility CommandToolTipService controls the format of the button ToolTip.

MenuItem Command

Using the class MenuItemCommandProvider can be used, to attach a Command to a MenuItem control:

XAML

<Button cmd:MenuItemCommandProvider.Command="{x:Static rtf:RichTextEditorCommands.EditUndo}" />

Code-Behind 

MenuItem undoMenuItem = new MenuItem();
MenuItemCommandProvider.SetCommand( undoMenuItem, RichTextEditorCommands.EditUndo );

MenuItemCommandProvider offers various functionality aspects which can be activated/deactivated on an optional basis:

MenuItem Functionality RoutedCommand Command
Image Display an image along with the menu entry Using WrapperCommand Yes
ToolTip ToolTip according to the command description Using WrapperCommand Yes
ToolTip Gesture ToolTip gesture according to the command description Yes Yes

Display of the image can be controlled using the class MenuItemImage.

The format of the ToolTip is controlled by the class CommandToolTipService.

Command Repository

The CommandRepository offers access to all the commands and command bindings. The events CommandExecuting and CommandExecuted allow tracking of command executions. The command 'Command Statistics' in the 'Help' menu of the RTF editor is a sample implementation which simply lists all registered commands. The statusbar of the RTF editor displays information about the running command and the total number of executed commands.

Unit Testing

With the basis of the CommandContext, we now have a way to realize individual test cases. The following testing method of the attached test project shows how to automate testing of a command:

// ----------------------------------------------------------------------
[TestMethod]
public void TestToggleBoldDefault()
{
    EditorCommandContext commandContext = CreateCommandContext();

    // setup selection
    TextRange startContent = new TextRange( commandContext.Document.ContentStart,
                                            commandContext.Document.ContentEnd );
    commandContext.Selection.Select( startContent.Start, startContent.End );
    commandContext.Selection.ApplyPropertyValue( FlowDocument.FontWeightProperty,
                                                 FontWeights.Normal );
    string startText = startContent.Text;

    // run the command
    FontWeight defaultFontWeigth = RichTextEditorCommands.ToggleBold.BoldFontWeight;
    RichTextEditorCommands.ToggleBold.Execute( commandContext );

    // tests
    TextRange endContent = new TextRange( commandContext.Document.ContentStart,
                                          commandContext.Document.ContentEnd );
    string endText = endContent.Text;
    Assert.AreEqual( startText, endText );

    object selectionFontWeight =
     commandContext.Selection.GetPropertyValue( FlowDocument.FontWeightProperty );
    Assert.AreEqual( defaultFontWeigth, selectionFontWeight );
} // TestToggleBoldDefault

// ----------------------------------------------------------------------
private EditorCommandContext CreateCommandContext()
{
    // document
    FlowDocument flowDocument = new FlowDocument();

    for ( int i = 0; i < 10; i++ )
    {
      Paragraph paragraph = new Paragraph();
      for ( int n = 0; n < 5; n++ )
      {
        paragraph.Inlines.Add( new Run( "Paragraph Text " +
                                        n.ToString() + " " ) );
      }
      flowDocument.Blocks.Add( paragraph );
    }

    // editor
    RichTextBox richTextBox = new RichTextBox();
    richTextBox.Document = flowDocument;

    // command context
    return new EditorCommandContext( richTextBox );
} // CreateCommandContext

Custom Command Control

According to the description in MSDN, the control CommandComboBox shows the creation of a control which serves as a source for commands. Selection of a list entry will execute the command. To recognize whether the entry has been selected by the user (as opposed to a programmed way), the class UIElementInput provides the relevant event information.

Usage

To use the command system, the following approach is suggested:

  1. Design and implement the command class hierarchy (optional: abstract classes and individual command contexts)
  2. Design and implement the command unit tests (optional)
  3. Create a CommandCollection: static binding of each command with a CommandDescription
  4. Embed button image resources (optional)
  5. Add Command references in XAML
  6. Configure the tooltip format upon application start (optional)
  7. Setup the CommandRepository at application start: CommandRepository.AddRange()
  8. Setup the CommandBinding in the application window: CommandBindings.AddRange()

Points of Interest

Further insights resulting from this article:

  • Disabling a ComboBox depending on the state of an instance of the class CommandComboBox
  • Content driven enabling: As soon as the font size reaches 25pt, the command IncreaseFontSize gets disabled
  • Recognizing differing formats in the RTF selection: EditFontSizeCommand.GetFontSize()
  • Localization of a CommandDescription in RichTextEditorCommands
  • Access to class values from XAML through the DependencyProperty of CommandWindow.CurrentCommandInfo
  • Updating the statusbar during selection of a menu entry using a XAML Style EventSetter with a call to CommandWindow.OnStateUpdate() and CommandWindow.OnStatusReset()
  • XAML Styling through class inheritance as in the examples ButtonImage and MenuItemImage

History

  • 23th April, 2012 - v1.1.0.0
    • Renamed ButtonCommand to ButtonCommandProvider
    • Renamed MenuItemCommand to MenuItemCommandProvider
    • ReSharped source code
    • Refreshed article and fixed formatting
  • 23th April, 2008
    • Image for a MenuItemCommand
    • New classes ImageButton and MenuItemButton
    • New class WrapperCommand
    • New section: Points of Interest
  • 21th April, 2008
    • Initial public release

License

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

Share

About the Author

Jani Giannoudis
Software Developer (Senior)
Switzerland Switzerland
Jani is Co-founder of Meerazo.com, a free service which allows to share resources like locations, things, persons and their services in a cooperating group of people.

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert20-Sep-12 2:21 
AnswerRe: My vote of 5 PinmvpJani Giannoudis20-Sep-12 2:35 
GeneralExcellent Article! Pinmemberxzz019514-May-12 6:44 
AnswerRe: Excellent Article! PinmvpJani Giannoudis14-May-12 9:08 
GeneralMy vote of 5 PinmemberAssil15-Nov-11 8:23 
QuestionWhat about adding an XAML CommandBinding? Pinmemberca0v26-Sep-09 17:24 
GeneralProblem with WindowsFormsHost Pinmemberagro_jupp29-Jun-09 21:38 
QuestionRe: Problem with WindowsFormsHost PinmemberJani Giannoudis30-Jun-09 18:44 
AnswerRe: Problem with WindowsFormsHost Pinmemberagro_jupp30-Jun-09 23:44 
AnswerRe: Problem with WindowsFormsHost PinmemberJani Giannoudis1-Jul-09 19:46 
GeneralRe: Problem with WindowsFormsHost Pinmemberagro_jupp1-Jul-09 20:49 
GeneralError 3 Metadata file 'CSharp3rdPCodes=-\WPF\WpfCmdPattern\Commands\bin\Debug\Itenso.Solutions.Community.Commands.dll' could not be found C:\-=CSharp3rdPCodes=-\WPF\WpfCmdPattern\CommandDemo\CSC PinmemberSazuke-kun19-May-09 13:58 
AnswerRe: Error 3 Metadata file 'CSharp3rdPCodes=-\WPF\WpfCmdPattern\Commands\bin\Debug\Itenso.Solutions.Community.Commands.dll' could not be found C:\-=CSharp3rdPCodes=-\WPF\WpfCmdPattern\CommandDemo\CSC PinmemberJani Giannoudis19-May-09 20:46 
QuestionVery Good Work Pinmemberagro_jupp2-Apr-09 3:29 
AnswerRe: Very Good Work PinmemberJani Giannoudis3-Apr-09 0:38 
GeneralThanks for the code PinmemberEllis Whitehead5-Jul-08 4:49 
AnswerRe: Thanks for the code PinmemberJani Giannoudis5-Jul-08 12:18 
GeneralRe: Thanks for the code PinmemberEllis Whitehead7-Jul-08 4:55 
AnswerRe: Thanks for the code PinmemberJani Giannoudis7-Jul-08 5:44 
GeneralThe Lib style code PinmemberWei Xu30-May-08 10:35 
AnswerRe: The Lib style code PinmemberJani Giannoudis30-May-08 19:20 

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 | Mobile
Web02 | 2.8.140926.1 | Last Updated 23 Apr 2012
Article Copyright 2008 by Jani Giannoudis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid