![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Intermediate
License: The Code Project Open License (CPOL)
WPF Command-Pattern AppliedBy Jani GiannoudisUsage of the Command Pattern in a WPF application. |
C# (C# 3.0), Windows, .NET (.NET 3.0), WPF
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

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 |
|
| Command Description |
|
| Command Runtime Behaviour |
|
| UI Integration |
|
| Command Repository |
|
The attached example demonstrates how to use the Command pattern in an RTF editor:
The test project also shows how to unit test a command. The control CommandComboBox shows how to implement a custom ICommandSource.
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
During the execution of a command, a command context is created which encapsulates all the runtime parameters. The following diagram documents the runtime behavior:
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.
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.
The class ButtonCommand can be used to bind a Command to a Button control:
<Button input:ButtonCommand.Command="{x:Static cmd:RichTextEditorCommands.EditUndo}" />
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:
The utility CommandToolTipService controls the format of the button ToolTip.
Using the class MenuItemCommand allows to bind a Command to a MenuItem control:
<Button input:MenuItemCommand.Command="{x:Static cmd:RichTextEditorCommands.EditUndo}" />
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.
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.
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
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.
To use the command system, the following approach is suggested:
CommandCollection: static binding of each command with a CommandDescription Command references in XAML CommandRepository at application start: CommandRepository.AddRange() CommandBinding in the application window: CommandBindings.AddRange(CommandRepository.Bindings ) Further insights resulting from this article:
ComboBox depending on the state of an instance of the class CommandComboBox IncreaseFontSize gets disabled EditFontSizeCommand.GetFontSize() CommandDescription in RichTextEditorCommands DependencyProperty of CommandWindow.CurrentCommandInfo Style EventSetter with a call to CommandWindow.OnStateUpdate() and CommandWindow.OnStatusReset() ButtonImage and MenuItemImage MenuItemCommand ImageButton and MenuItemButton WrapperCommand
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 25 Apr 2008 Editor: Sean Ewington |
Copyright 2008 by Jani Giannoudis Everything else Copyright © CodeProject, 1999-2009 Web16 | Advertise on the Code Project |