zoukankan      html  css  js  c++  java
  • 【WPF学习】第三十二章 执行命令

      前面章节已经对命令进行了深入分析,分析了基类和接口以及WPF提供的命令库。但尚未例举任何使用这些命令的例子。

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

    一、命令源

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

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

    表 ICommandSource接口的属性

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

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

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

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

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

    二、命令绑定

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

      例如,如果创建上一节提到的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属性,但需要输入正确的命令名称——由于并未提供包含命令的下拉列表,因此不能方便地从列表中选择命令。

    三、使用多命令源

      上面示例中触发普通事件的方式看起来不那么直接。然而,当添加使用相同命令的更多控件时,额外命令层的意义就提现出出来了。例如,可添加如下也使用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快捷键(如下图所示)。

     四、微调命令文本

      既然菜单具有自动提取命令项文本的功能,肯恩改回好奇其他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属性不是依赖项属性,所以没有自动的更改通知来更新用户界面)。

    五、直接调用命令

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

    ApplicationCommands.New.Execute(null,targetElement);

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

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

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

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

    六、禁用命令

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

       在该应用程序中,保持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事件,通知窗口中的命令源(按钮、菜单项等)。此后命令源会重新查询它们链接的命令并相应地更新它们的状态。

    七、具有内置命令的控件

      一个输入控件可自行处理命令事件。例如,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删除了上下文菜单)。

  • 相关阅读:
    【转】 java中Class对象详解和类名.class, class.forName(), getClass()区别
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    107. Binary Tree Level Order Traversal II
    109. Convert Sorted List to Binary Search Tree
    108. Convert Sorted Array to Binary Search Tree
    110. Balanced Binary Tree
    STL容器迭代器失效问题讨论
    113. Path Sum II
    112. Path Sum
  • 原文地址:https://www.cnblogs.com/Peter-Luo/p/12274329.html
Copyright © 2011-2022 走看看