WPF C# 命令的运行机制
1、概述
1.1 WPF C# 命令的本质
命令是 WPF 中的输入机制,它提供的输入处理比设备输入具有更高的语义级别。 例如,在许多应用程序中都能找到的“复制”、“剪切”和“粘贴”操作就是命令。
WPF 中的命令是通过实现 ICommand 接口创建的。 ICommand 的 WPF 实现是 RoutedCommand 类,这是WPF C# 命令的本质。
1.2 WPF C# 命令的机制
1.2.1 编程范围
ICommand 公开两个方法(Execute 及 CanExecute)和一个事件(CanExecuteChanged)。
Execute 执行与命令关联的操作。
CanExecute 确定是否可以在当前命令目标上执行命令。
如果集中管理命令操作的命令管理器检测到命令源中发生了更改,此更改可能使得已引发但尚未由命令绑定执行的命令无效,则将引发 CanExecuteChanged。
1.2.2 输入源
WPF 中的主要输入源是鼠标、键盘、墨迹和路由命令。 更加面向设备的输入使用路由事件( RoutedEvent )来通知应用程序页中的对象已发生了输入事件。无论何种输入源, RoutedCommand 没有不同。
1.2.3 应用程序逻辑
RoutedCommand 的 Execute 和 CanExecute 方法不包含命令的应用程序逻辑,而是引发这样的路由事件:沿元素树以隧道和冒泡形式传递,直到遇到具有 CommandBinding 的对象。CommandBinding 包含这些事件的处理程序,执行此命令的就是这些处理程序。( 有关 WPF 中的事件路由的更多信息,请参见路由事件概述。)
1.2.4 命令引发路由事件
RoutedCommand 上的 Execute 方法在命令目标上引发 PreviewExecuted 和 Executed 事件。
RoutedCommand 上的 CanExecute 方法在命令目标上引发 CanExecute 和PreviewCanExecute 事件。
这些事件沿元素树以隧道和冒泡形式传递,直到遇到具有该特定命令的 CommandBinding 的对象。
1.3、WPF C# 命令分类
WPF 通过命令类库提供一组预定义命令。如果命令库类中的命令不满足您的需要,则您可以创建自己的命令。
1.3.1 内置命令库
WPF 提供一组预定义命令库。 命令库包含以下类:ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands 以及 ComponentCommands。
这些类提供了一组常用的路由命令.诸如 Cut、BrowseBack、BrowseForward、Play、Stop 和 Pause 等命令。
这些命令仅包含 RoutedCommand 对象,而不包含命令的实现逻辑。 实现逻辑由在其上执行命令的对象负责。
命令库中的许多命令都包括一组默认输入绑定。 例如,如果您指定应用程序处理复制命令,则会自动获得键盘绑定“Ctrl+C”。您还会获得其他输入设备(如 Tablet PC 钢笔笔势和语音信息)的绑定。
当您使用 XAML 引用不同的命令库中的命令时,通常可以忽略公开静态命令属性的库类的类名。 通常,命令名称作为字符串是明确的,并且存在用于提供命令的逻辑分组的所属类型,但是这些类型对于消除歧义并不是必需的。 例如,您可以指定 Command="Cut",而不必指定更详细的 Command="ApplicationCommands.Cut"。 这是一种内置于命令的 WPF XAML 处理器中的便利机制(更准确地说,它是 WPF XAML 处理器在加载时引用的 ICommand 的类型转换器行为)。
1.3.2 创建自定义命令
如果命令库类中的命令不满足您的需要,则您可以创建自己的命令。
有两种方法可创建自定义命令。 第一种是从头开始,并实现 ICommand 接口。 另一种方法,也是更常用的方法,是创建RoutedCommand 或 RoutedUICommand。
有关创建自定义 RoutedCommand 的示例,请参见 Create a Custom RoutedCommand Sample(创建自定义 RoutedCommand 示例)。
1.4、WPF C# 命令的用途
命令有若干用途。
1.4.1 将语义以及调用命令的对象与执行命令的逻辑分离
第一个用途是将语义以及调用命令的对象与执行命令的逻辑分离开来。 这使得多个完全不同的源可以调用相同的命令逻辑,并使得可以针对不同的目标对命令逻辑进行自定义。 例如,在许多应用程序中都能找到的编辑操作“复制”、“剪切”和“粘贴”都可使用不同的用户操作进行调用(如果这些操作是使用命令实现的)。 应用程序可能允许用户通过单击按钮、选择菜单项或使用组合键(例如 Ctrl+X)剪切所选的对象或文本。 通过使用命令,您可以将各种类型的用户操作绑定到同一逻辑。
1.4.2 操作是否可用
命令的另一个用途是指示操作是否可用。 仍然以剪切对象或文本作为示例,该操作只有在选择了某些内容时才有意义。 如果用户尝试在没有选择任何内容的情况下剪切对象或文本,则不会发生任何操作。 为了向用户指明这一点,许多应用程序都会禁用按钮和菜单项,以使用户了解是否能够执行某项操作。 命令可通过实现 CanExecute 方法来指出操作是否可以执行。 按钮可以订阅CanExecuteChanged 事件,并且,如果 CanExecute 返回 false,则可以禁用按钮,或者,如果 CanExecute 返回 true,则可以启用按钮。
1.4.3 根据目标的类型采取相应的操作
命令的语义在应用程序和类之间可能是一致的,但是操作的逻辑是所作用于的特定对象所特有的。 Ctrl+X 组合键在文本类、图像类以及 Web 浏览器中调用“剪切”命令,但用于执行“剪切”操作的实际逻辑是由执行剪切的应用程序定义的。 RoutedCommand 使客户端能够实现逻辑。 文本对象可以将所选文本剪切到剪贴板中,而图像对象则可以剪切所选图像。 当应用程序处理Executed 事件时,它将能访问命令的目标,并根据目标的类型采取相应的操作。
2、使用WPF 命令
2.1 WPF 命令中的四个主要概念
WPF 中的路由命令模型可以分为四个主要概念:命令、命令源、命令目标以及命令绑定:
-
命令:是要执行的操作。
-
命令源:是调用命令的对象。
-
命令目标:是在其上执行命令的对象。
-
命令绑定:是将命令逻辑映射到命令的对象。
参见下面的示例。其中,Paste 命令是命令,MenuItem 是命令源,TextBox 是命令目标,命令绑定由 TextBox 控件提供。
值得注意的是,CommandBinding 并不总是由充当命令目标类的控件提供。 非常常见的情况是,CommandBinding 必须由应用程序开发人员创建,或者 CommandBinding 可能附加到命令目标的上级。
2.2 WPF 中的简单命令示例
在 WPF 中使用命令的最简单方法是从某个命令库类中使用预定义的 RoutedCommand;使用对处理此命令具有固有支持的控件;以及使用对调用命令具有固有支持的控件。
Paste 命令是ApplicationCommands 类中的预定义命令之一。 TextBox 控件具有处理 Paste 命令的内置逻辑。 MenuItem 类具有对调用命令的固有支持。下面的示例演示如何设置 MenuItem,以便单击该菜单项时,将对 TextBox 调用 Paste 命令(假定该 TextBox 具有键盘焦点)。
XAML <StackPanel> <Menu> <MenuItem Command="ApplicationCommands.Paste" /> </Menu> <TextBox /> </StackPanel>
C# // 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;
2.3 编程设计
2.3.1 命令
如前述所述,WPF 中的命令是通过实现 ICommand 接口创建的。 ICommand 公开两个方法(Execute 及 CanExecute)和一个事件(CanExecuteChanged)。 Execute 执行与命令关联的操作。CanExecute 确定是否可以在当前命令目标上执行命令。 如果集中管理命令操作的命令管理器检测到命令源中发生了更改,此更改可能使得已引发但尚未由命令绑定执行的命令无效,则将引发 CanExecuteChanged。
RoutedCommand 上的 Execute 方法在命令目标上引发 PreviewExecuted 和 Executed 事件。 RoutedCommand 上的 CanExecute 方法在命令目标上引发 CanExecute 和PreviewCanExecute 事件。 这些事件沿元素树以隧道和冒泡形式传递,直到遇到具有该特定命令的 CommandBinding 的对象。
2.3.2 命令源
命令源是调用命令的对象。 例如,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 执行时调用命令。
2.3.2.1 举例: ContextMenu 中的 MenuItem 用作 Properties 命令的命令源
下面的示例演示如何将 ContextMenu 中的 MenuItem 用作 Properties 命令的命令源。
XAML <StackPanel> <StackPanel.ContextMenu> <ContextMenu> <MenuItem Command="ApplicationCommands.Properties" /> </ContextMenu> </StackPanel.ContextMenu> </StackPanel>
C# 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 会将自己显示为灰色。
2.3.2.2 举例: InputGesture 作命令源
可以将 InputGesture 用作命令源。 WPF 中两种类型的输入笔势是 KeyGesture 和 MouseGesture。 可以将 KeyGesture 视为键盘快捷方式,如 Ctrl+C。 KeyGesture 由一个 Key 和一组ModifierKeys 组成。 MouseGesture 由一个 MouseAction 和一组可选的 ModifierKeys 组成。
为了使 InputGesture 充当命令源,必须将它与一个命令关联。 有几种方法可实现此目的。 一种方法是使用 InputBinding。
下面的示例演示如何在 KeyGesture 与 RoutedCommand 之间创建一个 KeyBinding。
XAML <Window.InputBindings> <KeyBinding Key="B" Modifiers="Control" Command="ApplicationCommands.Open" /> </Window.InputBindings>
C# KeyGesture OpenKeyGesture = new KeyGesture( Key.B, ModifierKeys.Control); KeyBinding OpenCmdKeybinding = new KeyBinding( ApplicationCommands.Open, OpenKeyGesture); this.InputBindings.Add(OpenCmdKeybinding);
2.3.2.3 InputGesture 与 RoutedCommand 关联
将 InputGesture 与 RoutedCommand 关联的另一种方法是将 InputGesture 添加到 RoutedCommand 的 InputGestureCollection。
下面的示例演示如何将一个 KeyGesture 添加到 RoutedCommand 的 InputGestureCollection。
WPF C# 命令的运行机制
命令源
命令源是调用命令的对象。 例如,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 执行时调用命令。