【WPF学习】第三十二章 执行命令

  前面章节已经对命令进行了深刻分析,分析了基类和接口以及WPF提供的命令库。但还没有例举任何使用这些命令的例子。编辑器

  如前所述,RoutedUICommand类没有任何硬编码的功能,而是只表达命令,为触发命令,须要有命令源(也可以使用代码)。为响应命令,须要有命令绑定,命令绑定将执行转发给广泛的事件处理程序。函数

1、命令源工具

  命令库中的命令始终可用。触发他们的最简单的方法是将它们关联到实现了ICommandSource接口的控件,其中包括继承自ButtonBase类的控件(Button和CheckBox等)、单独的ListBoxItem对象、HyperLink以及MenuItem。this

  ICommandSource接口定义了三个属性,以下表所示。编码

表 ICommandSource接口的属性spa

 

   例如,下面的按钮使用Command属性链接到ApplicationCommands.New命令:设计

 <Button Command="ApplicationCommands.New">New</Button>code

  WPF的智能程度足够高,它能查找前面介绍的全部5个命令容器类,这意味着可以使用下面的缩写的形式:xml

 <Button Command="New">New</Button>

  然而,因为没有指明包含命令的类,这种语法不够明确、不够清晰。对象

2、命令绑定

  当将命令关联到命令源时,会看到一些有趣的现象。命令源将会被自动禁用。

  例如,若是建立上一节提到的New按钮,该按钮的颜色就会变浅而且不能被单击,就像将IsEnabled属性设置为false那样(以下图所示)。这是由于按钮已经查询了命令的状态,并且因为命令尚未与其关联的绑定,因此按钮被认为是禁用的。

 

 

   为改变这种状态,须要为命令建立绑定以明确如下三件事:

  当命令被触发时执行什么操做。

  如何肯定命令是否可以被执行(这是可选的。若是未提供这一细节,只要提供了关联的事件处理程序,命令老是可用)。

  命令在何处起做用。例如,命令可被限制在单个按钮中使用,或在整个窗口中使用(这种状况更常见)。

  下面的代码片断为New命令建立绑定。可将这些代码添加到窗口的构造函数中:

//Create the binding
CommandBinding binding=new CommandBinding(ApplicationCommands.New);

//Attach the event handler
binding.Executed+=NewCommand_Executed;

//Register the binding
this.CommandBinding.Add(binding);

  注意,上面建立的CommandBinding对象呗添加到包含窗口的CommandBindings集合中,这经过事件冒泡进行工做。实际上,当单击按钮时,CommandBinding.Executed事件从按钮冒泡到包含元素。

  尽管习惯上为窗口添加全部绑定,但CommandBindings属性实际是在UIElement基类中定义的。这意味着任何元素都支持该属性。例如,若是将命令绑定直接添加到使用它的按钮中,这个示例仍工做的很好(尽管不能在将该绑定重用与其余高级元素)。为获得最大的灵活性,命令绑定一般被添加到顶级窗口。若是但愿在多个窗口中使用相同相同的命令,须要在这些窗口中分别建立命令绑定。

  上面的代码假定在同一个类中已有名为NewCommand_Executed的事件处理程序,该处理程序已经准备好接收命令。下面是一个示例,该例包含一些显示命令源的简单代码:

private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New command triggered by " + e.Source.ToString());
        }

  如今,若是容许应用成功需,按钮处于启用状态。若是单击按钮,就会触发Executed事件,该事件冒泡至窗口,并被上面给出的NewCommand_Executed()事件处理程序程序处理。这是,WPF会告知事件源(按钮)。经过ExecutedRoutedEventArgs对象还可得到被调用的命令的引用(ExecutedRoutedEventArgs.Command),以及全部同时传递的额外数据(ExecutedRoutedEventArgs.Parameter)。在该例中,由于没有传递任何额外的数据,因此参数为null(若是但愿传递附加数据,赢设置命令源的CommandParameter属性;而且若是但愿传递一些来自另外一个控件的信息,还须要使用数据绑定表达式设置CommandParameter属性)。

  在上面的示例中,使用代码生成了命令绑定。然而,若是但愿精简代码隐藏文件,使用XAML以生命方式关联命令一样容易。下面是所需的标记:

