指挥概述

命令是 Windows Presentation Foundation(WPF)中的一种输入机制,它提供比设备输入更多的语义级别的输入处理。 命令的示例包括许多应用程序中常见的“复制”、“剪切”和“粘贴”操作。

本概述定义 WPF 中的哪些命令、哪些类是命令模型的一部分,以及如何在应用程序中使用和创建命令。

本主题包含以下部分:

什么是命令

命令有多个用途。 第一个目的是将语义和调用命令的对象与执行命令的逻辑分离。 这允许多个和不同的源调用相同的命令逻辑,并允许为不同的目标自定义命令逻辑。 例如,许多应用程序中的编辑操作复制剪切粘贴,如果通过命令实现,可以通过不同的用户操作来调用。 应用程序可能允许用户通过单击按钮、选择菜单中的项或使用组合键(如 Ctrl+X)来剪切所选对象或文本。 通过使用命令,可以将每种类型的用户操作绑定到同一逻辑。

命令的另一个用途是指示动作是否可用。 继续举例说明剪切对象或文本,仅在选中内容时,此操作才有意义。 如果用户在没有选择任何内容的情况下尝试剪切对象或文本,则不会发生任何事情。 若要向用户指示这一点,许多应用程序会禁用按钮和菜单项,以便用户知道是否可以执行作。 命令可以通过实现 CanExecute 方法来指示作是否可行。 按钮可以订阅CanExecuteChanged事件,如果CanExecute返回false,则禁用;如果CanExecute返回true,则启用。

命令的语义可以在应用程序和类之间保持一致,但作的逻辑特定于所执行的特定对象。 组合键 CTRL+X 在文本类、图像类和 Web 浏览器中调用 Cut 命令,但执行剪切作的实际逻辑由执行 剪切 的应用程序定义。 RoutedCommand 使客户端能够实现逻辑。 文本对象可将所选文本剪切到剪贴板,而图像对象可以剪切所选图像。 当应用程序处理 Executed 事件时,它有权访问命令的目标,并且可以根据目标的类型采取适当的作。

WPF 中的简单命令示例

在 WPF 中使用命令的最简单方法是使用一个命令库类中的预定义 RoutedCommand ;使用具有本机支持的控件来处理命令;使用具有本机支持的控件来调用命令。 该 Paste 命令是类中的 ApplicationCommands 预定义命令之一。 TextBox 控件具有用于处理 Paste 命令的内置逻辑。 该MenuItem类对调用命令具有原生支持。

以下示例演示如何设置一个 MenuItem,以便在单击时调用 Paste 命令,并在 TextBox 上执行该命令,假设 TextBox 具有键盘焦点。

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()

' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)

' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste

WPF 命令中的四个主要概念

WPF 中的路由命令模型可以分为四个主要概念:命令、命令源、命令目标和命令绑定:

  • 该命令是要执行的操作。

  • 命令源是调用命令的对象。

  • 命令目标是执行命令的对象。

  • 命令绑定是将命令逻辑映射到命令的对象。

在前面的示例中, Paste 命令是命令, MenuItem 是命令源, TextBox 是命令目标,命令绑定由 TextBox 控件提供。 值得注意的是,作为命令目标类的控件并不总是提供 CommandBindingCommandBinding 通常必须由应用程序开发人员创建,而 CommandBinding 可能会附加到命令目标的父级。

指令

WPF 中的命令是通过实现 ICommand 接口创建的。 ICommand 公开两个方法, Execute以及 CanExecute一个事件 CanExecuteChangedExecute 执行与命令关联的操作。 CanExecute 确定该命令是否可以在当前命令目标上执行。 CanExecuteChanged 会被引发,如果集中指令操作的命令管理者检测到命令源中发生可能使已引发但尚未由命令绑定执行的命令失效的更改。 WPF 对 ICommand 的实现是 RoutedCommand 类,这是本概述的重点。

WPF 中输入的主要源是鼠标、键盘、墨迹和路由命令。 面向设备的输入使用一个 RoutedEvent 通知应用程序页中的对象已发生输入事件。 一个RoutedCommand没有什么不同。 RoutedCommandExecuteCanExecute 方法不包含命令的应用程序逻辑,而是引发路由事件,这些事件会在元素树中隧道和冒泡,直到遇到具有 CommandBinding 的对象。 CommandBinding包含这些事件的处理程序,它是执行命令的处理程序。 有关 WPF 中的事件路由的详细信息,请参阅 路由事件概述

方法Execute在命令目标上引发PreviewExecutedExecuted事件。 在RoutedCommand上的CanExecute方法会在命令目标上引发CanExecutePreviewCanExecute事件。 这些事件在元素树中传递和冒泡,直到遇到具有该特定命令的对象CommandBinding

