【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[].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删除了上下文菜单)。
【WPF学习】第三十二章 执行命令的更多相关文章
- 【WPF学习】第二十二章 文本控件
WPF提供了三个用于输入文本的控件:TextBox.RichTextBox和PasswordBox.PasswordBox控件直接继承自Control类.TextBox和RichTextBox控件间接 ...
- Gradle 1.12用户指南翻译——第三十二章. JDepend 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java第三十二章:增强for循环Foreach语法
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- o'Reill的SVG精髓(第二版)学习笔记——第十二章
第十二章 SVG动画 12.1动画基础 SVG的动画特性基于万维网联盟的“同步多媒体集成语言”(SMIL)规范(http://www.w3.org/TR/SMIL3). 在这个动画系统中,我们可以指定 ...
- 【WPF学习】第二十六章 Application类——应用程序的生命周期
在WPF中,应用程序会经历简单的生命周期.在应用程序启动后,将立即创建应用程序对象,在应用程序运行时触发各种应用程序事件,你可以选择监视其中的某些事件.最后,当释放应用程序对象时,应用程序将结束. 一 ...
- 【WPF学习】第二十四章 基于范围的控件
WPF提供了三个使用范围概念的控件.这些控件使用在特定最小值和最大值之间的数值.这些控件——ScrollBar.ProgressBar以及Slider——都继承自RangeBase类(该类又继承自Co ...
- 第三十二章 Linux常规练习题(一)
一.练习题一 1.超级用户(管理员用户)提示符是____,普通用户提示符是____.2.linux关机重启的命令有哪些 ?3.bash是什么?4.bash特性, 常见的bash特性有哪些?5.网卡的配 ...
- 【WPF学习】第十五章 WPF事件
前两章学习了WPF事件的工作原理,现在分析一下在代码中可以处理的各类事件.尽管每个元素都提供了许多事件,但最重要的事件通常包括以下5类: 生命周期事件:在元素被初始化.加载或卸载时发生这些事件. 鼠标 ...
随机推荐
- 洛谷$P2605\ [ZJOI2010]$基站选址 线段树优化$dp$
正解:线段树优化$dp$ 解题报告: 传送门$QwQ$ 难受阿,,,本来想做考试题的,我还造了个精妙无比的题面,然后今天讲$dp$的时候被讲到了$kk$ 先考虑暴力$dp$?就设$f_{i,j}$表示 ...
- ELK部署检测nginx日志demo
ELK E: ElasticSearch 搜索引擎 存储 https://www.elastic.co/cn/downloads/elasticsearch L: Logstash 日志收集 http ...
- SpringBoot集成Mybatis动态多数据源后,MybatisPlus的IPage失效的问题解决方案
背景 之前做数据抽取的时候,搭了一个mybatis动态数据源切换的架子.方便他们写抽取的代码.今天同事问我,架子里面的mybatisplus的IPage失效了是什么问题.想了一下,应该是写动态数据源的 ...
- ElementUi 两个表格反选
ElementUi 两个表格反选 1.先看看实现的图 表格内容显示 <el-row :gutter="20"> <el-col :span="16&qu ...
- jenkins介绍和安装
1.jenkins介绍 1.1 Jenkins概念: • Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,无论用的是什么平台. • 这是一个免费的源代码,可以处理任何类型的构建或持 ...
- BigInteger&BigDecimal类
BigInteger类 当需要处理超过 long 数值范围的大整数时,java.math 包中的 BigInteger 类提供任意精度的整数运算. 构造方式 //构造方法,将BigInteger的十进 ...
- 【转】ArcGIS Server 10.1 动态图层—添加栅格
本文将介绍如何通过arcgisserver10.1动态图层添加栅格影像.与添加矢量数据不同的是,天际栅格用到了RasterDataSource接口,如下所示 <esri:DynamicLayer ...
- 使用java做一个能赚钱的微信群聊机器人(2020年基于PC端协议最新可用版)
前言 微信群机器人,主要用来管理群聊,提供类似天气查询.点歌.机器人聊天等用途. 由于微信将web端的协议封杀后,很多基于http协议的群聊机器人都失效了,所以这里使用基于PC端协议的插件来实现. 声 ...
- css3让元素自适应高度
知识点: viewport:可视窗口,也就是浏览器.vw Viewport宽度, 1vw 等于viewport宽度的1%vh Viewport高度, 1vh 等于viewport高的的1% calc( ...
- C#实现DataTable转TXT文件
实现DataTable转TXT文件代码如下: public ExecutionResult DataTableToTxt(DataTable vContent, string vOutputFileP ...