<Window x:Class="Commands.TestNewCommand"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TestNewCommand" Height="300" Width="300">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand"></CommandBinding>
    </Window.CommandBindings>
    <StackPanel>
        <Button Margin="5" Padding="5" Command="ApplicationCommands.New">New</Button>
    </StackPanel>
</Window>

  使用Visual Studio没有为定义命令绑定提供任何设计时支持。对链接控件和命令的支持也较弱。可以使用Properties窗口设置控件的Command属性,但须要输入正确的命令名称——因为并未提供包含命令的下拉列表,所以不能方便地从列表中选择命令。

3、使用多命令源

  上面示例中触发普通事件的方式看起来不那么直接。然而,当添加使用相同命令的更多控件时,额外命令层的意义就提现出出来了。例如,可添加以下也使用New命令的菜单项:

        <Menu >
            <MenuItem Header="File">
                <MenuItem Command="New"></MenuItem>
            </MenuItem>
        </Menu>

  注意,New命令的这个MenuItem对象没有设置Header属性。这是由于MenuItem类足够智能,若是没有设置Header属性,它将从命令中提取文本(Button控件不具备这一特性)。虽然该特性带带来的便利看起来不大,但若是计划使用不一样的语言本地化应用程序,这一特性就很重要了。在这种状况下,只须要在一个地方修改文本便可(经过设置命令的Text属性)。这比在整个窗口中进行跟踪更容易。

  MenuItem类还有一项功能:能自动提取Command.InputBinding集合中的第一个快捷键(若是存在的话)。对于ApplicationCommands.New命令对象,这意味着在菜单文本的旁边会显示Ctrl+N快捷键(以下图所示)。

 

 4、微调命令文本

  既然菜单具备自动提取命令项文本的功能,肯恩改回好奇其余ICommandSource类是否也具备相似功能,如Button控件。

  可使用两种技术重用命令文本。一种选择是直接从静态命令对象中提取文本。XAML可以使用Static标记扩展完成这一任务。下面的示例获取命令名New,并将它做为按钮的文本:

<Button Margin="5" Padding="5" Command="New" Content="{x:Static ApplicationCommands.New}"></Button>

  该方法的问题在于,它指示调用命令对象命令对象的ToString()方法。所以,获得的是命令名,而不是命令的文本(对于哪些名称中包含多个单词的命令,使用命令文本更好些,由于命令文本包含空格)。虽然解决这一问题,但须要完成更多工做。这种方法还存在一个问题,一个按钮将同一个命令使用两次,可能会无心间从错误的命令获取文本)。

  更好的解决方案是使用数据绑定表达式。在此使用的数据绑定有些不寻常,由于他绑定到当前元素吗,获取正在使用的Command对象,并提取Text属性。下面是很是复杂的语法:

 <Button Margin="5" Padding="5" Command="New" Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"></Button>

  可经过另外一种更具想象力的方式使用该技术。例如,可以使用一幅小图像设置按钮的内容,而在按钮的工具提示中使用数据绑定表达式显示命令名:

<Button Margin="5" Padding="5" Command="ApplicationCommands.New" ToolTip="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}">
     <Image Source="redx.jpg"  Stretch="None"></Image>
</Button>

  按钮的内容能够是形状,也能够是显示为缩略图的位图。

  显然,这种方法比直接在标记中放置命令文本更麻烦些。然而,若是准备使用不一样的语言本地化应用程序,使用这个方法是值得的。当应用程序启动时,只须要为全部命令设置命令文本便可(若是在建立了命令绑定后改变命令文本,不会产生任何效果。由于Text属性不是依赖项属性,因此没有自动的更改通知来更新用户界面)。

5、直接调用命令

  并不是只能使用实现了ICommandSource接口的类来触发但愿执行的命令。也能够用Execute()方法直接调用来自任何事件处理程序的方法。这时须要传递参数值(或null引用)和对目标元素的引用:

ApplicationCommands.New.Execute(null,targetElement);

  目标元素是WPF开始查找命令绑定的地方。可以使用包含窗口(具备命令绑定)或嵌套的元素(例如,实际引起事件的元素)。

  也可在关联的CommandBinding对象中调用Execute()方法。在这种状况下,不须要提供目标元素,由于会自动将公开正在使用的CommandBindings集合的元素设置为目标元素。