WPF 提供了一组分布在多个类中的常用路由命令:MediaCommands、、ApplicationCommandsNavigationCommands、和ComponentCommandsEditingCommands。 这些类只由RoutedCommand对象构成,而不包括命令的实现逻辑。 实现逻辑是执行命令的对象的责任。

命令源

命令源是调用命令的对象。 命令源的示例包括MenuItemButtonKeyGesture

WPF 中的命令源通常实现 ICommandSource 接口。

ICommandSource 公开三个属性: CommandCommandTargetCommandParameter

实现ICommandSource的 WPF 类是ButtonBaseMenuItemHyperlinkInputBindingButtonBaseMenuItemHyperlink 在被单击时会调用命令,而 InputBinding 在执行与它关联的 InputGesture 时会调用命令。

以下示例演示如何在ContextMenu中使用MenuItem作为Properties命令的命令源。

<StackPanel>
  <StackPanel.ContextMenu>
    <ContextMenu>
      <MenuItem Command="ApplicationCommands.Properties" />
    </ContextMenu>
  </StackPanel.ContextMenu>
</StackPanel>
StackPanel cmdSourcePanel = new StackPanel();
ContextMenu cmdSourceContextMenu = new ContextMenu();
MenuItem cmdSourceMenuItem = new MenuItem();

// Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu;
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem);

// Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties;
Dim cmdSourcePanel As New StackPanel()
Dim cmdSourceContextMenu As New ContextMenu()
Dim cmdSourceMenuItem As New MenuItem()

' Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem)

' Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties

通常,命令源将侦听 CanExecuteChanged 事件。 此事件通知命令源,命令在当前命令目标上执行的能力可能已更改。 命令源可以使用该方法查询当前RoutedCommandCanExecute状态。 然后,如果命令无法执行,则命令源可以禁用自身。 一个示例是,当命令无法执行时,MenuItem 会自动变灰以示禁用。

可将 An InputGesture 用作命令源。 WPF 中的两种类型的输入手势是 KeyGestureMouseGesture。 可以将 KeyGesture 想象为键盘快捷方式,例如 Ctrl+C。 A KeyGesture 由一个 Key 和一组 ModifierKeys 组成。 A MouseGesture 由一个 MouseAction 和可选的一组 ModifierKeys 组成。

为了使命令 InputGesture 充当命令源,它必须与命令相关联。 可通过几种方法完成此作。 一种方法是使用 InputBinding.

以下示例演示如何在KeyGestureRoutedCommand之间创建KeyBinding

<Window.InputBindings>
  <KeyBinding Key="B"
              Modifiers="Control" 
              Command="ApplicationCommands.Open" />
</Window.InputBindings>
KeyGesture OpenKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

KeyBinding OpenCmdKeybinding = new KeyBinding(
    ApplicationCommands.Open,
    OpenKeyGesture);

this.InputBindings.Add(OpenCmdKeybinding);
Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

Dim OpenCmdKeybinding As New KeyBinding(ApplicationCommands.Open, OpenKeyGesture)

Me.InputBindings.Add(OpenCmdKeybinding)

另一种将InputGesture关联到RoutedCommand的方法是将InputGesture添加到InputGestureCollectionRoutedCommand上。

下面的示例演示如何将KeyGesture添加到InputGestureCollectionRoutedCommand中。

KeyGesture OpenCmdKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);
Dim OpenCmdKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture)

命令绑定

CommandBinding 命令与实现命令的事件处理程序相关联。

CommandBinding 类包含一个 Command 属性,以及 PreviewExecutedExecutedPreviewCanExecuteCanExecute 事件。

Command 是与 CommandBinding 相关联的命令。 附加到 PreviewExecutedExecuted 事件的事件处理程序实现命令逻辑。 附加到 PreviewCanExecute 事件和 CanExecute 事件的事件处理程序确定该命令是否可以在当前命令目标上执行。

以下示例演示如何在应用程序的Window上创建一个CommandBindingCommandBindingOpen命令与ExecutedCanExecute处理程序相关联。

<Window.CommandBindings>
  <CommandBinding Command="ApplicationCommands.Open"
                  Executed="OpenCmdExecuted"
                  CanExecute="OpenCmdCanExecute"/>
</Window.CommandBindings>
// Creating CommandBinding and attaching an Executed and CanExecute handler
CommandBinding OpenCmdBinding = new CommandBinding(
    ApplicationCommands.Open,
    OpenCmdExecuted,
    OpenCmdCanExecute);

this.CommandBindings.Add(OpenCmdBinding);
' Creating CommandBinding and attaching an Executed and CanExecute handler
Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)

Me.CommandBindings.Add(OpenCmdBinding)

