Click here to Skip to main content
Licence CPOL
First Posted 21 Apr 2008
Views 56,698
Downloads 493
Bookmarked 70 times

WPF Command-Pattern Applied

By Jani Giannoudis | 25 Apr 2008
Usage of the Command Pattern in a WPF application.
1 vote, 5.3%
1

2
3 votes, 15.8%
3
1 vote, 5.3%
4
14 votes, 73.7%
5
4.43/5 - 19 votes
1 removed
μ 4.35, σa 1.96 [?]

WpfCommandPatternApplied1.gif

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 EditingCommands
Command Repository
  • 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:

WPF Command Pattern Applied

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 Applied

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 ButtonCommand can be used to bind a Command to a Button control:

XAML

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

Code-Behind

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

CommandButton 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 command description Using WrapperCommand Yes
ToolTip Gesture ToolTip according to command description Yes Yes

Display of the image can be controlled using the class ButtonImage. The property ButtonCommand.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 MenuItemCommand allows to bind a Command to a MenuItem control:

XAML

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

Code-Behind

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

MenuItemCommand 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 command description Using WrapperCommand Yes
ToolTip Gesture ToolTip according to 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 Test

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();
    Paragraph firstParagraph = commandContext.Document.Blocks.FirstBlock as Paragraph;

    // 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(CommandRepository.Bindings )

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

  • 21 April 2008: Initial public release.
  • 23. April 2008:
    • Image for a MenuItemCommand
    • New classes ImageButton and MenuItemButton
    • New class WrapperCommand
    • New section: Points of Interest

License

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

About the Author

Jani Giannoudis

Software Developer (Senior)
Itenso GmbH
Switzerland Switzerland

Member
In 1989, I started programming using Visual Basic, Turbo Pascal, Assembler, Clipper and C. From 1994 to 2001, I was as head of development of a Win32-CAD system in C++. During a few mandates as project manager from 2002 to 2003, I started with .NET using C#.
 
In 2004, I established the company Itenso GmbH with assistance and development of software systems in different business domains. The solutions are based on different Microsoft Technologies like SQL-Server, VSTO, C#, ASP.NET, WPF and Silverlight.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 PinmemberAssil9:23 15 Nov '11  
QuestionWhat about adding an XAML CommandBinding? Pinmemberca0v18:24 26 Sep '09  
GeneralProblem with WindowsFormsHost Pinmemberagro_jupp22:38 29 Jun '09  
QuestionRe: Problem with WindowsFormsHost PinmemberJani Giannoudis19:44 30 Jun '09  
AnswerRe: Problem with WindowsFormsHost Pinmemberagro_jupp0:44 1 Jul '09  
AnswerRe: Problem with WindowsFormsHost PinmemberJani Giannoudis20:46 1 Jul '09  
GeneralRe: Problem with WindowsFormsHost Pinmemberagro_jupp21:49 1 Jul '09  
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-kun14:58 19 May '09  
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 Giannoudis21:46 19 May '09  
QuestionVery Good Work Pinmemberagro_jupp4:29 2 Apr '09  
AnswerRe: Very Good Work PinmemberJani Giannoudis1:38 3 Apr '09  
GeneralThanks for the code PinmemberEllis Whitehead5:49 5 Jul '08  
AnswerRe: Thanks for the code PinmemberJani Giannoudis13:18 5 Jul '08  
GeneralRe: Thanks for the code PinmemberEllis Whitehead5:55 7 Jul '08  
AnswerRe: Thanks for the code PinmemberJani Giannoudis6:44 7 Jul '08  
GeneralThe Lib style code PinmemberWei Xu11:35 30 May '08  
Jani,
 
It’s me again. This is another very useful article!
One suggestion: currently, the project contains 4 sub-projects. There are some codes hard-coded which makes it hard to generalize them into a library. By briefly looking at the program, the folders/projects of “command” and “input” can be put into a lib. However, the image resources are coded in such a way preventing the efforts without re-code it. It would be better if you can re-organize the entire project as the following.
theLib
CommandDemo
 
In this way, it would be much easier for a developer to grab the code.
 
Another $0.02 Smile | :)
 
Wei
AnswerRe: The Lib style code PinmemberJani Giannoudis20:20 30 May '08  

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.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120210.1 | Last Updated 25 Apr 2008
Article Copyright 2008 by Jani Giannoudis
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid