命令概述

更新:2007 年 11 月

命令是 Windows Presentation Foundation (WPF) 中的输入机制,它提供的输入处理比设备输入具有更高的语义级别。例如,在许多应用程序中都能找到的“复制”、“剪切”和“粘贴”操作就是命令。

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

本主题包括下列各节。

  • 什么是命令?
  • WPF 中的简单命令示例
  • WPF 命令中的四个主要概念
  • 命令库
  • 创建自定义命令
  • 相关主题

什么是命令?

WPF 中的命令系统基于 RoutedCommandRoutedEvent。命令不必是 RoutedCommand,可以仅仅实现 ICommand(将对此进行讨论),但是本概述中的大部分内容都集中介绍 RoutedCommand 模型。

命令与附加到按钮或计时器的简单事件处理程序的区别在于:命令将操作的语义和发起方与其逻辑分开。这使得多个完全不同的源可以调用相同的命令逻辑,并使得可以针对不同的目标对命令逻辑进行自定义。

例如,在许多应用程序中都能找到的编辑操作“复制”、“剪切”和“粘贴”就是命令。命令的语义在所有的应用程序和类中是一致的,但是操作的逻辑是所作用于的特定对象所特有的。Ctrl+X 组合键在文本类、图像类以及 Web 浏览器中调用“剪切”命令,但是执行“剪切”操作的实际逻辑是由发生剪切的对象或应用程序定义的,而不是在调用该命令的源上定义的。文本对象可以将选定的文本剪切到剪贴板中,图像对象可以剪切选定的图像,但是同一个命令源 KeyGestureToolBar 按钮可用于在两个类上调用此命令。

WPF 中的简单命令示例

在 WPF 中使用命令的最简单方法是从某个命令库类中使用预定义的 RoutedCommand;使用对处理此命令具有固有支持的控件;以及使用对调用命令具有固有支持的控件。Paste 命令是 ApplicationCommands 类中的预定义命令之一。TextBox 控件具有处理 Paste 命令的内置逻辑。MenuItem 类具有对调用命令的固有支持。

下面的示例演示如何设置 MenuItem,以便单击该菜单项时,将对 TextBox 调用 Paste 命令(假定该 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;

WPF 命令中的四个主要概念

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

  • 命令是要执行的操作。

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

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

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

在前面的示例中,Paste 命令是命令,MenuItem 是命令源,TextBox 是命令目标,命令绑定由 TextBox 控件提供。值得注意的是,CommandBinding 并不总是由充当命令目标类的控件提供。非常常见的情况是,CommandBinding 必须由应用程序开发人员创建,或者 CommandBinding 可能附加到命令目标的上级。

命令

WPF 中的命令是通过实现 ICommand 接口来创建的。ICommand 公开两个方法(ExecuteCanExecute)和一个事件 (CanExecuteChanged)。Execute 执行与命令关联的操作。CanExecute 确定是否可以在当前命令目标上执行命令。如果集中管理命令操作的命令管理器检测到命令源中发生了更改,此更改可能使得已引发但尚未由命令绑定执行的命令无效,则将引发 CanExecuteChangedICommand 的 WPF 实现是 RoutedCommand 类,这是本概述的重点。

WPF 中的主要输入源是鼠标、键盘、墨迹和路由命令。更加面向设备的输入使用 RoutedEvent 来通知应用程序页中的对象已发生了输入事件。RoutedCommand 没有不同。RoutedCommandExecuteCanExecute 方法不包含命令的应用程序逻辑,而是引发这样的路由事件:沿元素树以隧道和冒泡形式传递,直到遇到具有 CommandBinding 的对象。CommandBinding 包含这些事件的处理程序,执行此命令的就是这些处理程序。有关 WPF 中的事件路由的更多信息,请参见路由事件概述

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

WPF 提供了一组常用的路由命令,这组命令分布在几个类中:MediaCommandsApplicationCommandsNavigationCommandsComponentCommandsEditingCommands。这些类仅包含 RoutedCommand 对象,而不包含命令的实现逻辑。实现逻辑由在其上执行命令的对象负责。

命令源

命令源是调用命令的对象。例如,MenuItemButtonKeyGesture 就是命令源。

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

ICommandSource 公开三个属性:CommandCommandTargetCommandParameter

实现 ICommandSource 的 WPF 类包括:ButtonBaseMenuItemHyperlink 以及 InputBindingButtonBaseMenuItemHyperlink 在被单击时调用命令,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;

通常,命令源会侦听 CanExecuteChanged 事件。此事件通知命令源,在当前命令目标上执行命令的能力可能已更改。命令源可以通过使用 CanExecute 方法来查询 RoutedCommand 的当前状态。之后,如果命令无法执行,则命令源可以禁用其自身。这种情况的一个示例是:当无法执行命令时,MenuItem 会将自己显示为灰色。

可以将 InputGesture 用作命令源。WPF 中两种类型的输入笔势是 KeyGestureMouseGesture。可以将 KeyGesture 视为键盘快捷方式,如 Ctrl+C。KeyGesture 由一个 Key 和一组 ModifierKeys 组成。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);

InputGestureRoutedCommand 关联的另一种方法是将 InputGesture 添加到 RoutedCommandInputGestureCollection