接下来,将创建ExecutedRoutedEventHandlerCanExecuteRoutedEventHandler。 此时 ExecutedRoutedEventHandler 会打开一个 MessageBox,显示一个字符串,指出命令已执行。 CanExecuteRoutedEventHandlerCanExecute 属性设置为 true

void OpenCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
    String command, targetobj;
    command = ((RoutedCommand)e.Command).Name;
    targetobj = ((FrameworkElement)target).Name;
    MessageBox.Show("The " + command +  " command has been invoked on target object " + targetobj);
}
Private Sub OpenCmdExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
    Dim command, targetobj As String
    command = CType(e.Command, RoutedCommand).Name
    targetobj = CType(sender, FrameworkElement).Name
    MessageBox.Show("The " + command + " command has been invoked on target object " + targetobj)
End Sub
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}
Private Sub OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
    e.CanExecute = True
End Sub

A CommandBinding 附加到特定对象,例如应用程序的根 Window 或控件。 CommandBinding要附加的对象定义绑定的范围。 例如,附加到命令目标上级的 CommandBinding 可以被 Executed 事件访问,但附加到命令目标子代的 CommandBinding 不能被访问。 这是一种RoutedEvent从引发事件的对象中进行隧道和冒泡的机制的直接后果。

在某些情况下,CommandBinding 会附加到命令目标本身,例如 TextBox 类和 CutCopyPaste 命令。 不过,通常情况下,将 CommandBinding 附加到命令目标的上级(如主Window对象或 Application 对象)更为方便,尤其是在同一CommandBinding可以用于多个命令目标时。 这些是创建命令基础结构时需要考虑的设计决策。

指令目标

命令目标是执行命令的元素。 关于RoutedCommand,命令目标是ExecutedCanExecute元素的路由起始点。 如前所述,在 WPF 中,仅当ICommandSource上的CommandTarget属性为RoutedCommand时,ICommand适用。 如果在某个ICommandSource上设置了CommandTarget,且相应的命令不是RoutedCommand,则忽略命令目标。

命令源可以显式设置命令目标。 如果未定义命令目标,具有键盘焦点的元素将用作命令目标。 将元素与键盘焦点一起使用作为命令目标的好处之一是,它允许应用程序开发人员使用相同的命令源在多个目标上调用命令,而无需跟踪命令目标。 例如,如果在具有TextBox控件和PasswordBox控件的应用程序中调用Paste命令,则目标可以是TextBoxPasswordBox,具体取决于哪个控件具有键盘焦点。

以下示例演示如何在标记和后台代码中显式设置命令目标。

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste"
              CommandTarget="{Binding ElementName=mainTextBox}" />
  </Menu>
  <TextBox Name="mainTextBox"/>
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()

' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)

' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste

命令管理器

CommandManager 用于执行多个与命令相关的功能。 它提供一组静态方法,用于向/从特定元素添加和删除PreviewExecutedExecutedPreviewCanExecute、以及CanExecute事件处理程序。 它提供了一种将 CommandBindingInputBinding 对象注册到特定类的方法。 通过 CommandManager,它还提供了一种通过 RequerySuggested 事件在命令需要引发 CanExecuteChanged 事件时通知命令的方法。

InvalidateRequerySuggested 方法强制 CommandManager 引发 RequerySuggested 事件。 这在需要禁用/启用命令之条件下非常有用,但这些条件并不是 CommandManager 所知晓的。

命令库

WPF 提供一组预定义的命令。 命令库由以下类组成:ApplicationCommands、、NavigationCommandsMediaCommandsEditingCommandsComponentCommands。 这些类提供命令,例如CutBrowseBackBrowseForwardPlayStopPause

其中许多命令包括一组默认输入绑定。 例如,如果指定应用程序处理复制命令,则会自动获取键盘绑定“CTRL+C”,还可以获取其他输入设备的绑定,例如平板电脑笔势和语音信息。

使用 XAML 引用各种命令库中的命令时,通常可以省略公开静态命令属性的库类的类名。 通常,命令名称以字符串形式通常是明确的,并且所有者类型的存在是为了对命令进行逻辑分组,但不用于消除歧义。 例如,可以指定 Command="Cut" 而不是更冗长的 Command="ApplicationCommands.Cut"。 这是在 WPF XAML 处理器中为命令构建的便利机制(更确切地说,它是 WPF XAML 处理器在加载时引用的类型转换器行为 ICommand)。

创建自定义命令

如果命令库类中的命令不符合你的需求,则可以创建自己的命令。 可通过两种方法创建自定义命令。 第一个是从头开始实现 ICommand 接口。 另一种更常见的方法是创建一个RoutedCommand或一个RoutedUICommand

有关创建自定义 RoutedCommand的示例,请参阅 “创建自定义 RoutedCommand 示例”。

另请参阅