命令概述
命令是 Windows Presentation Foundation (WPF) 中的输入机制,它提供的输入处理比设备输入具有更高的语义级别。 例如,在许多应用程序中都能找到的**“复制”、“剪切”和“粘贴”**操作就是命令。
本概述定义哪些命令在 WPF 中,哪些类是命令模型的一部分,以及如何在您的应用程序中使用和创建命令。
本主题包含以下各节:
什么是命令?
WPF 中的简单命令示例
WPF 命令中的四个主要概念
命令库
创建自定义命令
什么是命令?
命令有若干用途。 第一个用途是将语义以及调用命令的对象与执行命令的逻辑分离开来。 这使得多个完全不同的源可以调用相同的命令逻辑,并使得可以针对不同的目标对命令逻辑进行自定义。 例如,在许多应用程序中都能找到的编辑操作**“复制”、“剪切”和“粘贴”**都可使用不同的用户操作进行调用(如果这些操作是使用命令实现的)。 应用程序可能允许用户通过单击按钮、选择菜单项或使用组合键(例如 Ctrl+X)剪切所选的对象或文本。 通过使用命令,您可以将各种类型的用户操作绑定到同一逻辑。
命令的另一个用途是指示操作是否可用。 仍然以剪切对象或文本作为示例,该操作只有在选择了某些内容时才有意义。 如果用户尝试在没有选择任何内容的情况下剪切对象或文本,则不会发生任何操作。 为了向用户指明这一点,许多应用程序都会禁用按钮和菜单项,以使用户了解是否能够执行某项操作。 命令可通过实现 CanExecute 方法来指出操作是否可以执行。 按钮可以订阅 CanExecuteChanged 事件,并且,如果 CanExecute 返回 false,则可以禁用按钮,或者,如果 CanExecute 返回 true,则可以启用按钮。
命令的语义在应用程序和类之间可能是一致的,但是操作的逻辑是所作用于的特定对象所特有的。 Ctrl+X 组合键在文本类、图像类以及 Web 浏览器中调用**“剪切”命令,但用于执行“剪切”**操作的实际逻辑是由执行剪切的应用程序定义的。 RoutedCommand 使客户端能够实现逻辑。 文本对象可以将所选文本剪切到剪贴板中,而图像对象则可以剪切所选图像。 当应用程序处理 Executed 事件时,它将能访问命令的目标,并根据目标的类型采取相应的操作。
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
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
// 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;
WPF 命令中的四个主要概念
WPF 中的路由命令模型可以分为四个主要概念:命令、命令源、命令目标以及命令绑定:
命令是要执行的操作。
命令源是调用命令的对象。
命令目标是在其上执行命令的对象。
命令绑定是将命令逻辑映射到命令的对象。
在前面的示例中,Paste 命令是命令,MenuItem 是命令源,TextBox 是命令目标,命令绑定由 TextBox 控件提供。 值得注意的是,CommandBinding 并不总是由充当命令目标类的控件提供。 非常常见的情况是,CommandBinding 必须由应用程序开发人员创建,或者 CommandBinding 可能附加到命令目标的上级。
命令
WPF 中的命令是通过实现 ICommand 接口创建的。 ICommand 公开两个方法(Execute 及 CanExecute)和一个事件(CanExecuteChanged)。 Execute 执行与命令关联的操作。CanExecute 确定是否可以在当前命令目标上执行命令。 如果集中管理命令操作的命令管理器检测到命令源中发生了更改,此更改可能使得已引发但尚未由命令绑定执行的命令无效,则将引发 CanExecuteChanged。 ICommand 的 WPF 实现是 RoutedCommand 类,这是本概述的重点。
WPF 中的主要输入源是鼠标、键盘、墨迹和路由命令。 更加面向设备的输入使用 RoutedEvent 来通知应用程序页中的对象已发生了输入事件。 RoutedCommand 没有不同。 RoutedCommand 的 Execute 和 CanExecute 方法不包含命令的应用程序逻辑,而是引发这样的路由事件:沿元素树以隧道和冒泡形式传递,直到遇到具有 CommandBinding 的对象。 CommandBinding 包含这些事件的处理程序,执行此命令的就是这些处理程序。 有关 WPF 中的事件路由的更多信息,请参见路由事件概述。
RoutedCommand 上的 Execute 方法在命令目标上引发 PreviewExecuted 和 Executed 事件。 RoutedCommand 上的 CanExecute 方法在命令目标上引发 CanExecute 和 PreviewCanExecute 事件。 这些事件沿元素树以隧道和冒泡形式传递,直到遇到具有该特定命令的 CommandBinding 的对象。
WPF 提供了一组常用的路由命令,这组命令分布在几个类中:MediaCommands、ApplicationCommands、NavigationCommands、ComponentCommands 和 EditingCommands。 这些类仅包含 RoutedCommand 对象,而不包含命令的实现逻辑。 实现逻辑由在其上执行命令的对象负责。
命令源
命令源是调用命令的对象。 例如,MenuItem、Button 和 KeyGesture 就是命令源。
WPF 中的命令源通常实现 ICommandSource 接口。
ICommandSource 公开三个属性:Command、CommandTarget 和 CommandParameter:
Command 是在调用命令源时执行的命令。
CommandTarget 是要在其上执行命令的对象。 值得注意的是,在 WPF 中,ICommandSource 上的 CommandTarget 属性只有在 ICommand 是 RoutedCommand 时才适用。 如果在 ICommandSource 上设置了 CommandTarget,而对应的命令不是 RoutedCommand,将会忽略命令目标。 如果未设置 CommandTarget,则具有键盘焦点的元素将是命令目标。
CommandParameter 是用户定义的数据类型,用于将信息传递到实现命令的处理程序。
实现 ICommandSource 的 WPF 类包括:ButtonBase、MenuItem、Hyperlink 以及 InputBinding。 ButtonBase、MenuItem 和 Hyperlink 在被单击时调用命令,InputBinding 在与之关联的 InputGesture 执行时调用命令。
下面的示例演示如何将 ContextMenu 中的 MenuItem 用作 Properties 命令的命令源。
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Properties" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
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
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 中两种类型的输入笔势是 KeyGesture 和 MouseGesture。 可以将 KeyGesture 视为键盘快捷方式,如 Ctrl+C。 KeyGesture 由一个 Key 和一组 ModifierKeys 组成。 MouseGesture 由一个 MouseAction 和一组可选的 ModifierKeys 组成。
为了使 InputGesture 充当命令源,必须将它与一个命令关联。 有几种方法可实现此目的。 一种方法是使用 InputBinding。
下面的示例演示如何在 KeyGesture 与 RoutedCommand 之间创建一个 KeyBinding。
<Window.InputBindings>
<KeyBinding Key="B"
Modifiers="Control"
Command="ApplicationCommands.Open" />
</Window.InputBindings>
Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)
Dim OpenCmdKeybinding As New KeyBinding(ApplicationCommands.Open, OpenKeyGesture)
Me.InputBindings.Add(OpenCmdKeybinding)
KeyGesture OpenKeyGesture = new KeyGesture(
Key.B,
ModifierKeys.Control);
KeyBinding OpenCmdKeybinding = new KeyBinding(
ApplicationCommands.Open,
OpenKeyGesture);
this.InputBindings.Add(OpenCmdKeybinding);
将 InputGesture 与 RoutedCommand 关联的另一种方法是将 InputGesture 添加到 RoutedCommand 的 InputGestureCollection。
下面的示例演示如何将一个 KeyGesture 添加到 RoutedCommand 的 InputGestureCollection。
Dim OpenCmdKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)
ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture)
KeyGesture OpenCmdKeyGesture = new KeyGesture(
Key.B,
ModifierKeys.Control);
ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);
CommandBinding
CommandBinding 将一个命令与实现该命令的事件处理程序关联。
CommandBinding 类包含一个 Command 属性以及 PreviewExecuted、Executed、PreviewCanExecute 和 CanExecute 事件。
Command 是 CommandBinding 要与之关联的命令。 附加到 PreviewExecuted 和 Executed 事件的事件处理程序实现命令逻辑。 附加到 PreviewCanExecute 和 CanExecute 事件的事件处理程序确定命令是否可以在当前命令目标上执行。
下面的示例演示如何在应用程序的根 Window 上创建一个 CommandBinding。 CommandBinding 将 Open 命令与 Executed 和 CanExecute 处理程序关联。
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open"
Executed="OpenCmdExecuted"
CanExecute="OpenCmdCanExecute"/>
</Window.CommandBindings>
' Creating CommandBinding and attaching an Executed and CanExecute handler
Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)
Me.CommandBindings.Add(OpenCmdBinding)
// Creating CommandBinding and attaching an Executed and CanExecute handler
CommandBinding OpenCmdBinding = new CommandBinding(
ApplicationCommands.Open,
OpenCmdExecuted,
OpenCmdCanExecute);
this.CommandBindings.Add(OpenCmdBinding);
接下来,创建 ExecutedRoutedEventHandler 和 CanExecuteRoutedEventHandler。 ExecutedRoutedEventHandler 打开一个 MessageBox,此消息框显示一个字符串,指出命令已执行。 CanExecuteRoutedEventHandler 将 CanExecute 属性设置为 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 类以及 Cut、Copy 和 Paste 命令就是如此。 但是,更常见的情况是,将 CommandBinding 附加到命令目标上级(例如,主 Window 或 Application 对象)更方便,尤其当同一个 CommandBinding 可以用于多个命令目标时,这种优势更为明显。 这些是您在创建命令基础结构时要考虑的设计决策。
命令目标
命令目标是在其上执行命令的元素。 对于 RoutedCommand 而言,命令目标是 Executed 和 CanExecute 的路由的起始元素。 前面已提到,在 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
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
// 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;
CommandManager
CommandManager 提供许多命令相关函数。 它提供一组用于向/从特定元素中添加/移除 PreviewExecuted、Executed、PreviewCanExecute 和 CanExecute 事件处理程序的静态方法。 它提供一种将 CommandBinding 和 InputBinding 对象注册到特定类的方式。 CommandManager 还通过 RequerySuggested 事件提供一种通知命令应在何时引发 CanExecuteChanged 事件的方式。
InvalidateRequerySuggested 方法强制 CommandManager 引发 RequerySuggested 事件。 这对于应禁用/启用命令、但不被 CommandManager 识别的条件很有用。
命令库
WPF 提供一组预定义命令。 命令库包含以下类:ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands 以及 ComponentCommands。 这些类提供诸如 Cut、BrowseBack、BrowseForward、Play、Stop 和 Pause 等命令。
其中的许多命令都包括一组默认输入绑定。 例如,如果您指定应用程序处理复制命令,则会自动获得键盘绑定“Ctrl+C”。您还会获得其他输入设备(如 Tablet PC 钢笔笔势和语音信息)的绑定。
当您使用 XAML 引用不同的命令库中的命令时,通常可以忽略公开静态命令属性的库类的类名。 通常,命令名称作为字符串是明确的,并且存在用于提供命令的逻辑分组的所属类型,但是这些类型对于消除歧义并不是必需的。 例如,您可以指定 Command="Cut",而不必指定更详细的 Command="ApplicationCommands.Cut"。 这是一种内置于命令的 WPF XAML 处理器中的便利机制(更准确地说,它是 WPF XAML 处理器在加载时引用的 ICommand 的类型转换器行为)。
创建自定义命令
如果命令库类中的命令不满足您的需要,则您可以创建自己的命令。 有两种方法可创建自定义命令。 第一种是从头开始,并实现 ICommand 接口。 另一种方法,也是更常用的方法,是创建 RoutedCommand 或 RoutedUICommand。
有关创建自定义 RoutedCommand 的示例,请参见 Create a Custom RoutedCommand Sample(创建自定义 RoutedCommand 示例)。