命令
Contents
理解命令
在设计良好的Windows应用程序中,应用程序逻辑不应位于时间处理中,而应在更高层的方法中编写代码。其中的没个方法都代表单独的应用程序“任务”。
WPF使用了新的命令模型帮助您解决这些问题,他增加了两个重要特性:
– 将事件委托到适当的命令
– 使控件的启用状态和相应命令你的状态保持同步
WPF命令模型
具有如下4个重要元素:
– 命令
命令表示应用程序任务,并且跟随任务是否能够被执行,然而,命令实际上不包含执行应用程序任务的代码
– 命令绑定
每个命令绑定针对用户界面的具体区域,将命令链接到相关的应用程序逻辑。这种分解的设计是非常重要的,因为单个命令可用于应用程序中的多个地方,并且在每个地方具有不同的意义。为处理这一问题,需要将统一命令与不同的命令绑定
– 命令源
命令源触发命令。例如,MenuItem和Button都是命令源。单击他们都会执行绑定命令。
– 命令目标
命令目标是在其中执行命令的元素。例如,Paste命令可在TextBox控件中插入文本,而OpenFile命令可在DocumementViewer中打开文档。根据命令的本质,目标可能很重要,也可能不重要。
ICommand 接口
WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理,该接口包含两个方法和一个事件:
public interface ICommand
{
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
它使用Execute()方法引发一个更复杂的过程,该过程最终触发在应用程序其他地方处理的事件。通过这种方式可以使用预先准备好的命令类,并插入自己的逻辑,还可以灵活地在几个不同的地方使用同一个命令。
CanExecute()方法返回命令的状态——如果命令可用,就返回true,如果不可用就返回false。Execute()和CanExecute()方法都接受一个附加的参数对象,可使用该对象传递所需的任何附加信息。
最后,当命令状态改变时引发CanExecuteChanged事件。当命令可用时,命令源(如Button)可自动启用自身;当命令不可用时,禁用自身。
RoutedCommand类
当创建自己的命令时,不会直接实现ICommand接口;而是使用System.Windows.Input.RoutedCommand类,该类自动实现了ICommand接口。RoutedCommand类是WPF中唯一实现了ICommand接口的类,换句话说,所有WPF命令都是RoutedCommand类及其派生类的实例。
在WPF命令模型背后一个重要概念是,RoutedCommand类不包含任何应用程序逻辑,而只代表命令,这意味着各个RoutedCommand对象具有相同的功能。
RoutedCommand类还引入了三个属性:命令名称(Name属性)、包含命令的(OwnerType)以及任何可用于触发命令的案件或鼠标操作(位于InputGestures集合中)。
RoutedUICommand类
在程序中处理的大部分命令是RoutedUICommand类的实例,继承自RoutedCommand类(实际上,WPF提供的所有预先构建好的命令都是RoutedUICommand对象)。
RoutedUICommand类用于具有文本的命令,这些文本显示在用户界面中的某些地方(例如菜单项文本、工具栏按钮的工具提示).RoutedUICommand类只增加了Text属性,该属性是为命令显示的文本。
如果命令文本永远不会在用户界面的任何地方显示,那么RoutedUICommand类和RoutedCommand类是等效的。
不见得要在用户界面使用RoutedUICommand文本,实际上,可嗯那个有更好的原因使用其他内容,例如,可能更愿意使用PrintDocument而不只是Print,而且在某些情况下完全可以用小图形替代文本。
命令库
这些命令通过以下5个专门的静态类的静态属性提供:
– ApplicationCommands
该类提供了通用的命令,包括剪贴板命令(如Copy、Cut和Paste)以及文档命令(如New、Open、Save、SaveAs和Print等)。
– NavigationCommands
该类提供了用于导航的命令,包括为基于页面的应用程序设计的一些命令(如BrowserBack、BrowseForward和NextPage),以及其他适合于基于文档应用程序的命令(如IncreaseZoom和Refresh)。
– EditingCommands
该类提供了许多重要的文档变基命令,包括用于移动的命令(MoveToLineEnd、MoveLeftByWord和MoveUpByPage等),选择内容的命令(SelectToLineEnd、SelectLeftByWord)以及改变格式的命令(ToggleBold和ToggleUnderLine)。
– ComponentCommands
该类提供了由用户界面组件使用的命令,包括用于移动和选择内容的命令,这些命令和EditingCommands类中的一些命令类似
– MediaCommands
该类提供了一组用于处理多媒体的命令(如Play、Pause、NextTrack以及IncreaseVolume)
ApplicationCommands类提供了一组基本命令,如下:
New、Open、Save、SaveAs、Close、Print、PrintPreview、CancePrint、Copy、Cut、Paste、Delete、Undo、Redo、Find、Replace、SelectAll、Stop、ContextMenu、CorrectionList、Properties、Help
命令为静态属性,整个应用程序只有一个该命令实例。然而根据命令源的不同,可采用不同的处理方式
每个命令的RoutedUICommand.Text属性和名称是相互匹配的,只是在单次之间添加了空格
许多命令对象都有一个额外的特征:默认输入绑定。例如,ApplicationCommads.Open命令被映射到Ctrl+O快捷键,只要命令绑定到命令源,并为窗口添加命令源,这个快捷键就会被激活,即使没有在用户界面的任何地方显示该命令也同样如此。
执行命令
RoutedUICommand类没有任何硬编码的功能,只是表示命令,为了触发命令,需要有命令源,为了响应命令,需要有命令绑定,命令绑定将执行转发给普通的事件处理程序
命令源
命令库中的命令始终可用,触发他们最简单的方法就是将他们关联到实现了ICommandSource接口的控件,其中抱愧继承自ButtonBase类的控件(Buttton和CheckBox等)、单独的ListBoxItem对象、Hyperlink以及MenuItem。
ICommandSource接口定义了三个属性
名称 | 说明 |
---|---|
Command | 指向连接的命令,这是唯一必须的细节 |
CommandParameter | 提供其他希望随命令发送的数据 |
CommandTarget | 确定将在其中执行命令的元素 |
例如
<Button Command="ApplicationCommands.New">New</Button>
<!--或者-->
<Button Command="New">New</Button>
命令绑定
当将命令关联到命令源时,命令源会被自动禁用,这是因为按钮已经查询到命令状态,而且命令还没有与其关联的绑定所以按钮被认为是禁用的
为了改变这种状态,需要为命令创建绑定以明确三件事:
– 当命令被触发时执行什么操作
– 如何确定命令是否能够被执行(这是可选的,如果未提供这一细节,只要提供了关联的时间处理程序,命令总是可用的)
– 命令在何处起作用。例如,命令可被限制在单个按钮中使用,或在整个窗口中使用(这种情况更常见)
以下的代码片段为New命令创建绑定。可将这些代码添加到窗口的构造函数中:
//创建绑定
CommandBinding binding=new CommandBinding(ApplicationCommands.New);
//添加事件
binding.Executed+=NewCommand_Executed;
//注册绑定
this.CommandBindings.Add(binding);
尽管习惯上为窗口添加所有绑定,但CommandBinding属性实际是在UIElement基类中定义的。这意味着任何元素都支持该属性。。如果将命令绑定直接添加到使用它的按钮中,这个实例仍工作的很好(尽管不能再将该绑定冲用于其他高级元素)。为了得到最大的灵活性,命令绑定通常被添加到顶级窗口。如果希望在多个窗口中使用相同的命令,需要在这些窗口中分别创建命令绑定。
也可以处理CommandBinding.PreviewExecuted事件,首先在最高层次的容器(窗口)中引发该事件,然后隧道路由至按钮。可通过事件隧道拦截和停止事件。如果将RoutedEventArgs.Handled属性设置为true,将永远不会发生Executed事件
可以使用XAML以声明方式关联命令
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="NewCommand_Executed"></CommandBinding>
</Window.CommandBindings>
使用多命令源
<Menu>
<MenuItem Header="File">
<MenuItem Command="New"></MenuItem>
</MenuItem>
</Menu>
MenuItem足够智能,如果没有设置Header属性,他将从命令中提取文本(Button空间不具有这一特性),可以自动提取Command.InputBindings集合中的第一个快捷键(如果存在的话)
微调命令文本
可以使用两种技术重用命令文本
1. 直接从静态命令对象中提取文本
XAML可以使用Static标记扩展完成这一任务
<Button Command="New" Content="{x:Staitc ApplicationCommands.New}"></Button>
该方法只是调用命令对象的toString(),得到的是命令名,而不是命令的文本。
2. 使用数据绑定表达式
<Button Command="New" Content="{Binding RelativeSource={RelativeSource Self},Path=Commmand.Text}"></Button>
直接调用命令
并非只能使用实现了ICommandSource接口的类来触发希望执行的命令。也可以用Execute()方法直接调用任何来自时间处理程序的方法
ApplicationCommands.New.Execute(null,targetElement);
也可以在关联CommandBinding对象中调用Execute()方法。
this.CommandBinding[0].Command.Execute(null);
这种方法只使用了半个命令模型,虽然也可触发命令,但不能相应命令的状态变化
禁用命令
//创建绑定
CommandBinding binding=new CommandBinding(ApplicationCommands.New);
//添加事件
binding.Executed+=NewCommand_Executed;
binding.CanExecute+=NewCommand_CanExecute;
//注册绑定
this.CommandBindings.Add(binding);
或使用声明方式
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Save" Executed="SaveCommand_Executed" CanExectue="SaveCommand_CanExecute"></CommandBinding>
</Window.CommandBindings>
在时间处理程序中,只需要检查isDirty变量,并相应的设置CanExecuteRoutedEventArg.CanExecute属性:
private void SaveCommand_CanExecute(object sender,CanExecuteRoutedEventArgs e)
{
e.CanExecute = isDirty;
}
具有内置命令的控件
<ToolBar DockPanel.Dock="Top">
<Button Command="Cut">Cut</Button>
<Button Command="Copy">Copy</Button>
<Button Command="Paste">Paste</Button>
</ToolBar>
单击这些按钮会复制、剪切或粘贴问题。文本框还处理CanExecute事件。ToolBar提供了一些内置逻辑,可将其子元素的CommandTarget属性动态设置为当前具有焦点的控件
如果在不同容器(不是ToolBar或Menu控件)中放置按钮,就不会获得这优势,除非手动设置CommandTarget属性,否则按钮不能工作。
<ToolBar DockPanel.Dock="Top">
<Button Command="Cut" commandTarget="{Binding ElementName=txtDocument}">Cut</Button>
<Button Command="Copy" commandTarget="{Binding ElementName=txtDocument}">Copy</Button>
<Button Command="Paste" commandTarget="{Binding ElementName=txtDocument}">Paste</Button>
</ToolBar>
另一个较简单的选择是使用附加属性FocusManager.IsFocusSCOPE穿件新的焦点范围。当触发命令时,该焦点范围会通知WPF在父元素的焦点范围内查找元素:
<StackPanel FocusManager.IsFocusScope="True">
<Button Command="Cut">Cut</Button>
<Button Command="Copy">Copy</Button>
<Button Command="Paste">Paste</Button>
</StackPanel>
该方法有一个附加优点,相同的命令可用于多个控件。Menu和Toolbar控件默认将FocusManager.IsFocusScope属性设置为True。
在极少数情况,您可能发现控件支持内置命令,而您并不想启用它。在这种情况下,可以采用三种方法禁用命令:
1. 理想情况下,控件会提供用于关闭命令支持的属性
例如,TextBox控件提供了IsUndoEnabled属性,为了阻止Undo特性,可将该属性设置为false。
2. 如果这种做法行不通,可为希望禁用的命令添加新的命令绑定。然后该命令绑定可提供新的CanExecute事件处理程序,并总是响应false.
下面举一个使用该技术上删除文本框Cut特性支持的示例“
CommandBinding commandBinding=new CommandBinding(ApplicationCommands.Cut,null,SuppressCommand);
txt.CommandBindings.Add(commandBinding);
//改时间处理程序设置CanExecute状态:
private void SuppressCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute=false;
e.Handled=true;
}
上面的艾玛设置了Hanlded标志以组织文本框自我执行计算,而文本框可能将CanExecute属性设置为true.
该方法并不完美,他可成功地为文本框禁用Cut快捷键上上下文菜单中的Cut命令,然而,仍会在上下文菜单中显示处于禁用的该选项。
3. 使用InputBinding集合删除触发命令的输入
例如,可使用代码禁用触发TextBox空间中Copy命令的Ctrl+C组合键,如下所示:
KeyBinding keyBinding=new KeyBinding(ApplicationCommands.NotACommand,Key.C,ModifierKeys.Control);
txt.InputBindings.Add(keyBinding);
技巧是使用这种特定的ApplicationCommands.NotACommand值,该命令什么都不作,它专门用于禁用输入绑定。
当时用这种方法是,仍启用Copy命令,可通过自己创建的按钮触发该命令(或使用文本框的上下文菜单触发命令,除非也通过将ContextMenu属性设置为null删除了上下文菜单)