this.CommandBindings[0].Command.Execute(null);

  这种方法只使用了半个命令模型。虽然也触发命令,但不能响应命令的状态变化。若是但愿实现该特性,当命令变为启用或禁用时,也可能但愿处理RoutedCommand.CanExecuteChanged事件进行响应。当引起CanExecuteChanged事件时,须要调用RoutedCommand.CanExecute()方法检查命令是否处于可用状态。若是命令不可用。可禁用或改变用户界面中的部份内容。

6、禁用命令

  若是想要建立状态在启用和禁用之间变化的命令,你将体会到命令模型的真正优点。例如,分析下图中显示的单窗口应用程序,它是有菜单、工具栏以及大文本框构成的简单文本编辑器。该应用程序能够打开文件,建立新的(空白)文档,以及保存所执行的操做。

 

   在该应用程序中,保持New、Open、Save、SaveAs以及Close命令一直可用是很是合理的。但还有一种设计,只有当某些操做使文本相对于原来的文件发生了变化时才启用Save命令。根据约定,可在代码中使用简单的Boolean值来跟踪这一细节:

private bool isDirty = false;

  而后当文本发生变化时设置该标志:

private void txt_TextChanged(object sender, RoutedEventArgs e)
        {
            isDirty = true;
        }

  如今须要从窗口命令绑定传递信息,使连接的控件可根据须要进行更新。技巧是处理命令绑定的CanExecute事件。可经过下面的代码为该事件关联事件处理程序:

CommandBinding binding = new CommandBinding(ApplicationCommands.Save);
            binding.Executed += SaveCommand_Executed;
            binding.CanExecute += SaveCommand_CanExecute;
            this.CommandBindings.Add(binding);

  或使用声明方式:

<Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Save" 
                        Executed="SaveCommand_Executed" 
                        CanExecute="SaveCommand_CanExecute"></CommandBinding>
    </Window.CommandBindings>

  在事件处理程序中,只须要检查isDirty变量,并相应地设置CanExecuteRoutedEventArgs.CanExecute属性:

private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = isDirty;
        }

  若是isDirty的值时false,就禁用命令。若是isDirty的值为true,就启用命令(若是没有设置CanExecute标志,就会保持最近的值)。

  当使用CanExecute事件时,还须要理解一个问题,由WPF负责调用RoutedCommand.CanExecute()方法来触发事件处理程序,并肯定命令的状态。当WPF命令管理器探测到某个确信十分重要的变化——例如,当焦点从一个控件移到另外一个控件上时,或执行了某个命令后,WPF命令管理器就会完成该工做。控件还能引起CanExecuteChanged事件以通知WPF从新评估命令——例如,当用户在文本框中按下一个键时会发生该事件。总之,CanExecute事件会被频繁地触发,而且不该在该事件的处理程序中使用耗时的代码。

  然而,其余因素可能影响命令状态。在当前示例中,为响应其余操做,可能会修改isDirty标志。若是发现命令状态未在正确的时间被更新,可强制WPF为全部正在使用的命令调用CanExecute()方法。经过调用静态方法CommandManager.InvalidateRequerySuggested()完成该工做。而后命令管理器触发RequerySuggested事件,通知窗口中的命令源(按钮、菜单项等)。此后命令源会从新查询它们连接的命令并相应地更新它们的状态。

7、具备内置命令的控件

  一个输入控件可自行处理命令事件。例如,TextBox类处理Cut、Copy以及Paste命令(还有Undo、Redo命令,以及一些来自EditingCommd类的用于选择文本以及将光标移到不一样位置的命令)。

  当控件具备本身的硬编码命令逻辑时,为使命令工做不须要作其余任何事情。例如,对于上节示例的简单编辑器,添加以下工具栏按钮,就会自动获取对剪切、复制和粘贴文本的支持:

