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

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

一、命令源

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

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

表 ICommandSource接口的属性

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

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

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

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

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

二、命令绑定

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

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

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

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

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

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

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

  1. //Create the binding
  2. CommandBinding binding=new CommandBinding(ApplicationCommands.New);
  3.  
  4. //Attach the event handler
  5. binding.Executed+=NewCommand_Executed;
  6.  
  7. //Register the binding
  8. this.CommandBinding.Add(binding);

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

  尽管习惯上为窗口添加所有绑定,但CommandBindings属性实际是在UIElement基类中定义的。这意味着任何元素都支持该属性。例如,如果将命令绑定直接添加到使用它的按钮中,这个示例仍工作的很好(尽管不能在将该绑定重用与其他高级元素)。为得到最大的灵活性,命令绑定通常被添加到顶级窗口。如果希望在多个窗口中使用相同相同的命令,需要在这些窗口中分别创建命令绑定。

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

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

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

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

  1. <Window x:Class="Commands.TestNewCommand"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="TestNewCommand" Height="300" Width="300">
  5. <Window.CommandBindings>
  6. <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand"></CommandBinding>
  7. </Window.CommandBindings>
  8. <StackPanel>
  9. <Button Margin="5" Padding="5" Command="ApplicationCommands.New">New</Button>
  10. </StackPanel>
  11. </Window>

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

三、使用多命令源

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

  1. <Menu >
  2. <MenuItem Header="File">
  3. <MenuItem Command="New"></MenuItem>
  4. </MenuItem>
  5. </Menu>

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

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

 四、微调命令文本

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

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

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

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

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

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

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

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

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

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

五、直接调用命令

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

  1. ApplicationCommands.New.Execute(null,targetElement);

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

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

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

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

六、禁用命令

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

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

  1. private bool isDirty = false;

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

  1. private void txt_TextChanged(object sender, RoutedEventArgs e)
  2. {
  3. isDirty = true;
  4. }

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

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

  或使用声明方式:

  1. <Window.CommandBindings>
  2. <CommandBinding Command="ApplicationCommands.Save"
  3. Executed="SaveCommand_Executed"
  4. CanExecute="SaveCommand_CanExecute"></CommandBinding>
  5. </Window.CommandBindings>

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

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

  如果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类的用于选择文本以及将光标移到不同位置的命令)。

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

  1. <ToolBar>
  2. <Button Command="Cut">Cut</Button>
  3. <Button Command="Copy">Copy</Button>
  4. <Button Command="Paste">Paste</Button>
  5. </ToolBar>

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

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

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

  1. <Button Command="Cut" CommandTarget="{Binding ElementName=txtDocument}">Cut</Button>
  2. <Button Command="Copy" CommandTarget="{Binding ElementName=txtDocument}">Copy</Button>
  3. <Button Command="Paste" CommandTarget="{Binding ElementName=txtDocument}">Paste</Button>

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

  1. <StackPanel FocusManager.IsFocusScope="True">
  2. <Button Command="Cut">Cut</Button>
  3. <Button Command="Copy">Copy</Button>
  4. <Button Command="Paste">Paste</Button>
  5. </StackPanel>

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

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

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

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

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

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

  1. private void SupressCommand(object sender,CanExecuteRoutedEventArgs e)
  2. {
  3. e.CanExecute=false;
  4. e.Handled=false;
  5. }

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

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

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

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

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

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

【WPF学习】第三十二章 执行命令的更多相关文章

  1. 【WPF学习】第二十二章 文本控件

    WPF提供了三个用于输入文本的控件:TextBox.RichTextBox和PasswordBox.PasswordBox控件直接继承自Control类.TextBox和RichTextBox控件间接 ...

  2. Gradle 1.12用户指南翻译——第三十二章. JDepend 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  3. “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. “全栈2019”Java第三十二章:增强for循环Foreach语法

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  5. o'Reill的SVG精髓(第二版)学习笔记——第十二章

    第十二章 SVG动画 12.1动画基础 SVG的动画特性基于万维网联盟的“同步多媒体集成语言”(SMIL)规范(http://www.w3.org/TR/SMIL3). 在这个动画系统中,我们可以指定 ...

  6. 【WPF学习】第二十六章 Application类——应用程序的生命周期

    在WPF中,应用程序会经历简单的生命周期.在应用程序启动后,将立即创建应用程序对象,在应用程序运行时触发各种应用程序事件,你可以选择监视其中的某些事件.最后,当释放应用程序对象时,应用程序将结束. 一 ...

  7. 【WPF学习】第二十四章 基于范围的控件

    WPF提供了三个使用范围概念的控件.这些控件使用在特定最小值和最大值之间的数值.这些控件——ScrollBar.ProgressBar以及Slider——都继承自RangeBase类(该类又继承自Co ...

  8. 第三十二章 Linux常规练习题(一)

    一.练习题一 1.超级用户(管理员用户)提示符是____,普通用户提示符是____.2.linux关机重启的命令有哪些 ?3.bash是什么?4.bash特性, 常见的bash特性有哪些?5.网卡的配 ...

  9. 【WPF学习】第十五章 WPF事件

    前两章学习了WPF事件的工作原理,现在分析一下在代码中可以处理的各类事件.尽管每个元素都提供了许多事件,但最重要的事件通常包括以下5类: 生命周期事件:在元素被初始化.加载或卸载时发生这些事件. 鼠标 ...

随机推荐

  1. 洛谷$P2605\ [ZJOI2010]$基站选址 线段树优化$dp$

    正解:线段树优化$dp$ 解题报告: 传送门$QwQ$ 难受阿,,,本来想做考试题的,我还造了个精妙无比的题面,然后今天讲$dp$的时候被讲到了$kk$ 先考虑暴力$dp$?就设$f_{i,j}$表示 ...

  2. ELK部署检测nginx日志demo

    ELK E: ElasticSearch 搜索引擎 存储 https://www.elastic.co/cn/downloads/elasticsearch L: Logstash 日志收集 http ...

  3. SpringBoot集成Mybatis动态多数据源后,MybatisPlus的IPage失效的问题解决方案

    背景 之前做数据抽取的时候,搭了一个mybatis动态数据源切换的架子.方便他们写抽取的代码.今天同事问我,架子里面的mybatisplus的IPage失效了是什么问题.想了一下,应该是写动态数据源的 ...

  4. ElementUi 两个表格反选

    ElementUi 两个表格反选 1.先看看实现的图 表格内容显示 <el-row :gutter="20"> <el-col :span="16&qu ...

  5. jenkins介绍和安装

    1.jenkins介绍 1.1 Jenkins概念: • Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,无论用的是什么平台. • 这是一个免费的源代码,可以处理任何类型的构建或持 ...

  6. BigInteger&BigDecimal类

    BigInteger类 当需要处理超过 long 数值范围的大整数时,java.math 包中的 BigInteger 类提供任意精度的整数运算. 构造方式 //构造方法,将BigInteger的十进 ...

  7. 【转】ArcGIS Server 10.1 动态图层—添加栅格

    本文将介绍如何通过arcgisserver10.1动态图层添加栅格影像.与添加矢量数据不同的是,天际栅格用到了RasterDataSource接口,如下所示 <esri:DynamicLayer ...

  8. 使用java做一个能赚钱的微信群聊机器人(2020年基于PC端协议最新可用版)

    前言 微信群机器人,主要用来管理群聊,提供类似天气查询.点歌.机器人聊天等用途. 由于微信将web端的协议封杀后,很多基于http协议的群聊机器人都失效了,所以这里使用基于PC端协议的插件来实现. 声 ...

  9. css3让元素自适应高度

    知识点: viewport:可视窗口,也就是浏览器.vw Viewport宽度, 1vw 等于viewport宽度的1%vh Viewport高度, 1vh 等于viewport高的的1% calc( ...

  10. C#实现DataTable转TXT文件

    实现DataTable转TXT文件代码如下: public ExecutionResult DataTableToTxt(DataTable vContent, string vOutputFileP ...