65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 命令模式应用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (29投票s)

2008年4月21日

CPOL

6分钟阅读

viewsIcon

145497

downloadIcon

2247

在 WPF 应用程序中应用命令模式。

WPF Command Pattern

介绍 

WPF 命令为将 GoF 命令模式集成到应用程序中提供了基础。本文介绍了一种实现以下目标的方法

程序架构

  • 在层次结构中创建功能单元
  • 减少大型应用程序的复杂性
  • 自动化测试
  • 模式和命令的可重用性

命令描述

  • 命令描述应与所使用的 GUI 无关
  • 命令应易于本地化

命令运行时行为

  • 静态参数(每个命令实例)
  • 上下文敏感的运行时数据
  • 源提供的运行时参数 (ICommandSource.CommandParameter)

UI 集成

  • 现有命令源(ButtonMenuItem)的图像和带有手势的工具提示
  • Button 控件禁用状态的可视化
  • 通过 XAML 或代码隐藏选择性绑定
  • 集成现有的 WPF 命令,例如 System.Windows.Documents.EditingCommands

命令存储库

  • 集中管理命令
  • 命令的动态注册
  • 访问所有命令
  • 命令执行跟踪

所附示例演示了如何在 RTF 编辑器中使用命令模式

WPF Command Pattern Editor

测试项目还展示了如何对命令进行单元测试。控件 CommandComboBox 展示了如何实现自定义 ICommandSource

命令

Command,本身派生自 RoutedUICommand,为所有命令提供了基础。它作为开发单个命令结构的公共祖先。以下概述显示了 RTF 编辑器的一些命令类

注意:此类层次结构仅用于演示命令结构,不适用于 RTF 编辑器的生产级使用。

命令类的实现紧凑且具有描述性,并为未来扩展步骤中的分析提供了明确的入口点

// ------------------------------------------------------------------------
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

WrapperCommand 允许集成现有的 WPF 命令。以下示例展示了如何重用系统命令 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

命令运行时行为

在命令执行期间,会创建一个命令上下文,该上下文封装所有运行时参数。下图记录了运行时行为

WPF Command Pattern Runtime

CommandContext 实例的创建委托给 Command 类本身,以支持单个上下文类型。RTF 编辑器示例为此目的使用了 ApplicationCommandContext(错误处理)和 EditorCommandContext(访问 RichTextBox)类。

注意:将所有运行时值组合到 CommandContext 类中增强了未来修改的灵活性:可以在不更新所有现有命令的方法签名的情况下扩展 CommandContext

接下来将展示如何将 CommandContext 用于单元测试。

属性 Command.Target 提供了一种为每个命令实例设置参数的方法。由于命令的生命周期是静态的,因此此 Command.Target 属性对应于一个 static 参数。

UI 集成

使用属性 Command.HasRequirements(默认值 = True)允许指定启用/禁用是否与命令相关。如果没有要求,命令始终处于活动状态,并且 Command.CanExecute() 将永远不会执行。

按钮命令

可以使用类 ButtonCommandProviderCommand 附加到 Button 控件

XAML

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

代码隐藏

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

ButtonCommandProvider 提供了各种功能方面,可以根据需要激活/停用

Button 功能 路由命令 命令
Image 插入按钮图像(在 Visual Studio XAML 设计器中预览) 使用 WrapperCommand
启用 禁用状态的可视化 按钮内容 按钮内容
工具提示 根据命令描述的工具提示 使用 WrapperCommand
工具提示手势 根据命令描述的工具提示手势 

可以使用 ButtonImage 类控制图像的显示。属性 ButtonCommandProvider.DisabledOpacity 确定非活动按钮的内容将如何显示。

图像的来源由 CommandImageService 类确定。此外,属性 Command.ImageUri 提供了一种为图像设置另一个来源的方法。默认情况下,按钮图像如下

  • PNG 格式
  • 命令程序集中的嵌入资源
  • 位于“Images”文件夹中

实用程序 CommandToolTipService 控制按钮工具提示的格式。

菜单项命令

可以使用 MenuItemCommandProvider 类将 Command 附加到 MenuItem 控件

XAML

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

代码隐藏 

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

MenuItemCommandProvider 提供了各种功能方面,可以根据需要激活/停用

MenuItem 功能 路由命令 命令
Image 在菜单项旁边显示图像 使用 WrapperCommand
工具提示 根据命令描述的工具提示 使用 WrapperCommand
工具提示手势 根据命令描述的工具提示手势

可以使用 MenuItemImage 类控制图像的显示。

工具提示的格式由 CommandToolTipService 类控制。

命令存储库

CommandRepository 提供对所有命令和命令绑定的访问。事件 CommandExecutingCommandExecuted 允许跟踪命令执行。RTF 编辑器“帮助”菜单中的命令“命令统计”是一个简单的实现示例,它只列出所有注册的命令。RTF 编辑器的状态栏显示有关正在运行的命令和已执行命令总数的信息。

单元测试

CommandContext 的基础上,我们现在有一种方法来实现单独的测试用例。所附测试项目的以下测试方法展示了如何自动化命令测试

// ----------------------------------------------------------------------
[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

自定义命令控件

根据 MSDN 中的描述,控件 CommandComboBox 展示了如何创建一个用作命令源的控件。选择列表项将执行命令。为了识别条目是否由用户选择(而不是通过编程方式),UIElementInput 类提供了相关的事件信息。

用法

要使用命令系统,建议采用以下方法

  1. 设计并实现命令类层次结构(可选:抽象类和单个命令上下文)
  2. 设计并实现命令单元测试(可选)
  3. 创建 CommandCollection:将每个命令与 CommandDescription 进行静态绑定
  4. 嵌入按钮图像资源(可选)
  5. 在 XAML 中添加 Command 引用
  6. 在应用程序启动时配置工具提示格式(可选)
  7. 在应用程序启动时设置 CommandRepositoryCommandRepository.AddRange()
  8. 在应用程序窗口中设置 CommandBindingCommandBindings.AddRange()

关注点

本文带来的更多见解

  • 根据 CommandComboBox 类实例的状态禁用 ComboBox
  • 内容驱动的启用:一旦字体大小达到 25pt,命令 IncreaseFontSize 就会被禁用
  • 识别 RTF 选中内容中的不同格式:EditFontSizeCommand.GetFontSize()
  • RichTextEditorCommandsCommandDescription 的本地化
  • 通过 CommandWindow.CurrentCommandInfoDependencyProperty 从 XAML 访问类值
  • 在选择菜单项时,使用 XAML Style EventSetter 调用 CommandWindow.OnStateUpdate()CommandWindow.OnStatusReset() 更新状态栏
  • 通过类继承进行 XAML 样式设置,如示例 ButtonImageMenuItemImage

历史

  • 2012 年 4 月 23 日 - v1.1.0.0
    • ButtonCommand 重命名为 ButtonCommandProvider
    • MenuItemCommand 重命名为 MenuItemCommandProvider
    • 重构了源代码
    • 更新了文章并修复了格式
  • 2008 年 4 月 23 日
    • MenuItemCommand 的图像
    • 新类 ImageButtonMenuItemButton
    • 新类 WrapperCommand
    • 新部分:兴趣点
  • 2008 年 4 月 21 日
    • 首次公开发布
© . All rights reserved.