下面的示例演示如何将一个 KeyGesture 添加到 RoutedCommandInputGestureCollection

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

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);

CommandBinding

CommandBinding 将一个命令与实现该命令的事件处理程序关联。

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

CommandCommandBinding 要与之关联的命令。附加到 PreviewExecutedExecuted 事件的事件处理程序实现命令逻辑。附加到 PreviewCanExecuteCanExecute 事件的事件处理程序确定命令是否可以在当前命令目标上执行。

下面的示例演示如何在应用程序的根 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);

接下来,创建 ExecutedRoutedEventHandlerCanExecuteRoutedEventHandlerExecutedRoutedEventHandler 打开一个 MessageBox,此消息框显示一个字符串,指出命令已执行。CanExecuteRoutedEventHandlerCanExecute 属性设置为 true。

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 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 OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
    e.CanExecute = True
End Sub
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

CommandBinding 附加到一个特定的对象,如应用程序的根 Window 或控件。CommandBinding 所附加到的对象定义绑定的范围。例如,可以通过 Executed 事件来到达附加到命令目标上级的 CommandBinding,但无法到达附加到命令目标后代的 CommandBinding。这是从引发事件的对象以隧道和冒泡方式传递 RoutedEvent 的直接后果。

在某些情形下,CommandBinding 附加到命令目标本身,例如,对于 TextBox 类以及 CutCopyPaste 命令就是如此。但是,更常见的情况是,将 CommandBinding 附加到命令目标上级(例如,主 Window 或 Application 对象)更方便,尤其当同一个 CommandBinding 可以用于多个命令目标时,这种优势更为明显。这些是您在创建命令基础结构时要考虑的设计决策。

命令目标

命令目标是在其上执行命令的元素。对于 RoutedCommand 而言,命令目标是 ExecutedCanExecute 的路由的起始元素。前面已提到,在 WPF 中,ICommandSource 上的 CommandTarget 属性只有在 ICommand 是一个 RoutedCommand 时才适用。如果在 ICommandSource 上设置了 CommandTarget,而对应的命令不是 RoutedCommand,将会忽略命令目标。

命令源可以显式设置命令目标。如果未定义命令目标,则具有键盘焦点的元素将用作命令目标。将具有键盘焦点的元素用作命令目标的一个好处是,应用程序开发人员可以使用同一个命令源在多个目标上调用命令,而不必跟踪命令目标。例如,如果 MenuItem 在具有一个 TextBox 控件和一个 PasswordBox 控件的应用程序中调用“粘贴”命令,则目标既可以是 TextBox,也可以是 PasswordBox,具体取决于哪个控件具有键盘焦点。

下面的示例演示如何显式地在标记和代码隐藏中设置命令目标。

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

CommandManager

CommandManager 提供许多命令相关函数。它提供一组用于向/从特定元素中添加/移除 PreviewExecutedExecutedPreviewCanExecuteCanExecute 事件处理程序的静态方法。它提供一种将 CommandBindingInputBinding 对象注册到特定类的方式。CommandManager 还通过 RequerySuggested 事件提供一种通知命令应在何时引发 CanExecuteChanged 事件的方式。

InvalidateRequerySuggested 方法强制 CommandManager 引发 RequerySuggested 事件。这对于应禁用/启用命令、但不被 CommandManager 识别的条件很有用。有关调用 InvalidateRequerySuggested 来强制 CommandManager 引发 RequerySuggested 事件的示例,请参见通过调度程序计时器禁用命令源的示例示例。

命令库

WPF 提供一组预定义命令。命令库包含以下类:ApplicationCommandsNavigationCommandsMediaCommandsEditingCommands 以及 ComponentCommands。这些类提供诸如 CutBrowseBackBrowseForwardPlayStopPause 等命令。

其中的许多命令都包括一组默认输入绑定。例如,如果您指定应用程序处理复制命令,则会自动获得键盘绑定“Ctrl+C”。您还会获得其他输入设备(如 Tablet PC 钢笔笔势和语音信息)的绑定。

当您使用 XAML 引用不同的命令库中的命令时,通常可以忽略公开静态命令属性的库类的类名。通常,命令名称作为字符串是明确的,并且存在用于提供命令的逻辑分组的所属类型,但是这些类型对于消除歧义并不是必需的。例如,您可以指定 Command="Cut",而不必指定更详细的 Command="ApplicationCommands.Cut"。这是一种内置于命令的 WPF XAML 处理器中的便利机制(更准确地说,它是 WPF XAML 处理器在加载时引用的 ICommand 的类型转换器行为)。

创建自定义命令

如果命令库类中的命令不满足您的需要,则您可以创建自己的命令。有两种方法可创建自定义命令。第一种是从头开始,并实现 ICommand 接口。另一种方法,也是更常用的方法,是创建 RoutedCommandRoutedUICommand

有关创建自定义 RoutedCommand 的示例,请参见创建自定义 RoutedCommand 的示例

请参见

任务

创建自定义 RoutedCommand 的示例

如何:实现 ICommandSource

如何:向 MenuItem 中添加命令

通过调度程序计时器禁用命令源的示例

概念

输入概述

路由事件概述

参考

RoutedCommand

CommandBinding

InputBinding

CommandManager