使用路由事件可相应广泛的鼠标和键盘动作,但事件是非常低级的元素。在实际应用过程中,功能被划分成一些高级的任务,可通过不同的动作和用户界面元素触发,包括主菜单、上下文菜单、键盘以及工具栏。定义这些任务-命令-并将控件连接到命令,从而不需要重复编写事件处理代码。更重要的,当命令不可用时,命令特性通过自动禁用控件来管理用户界面的状态。
命令模型
WPF命令模型由许多可变的部分组成:
- 命令 : 命令表示程序任务,并跟踪任务是否能够被执行。然而命令不包含执行程序任务的代码。
- 命令绑定 : 每个命令绑定针对界面的具体区域,单个命令可用于多个地方。
- 命令源 : 命令源触发命令。
- 命令目标 : 命令目标是在其中执行命令的元素。
ICommand 接口
命令模型的核心是 System.Windows.Input.ICommand 接口,包含两个方法和一个事件:
public interface ICommand
{
// 程序任务逻辑
void Execute(object parameter);
// 返回命令的状态-true代表可用,false代表不可用
bool CanExecute(object parameter);
// 指示信号,状态改变
event EventHandler CanExecuteChanged;
}
RoutedCommand 类
自己创建命令时,不会直接实现 ICommand 接口,而是继承 System.Windows.Input.RoutedCommand 类。如果是实现 ICommand 接口,代码被硬连接到命令,没有事件冒泡。RoutedCommand 类不包含任何程序逻辑,只代表命令,这意味着各个RoutedCommand 对象具有相同的功能。RoutedCommand为事件冒泡和隧道添加一些额外的基础结构,使得命令在WPF元素层次结构中冒泡 。
WPF命令为什么需要路由事件? WPF使用了大量预先构建的命令,不包含实际代码,为确保可在某个位置处理事件,甚至事件是由同一个窗口中不同命令源引发的,就需要使用事件冒泡的功能。
为什么非要使用预先构建的命令? 预先构建命令为集成提供了更好的可能,是WPF可插入体系架构的主要部分之一。
RoutedUICommand 类
在程序中处理的大部分命令不是 RoutedCommand 对象,而是 RoutedUICommand 类的实例。实际上,WPF提供的所有预先构建好的命令都是 RoutedUICommand对象。
RoutedUICommand 用于具有文本的命令,这些文本显示在界面某些地方(菜单项文本,工具按钮的提示)。Text属性是为了命令显示的文本,这样可以在某个位置执行本地化。如果命令文本不会在界面上显示,RoutedUICommand和RoutedCommand是等效的。
命令库
WPF提供了基本命令库,通过 5 个专门的静态类的静态属性提供:
- ApplicationCommands : 通用命令,包括剪切板命令(Copy,Cut,Paste)和文档命令(New,Open,Save,Save as,Print).
- NavigationCommands : 用于导航的命令。(BrowseBack,BrowseForward,NextPage,IncreaseZoom,Refresh).
- EditingCommands : 文档编辑命令。(MoveToLinedEnd,MoveLeftByWord,MoveUpByPage,SelectToLinedEnd,SelectLeftByWord,ToggleBold,ToggleUnderline)
- ComponentCommands : 用户界面组件使用的命令。
- MediaCommands : 多媒体命令。(Play,Pause,NextTrack,IncreaseVolume).
许多命令对象都有一个额外的特征:默认输入绑定 ,例如 ApplicationCommands.Open 命令被映射到 Ctrl+O 快捷键。
执行命令
命令绑定将执行转发给普通的事件处理程序。
命令源
触发命令库的方法是将它们关联到实现了 ICommandSource 接口的控件,包括 ButtonBase类控件(Button,CheckBox等),ListBoxItem,Hyperlink,MenuItem.
ICommandSource 接口定义了三个属性:
名称 | 说明 |
---|---|
Command | 指向连接的命令,必填 |
CommandParameter | 希望命令发送的数据 |
CommandTarget | 将在其中执行命令的元素 |
<Button Command="ApplicationCommands.New">New</Button>
<!--WPF很智能,能自己查找5个命令容器类-->
<Button Command="New">New</Button>
<!--但上面这种语法不够明确,不够清晰-->
命令绑定
为命令创建绑定需要明确三件事情:
- 当触发时执行什么操作
- 如何确定命令是否能够被执行
- 命令在何处起作用
// 创建绑定
CommandBinding binding = new CommandBinding(ApplicationCommands.New);
// 绑定执行委托
binding.Executed += NewCommand_Executed;
// 注册绑定,通过事件冒泡进行工作
this.CommandBindings.Add(Binding);
上面代码里的绑定被添加到窗口的 CommandBindings 集合中,这通过事件冒泡进行工作,当单击按钮时 CommandBinding.Executed 事件从按钮冒泡到窗口。CommandBindings 属性是在 UIElement 基类中定义的,也可以将绑定直接添加到按钮中。为了灵活性,绑定通常添加到顶级窗口。如果希望在多个窗口使用相同的命令,需要在这些窗口中分别创建命令绑定。
使用XAML声明方式关联命令:
<Window >
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New"
Executed="NewCommand_Executed"/>
</Window.CommandBindings>
<Button Command="ApplicationCommands.New">New</Button>
</Window>
多命令源
如果此时同一窗体的菜单也使用New命令:
<Menu>
<MenuItem Header="File">
<MenuItem Command="New"></MenuItem>
</MenuItem>
</Menu>
这里,New菜单并没有设置 Header 属性,因为MenuItem很智能,可以从命令中提取文本(Button不具有这一特性)。在不同语言本地化应用程序时很重要。MenuItem还能自动提取 Command.InputBindings 集合中的第一个快捷键。
微调命令文本
其他 ICommandSource 不会自动提取命令项文本,但通过一点额外的工作也能实现这个功能。
一种是直接从静态命令对象中提取文本:
<!--获取命令名,并作为按钮的文本-->
<Button Command="New" Content="{x:Static ApplicationCommands.New}"/>
这种方法只是调用命令对象的 ToString() 方法,得到的是命令名,而不是命令的文本(多个单词间有空格)。
另一个更好的方法是使用数据绑定表达式,绑定到当前元素。
<Button Margin="5" Command="New"
Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}">
</Button>
直接调用命令
并非只能实现 ICommandSource 接口的类能触发命令,也可以用 Execute() 方法直接调用来自任何事件处理程序的方法。
ApplicationCommands.New.Execute(null, targetElement);
目标元素是WPF开始查找命令绑定的地方,可使用包含窗口(具有命令绑定)或嵌套的元素。也可以在关联的 CommandBinding 对象中调用 Execute() 方法,这种情况不需要提供目标元素。
this.CommandBindings[0].Command.Execute(null);
高级命令
自定义命令通过实例化一个新的 RoutedUICommand 对象:
public class DataCommands
{
private static RoutedUICommand requery;
static DataCommands()
{
InputGestureCollection inputs = new InputGestureCollection();
inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
requery = new RoutedUICommand("Requery", "Requery", typeof(DataCommands), inputs);
}
public static RoutedUICommand Requery
{
get {return requery;}
}
}
<Button Command="local:DataCommands.Requery"
Executed="RequeryCommand_Executed">Requery</Button>
使用命令参数
通过设置 CommandParameter 属性可以传递额外信息:
<Button Command="NavigationCommands.Zoom"
CommandParameter="{Binding ElementName=txtZoom, Path=Text}">Zoom To Value
</Button>