<ToolBar>
     <Button Command="Cut">Cut</Button>
     <Button Command="Copy">Copy</Button>
     <Button Command="Paste">Paste</Button>
 </ToolBar>

  如今淡季这些按钮中的任意一个(当文本框具备焦点时),就能够复制、剪切或从剪贴板粘贴文本。有趣的是,文本框还处理CanExecute事件。若是当前未在文本框中选中任何内容,就会禁用剪切和复制命令。当焦点移到其余不支持这些命令的控件时,会自动禁用全部这三个命令(除非关联本身的CanExecute事件处理程序以启动这些命令)。

  该例有一个有趣的细节。Cut、Copy和Paste命令被具备焦点的文本框处理。然而,由工具栏上的按钮触发的命令时彻底独立的元素。在该例中,这个过程之因此可以无缝工做,是由于按钮被放到工具栏上,ToolBar类提供了一些内置逻辑,可将其子元素的CommandTarget属性动态设置为当前具备焦点的控件(从技术角度看,ToolBar控件一直在关注着其父元素,即窗口,并在上下文中查找最近具备焦点的控件,即文本框。ToolBar控件有单独的焦点范围(focus scope),而且在其上下文中按钮是具备焦点的)。

若是在不一样容器(不是ToolBar或Menu控件)中放置按钮,就不会得到这些优点。这意味着除非手动设置CommanTarget属性,不然按钮不能工做。为此,必须使用命令目标元素的绑定的表达式。例如,若是文本框被命名为txtDocument,就应该像下面这样定义按钮:

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

  另外一个较简单的选择是使用附加属性FocusManager.IsFocusScope建立新的焦点范围。当触发命令时,该焦点范围会通知WPF在父元素的焦点范围内查找元素:

<StackPanel FocusManager.IsFocusScope="True">
    <Button Command="Cut">Cut</Button>
    <Button Command="Copy">Copy</Button>
    <Button Command="Paste">Paste</Button>
</StackPanel>

  该方法还有一个附加优势,即相同的命令可应用于多个控件,不像上个示例那样对CommandTarget进行硬编码。此外,Menu和ToolBar控件默认将FocusManager.IsFocusScope属性设置为true,但若是但愿简化命令路由行为,不在父元素上下文中查找具备焦点的元素,也可将该属性设为false。

  在极少数状况下,你可能发现控件支持内置命令,而你并不想启用它。在这种状况下,能够采用三种方法禁用命令。

  理想状况下,控件提供用于关闭命令支持的属性,从而确保控件移除这些特性并连贯地调整自身。例如,TextBox控件提供了IsUndoEnabled属性,为阻止Undo特性,可将该属性设置为false(若是IsUndoEnabled属性为true,Ctrl+Z组合键将触发Undo命令)。

  若是这种作法行不通,可为但愿禁用的命令添加新的命令绑定。而后该命令绑定可提供新的CanExecute事件处理程序,并老是响应false。下面举一个使用该技术删除文本框Cut特性支持的示例:

CommandBinding commandBinding=new CommandBinding(ApplicationCommands.Cut,null,SuppressCommand);
txt.CommandBindings.Add(commandBinding);

  并且该事件处理程序设置CanExecute状态:

private void SupressCommand(object sender,CanExecuteRoutedEventArgs e)
{
  e.CanExecute=false;
  e.Handled=false;    
}

  注意,上面的代码设置了Handled标志以阻止文本框自我执行计算,而文本框可能将CanExecute属性设置为true。

  该方法并不完美。它可成功地为文本框禁用Cut快捷键(Ctrl+X)和上下文菜单中的Cut命令。然而,仍会在上下文菜单中显示处理禁用状态的该选项。

  最后一种选择是,使用InputBinding集合删除触发命令的输入。例如,可以使用带阿妈禁用触发TextBox控件中的Copy命令的Ctrl+C组合键,以下所示:

KeyBinding keyBinding=new KeyBinding(ApplicationCommands.NotACommand,Key.C,ModifierKeys.Control);
txt.InputBinding.Add(keyBinding);

  技巧是使用特定的ApplicationCommands.NotACommand值,该命令什么都不作,它专门用于禁用输入绑定。

  当使用这种方法时,仍启用Copy命令。可经过本身建立的按钮触发该命令(或使用文本框的上下文菜单触发命令,除非也经过将ContextMenu属性设置为null删除了上下文菜单)。

相关文章
相关标签/搜索