八,WPF 命令
- WPF命令模型
ICommand接口
WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理,它包含了两个方法和一个事件:public interface ICommand
{
void Execute(object parameter); //定义在调用此命令时调用的方法。
bool CanExecute(object parameter); //此方法返回命令的状态,如果命令可用则返回true,否则返回false.
event EventHandler CanExecuteChanged; //当命令状态改变时,引发该事件。
}RoutedCommand类
当创建自己的命令时,不会直接实现ICommand接口,而是使用System.Windows.Input.RoutedCommand类。它是WPF中唯一实例了ICommand接口的类,它为事件冒泡和隧道添加了一些额外的基础结构。为了支持路由事件,RoutedCommand类私有地实现了ICommand接口,并且添加了ICommand接口方法的一些不同的版本,最明显的变化是,Execute()和CanExecute()方法使用了一个额外参数。代码示例如下:public void Execute(object parameter, IInputElement target)
{
}
public bool CanExecute(object parameter, IInputElement target)
{
}参数target是开始处理事件的元素,事件从target元素开始,然后冒泡至高层的容器,直到应用程序为了执行合适的任务而处理了事件。
RoutedCommand类还引入了三个属性:Name(命令名称)、OwnerType(包含命令的类)及InputGestures集合(可以被用于触发命令的按键或鼠标操作)。RoutedUICommand类
RoutedUICommand类只增加了一个属性 Text,它是命令显示的文本。在程序中处理的大部分命令不是RoutedCommand对象,而是RoutedUICommand类的实例,RoutedUICommand类继承自RoutedCommand类。而WPF提供的所有预先构建好的命令都是RoutedUICommand对象。RoutedUICommand类用于具有文本的命令,这些文本显示在用户界面中的某些地方(如菜单项文本,工具栏按钮的工具提示)。命令库
因为每个应用程序可能都有大量的命令,且对于许多不同的应用程序,很多命令是通用的,为了减少创建这些命令所需要的工作,WPF提供了一个基本命令库,这些命令通过以下5个专门的静态类的静态属性提供:
许多命令对象都是有一个额外的特征:默认输入绑定,例如,ApplicationCommands.Open命令被映射到Ctrl+O组合键,只要将命令绑定到一个命令源,并为窗口添加该命令源,这个组合键就会被激活,即使没有在用户界面的任何地方显示该命令也同样如此。
- 命令源
命令源是一个实现了ICommandSource接口的控件,它定义了三个属性:
例如,下面的按钮使用Command属性连接到ApplicationCommands.New命令:
<Button Command="New">New</Button>
此时,会看到按钮是被禁用的状态,这是因为按钮查询到命令还没有进行操作绑定,命令的状态为不可用,所以按钮也设置为不可用。
为命令进行操作绑定
下在的代码片段为New命令创建绑定,可将这些代码添加到窗口的构造函数中:CommandBinding binding = new CommandBinding(ApplicationCommands.New);
binding.Executed += new ExecutedRoutedEventHandler(binding_Executed);
this.CommandBindings.Add(binding); void binding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("New command triggered by " + e.Source.ToString());
}尽管习惯上为窗口创建所有绑定,但CommandBindings属性实际上是在UIElement基类中定义的,所以任何元素都支持该属性,但为了得到最大的灵活性,命令绑定通常被添加到顶级窗口,如果希望在多个窗口中使用相同的命令,就需要在这些窗口中分别创建命令绑定。
以上的命令绑定是使用代码生成的,但,如果希望精简代码隐藏文件,使用XAML以声明方式关联命令也很容易,如下所示:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="binding_Executed"></CommandBinding>
</Window.CommandBindings>
<Button Command="ApplicationCommands.New">New</Button>使用多命令源,如下是为命令New创建了一个MenuItem的命令源:
<Menu>
<MenuItem Header="File">
<MenuItem Command="New"></MenuItem>
</MenuItem>
</Menu>注意,没有为New命令的MenuItem对象设置Header属性,这是因为MenuItem类足够智能,如果没有设置Header属性,它将从命令中提取文本(Button不具有此特性)。虽然该特性带来的便利看起来很小,但是如果计划使用不同的语言本地化应用程序,这一特性就很重要了。MunuItem类还有另一个功能,它能够自动提取Command.InputBindings集合中的第一个快捷键,对于以上的New命令,在菜单旁边会显示快捷键:Ctrl+N。
使Button这种不能自动提取命令文本的控件来提取命令文本,有两种技术来重用命令文本,一种是直接从静态的命令对象中提取文本,XAML可以使用Static标记扩展完成这一任务,该方法的问题在于它只是调用命令对象的ToString()方法,因此,得到的是命令的名称,而不是命令的文本。最好的方法是使用数据绑定表达式,以下第二条代码示例绑定表达式绑定到当前元素,获取正在使用的Command对象,并且提取其Text属性:
<Button Command="ApplicationCommands.New" Content="{x:Static ApplicationCommands.New}"></Button>
<Button Command="ApplicationCommands.New" Content="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Command.Text}"></Button>直接调用命令
不是只有实现了ICommandSource接口的类才能触发命令的执行,也可以使用Execute()方法直接调用来自任何事件处理程序的方法:ApplicationCommands.New.Execute(null,targetElement);
targetElement是WPF开始查找命令绑定的地方。可以使用包含窗口(具有命令绑定)或嵌套的元素(实际引发事件的元素)。也可以在关联的CommandBinding对象中调用Execute()方法,对于这种情况,不需要提供目标元素,因为会自动公开正在使用的CommandBindings集合的元素设置为目标元素:this.CommandBindings[0].Command.Execute(null);
禁用命令
例如有一个由菜单、工具栏及一个大的文本框构成的文本编辑器的应用程序,该应用程序可以打开文件,创建新的文档以及保存所进行的操作。在应用程序中,只有文本框中的内容发生了变化才启用Save命令,我们可以在代码中使用一个Boolean变量isUpdate来跟踪是否发生了变化。当文本发生了变化时设置标志。private bool isUpdate = false;
private void txt_TextChanged(object sender, TextChangedEventArgs e)
{
isUpdate = true;
}现在需要从窗口向命令绑定传递信息,从而使连接的控件可以根据需要进行更新,技巧是处理命令绑定的CanExecute事件,代码如下:
CommandBinding binding = new CommandBinding(ApplicationCommands.Save);
binding.Executed += new ExecutedRoutedEventHandler(binding_Executed);
binding.CanExecute += new CanExecuteRoutedEventHandler(binding_CanExecute);
this.CommandBindings.Add(binding);或者使用声明方式:
<CommandBinding Command="Save" Executed="CommandBinding_Executed_1" CanExecute="binding_CanExecute"></CommandBinding>
在事件处理程序中,只需要检查isUpdate变量,并设置CanExecuteRoutedEventArgs.CanExecute属性:
void binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = isUpdate;
}如果isUpdate的值为false,就会禁用Save命令,否则会启用Save命令。
当使用CanExecute事件时,是由WPF负责调用RoutedCommand.CanExecute()方法触发事件处理程序,并且确定命令的状态。当WPF命令管理器探测到一个确信是重要的变化时,例如,当焦点从一个控件移动到另一个控件,或者执行了一个命令之后,WPF命令管理器就会完成该工作。控件还能引发CanExecuteChanged事件以通知WPF重新评估命令,例如,当用户在文本框中按下一个键时就会发生该事件,总之,CanExecute事件会被频繁的触发,所以不应当在该事件的处理程序中使用耗时的代码。 然而,其化因素有可能会影响命令的状态,在以上的示例中,为了响应其它操作,isUpdate标志可能会被修改,如果注意到命令状态没有在正确的时间更新,可以强制WPF为所有正在使用的命令调用CanExecute()方法,通过调用静态的CommandManager.InvalidateRequerySuggested()方法完成该工作。然后命令管理器触发RequerySuggested事件,通知窗口中的命令源。然后命令源会查询它们连接的命令并相应地更新它们的状态。具有内置命令的控件
一些输入控件自身可以处理命令事件,如TextBox类的Cut、Copy及Paste命令,以及一些来自EditingCommand类的用于选择文本以及将光标移到不同位置的命令,把此类命令绑定到命令源会自动获取对应命令的功能,而不需要再为命令绑定操作。如:
<ToolBar>
<Button Command="Cut" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Copy" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Paste" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
</ToolBar>此外,文本框还处理了CanExecute事件,如果在文本框中当前没有选中任何内容,剪切和复制命令就会被禁用,当焦点改变到其他不支持这些命令的控件时,这些命令都会被禁用。
在以上代码中使用了ToolBar控件,它提供了一些内置逻辑,可以将它的子元素的CommandTarget属性自动设置为具有焦点的控件。但如果在不同的容器(不是ToolBar或Menu控件)中放置按钮,就不会得到这一优点而按钮不能正常工作,此时就需要手动设置CommandTarget属性,为此,必须使用命名目标元素的绑定表达式。如:<StackPanel Grid.Row="1">
<Button Command="Cut" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/>
<Button Command="Copy" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/>
<Button Command="Paste" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/>
</StackPanel>另一个较简单的选择是使用FocusManager.IsFocusScope附加属性创建新的焦点范围,当命令触发时,该焦点范围会通知WPF在父元素的焦点范围中查找元素:
<StackPanel FocusManager.IsFocusScope="True" Grid.Row="1">
<Button Command="Cut" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Copy" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
<Button Command="Paste" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button>
</StackPanel>在有些情况下,可能发现控件支持内置命令,但不想启用它,此时有三种方法可以禁用命令:
理想情况下,控件会提供用于关闭命令支持的属性,例如TextBox控件的IsUndoEnabled属性。
如果控件没有提供关闭命令支持的属性,还可以为希望禁用的命令添加一个新的命令绑定,然后该命令绑定可以提供新的事件处理程序。且总是将CanExecute属性设置为false,下面是一个使用该技术删除文本框Cut特性支持的示例:
CommandBinding binding = new CommandBinding(ApplicationCommands.Cut, null, SuppressCommand);
this.CommandBindings.Add(binding);
private void SuppressCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute= false;
e.Handled= true;
}上面的代码设置了Handled标志,以阻止文本框自我执行计算,而文本框可能将CanExecute属性设置为true.
使用InputBinding集合删除触发命令的输入,例如,可以使用代码禁用触发TextBox控件中Cut命令的Ctrl+X组合键,如下所示:
KeyBinding keyBinding = new KeyBinding(ApplicationCommands.NotACommand, Key.X, ModifierKeys.Control);
txt.InputBindings.Add(keyBinding);ApplicationCommands.NotACommand命令不做任何事件,它专门用于禁用输入绑定。
文本框默认显示上下文菜单,可以通过将ContextMenu属性设置为null删除上下文本菜单:<TextBoxGrid.Row="3" Name="txt" ContextMenu="{x:Null}" TextWrapping="Wrap" TextChanged="txt_TextChanged" />
自定义命令
下面的示例定义了一个Requery的命令:public class DataCommands
{
private static RoutedUICommand requery;
static DataCommands()
{
InputGestureCollection inputs= new InputGestureCollection();
inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
requery= new RoutedUICommand("查询", "Requery", typeof(DataCommands), inputs);
}
public static RoutedUICommand Requery //通过静态属性提供自定义的命令
{
get { return requery; }
}
}使用Requery命令时需要将它的.Net名称空间映射为一个XML名称空间,XAML代码如下:
<Window x:Class="WpfApplication1.Test4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Test4" Height="300" Width="300">
<Window.CommandBindings>
<CommandBinding Command="local:DataCommands.Requery" Executed="Requery_Executed"></CommandBinding>
</Window.CommandBindings>
<Grid>
<Button Command="local:DataCommands.Requery" CommandParameter="ai" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}" />
</Grid>
</Window>在以上代码中使用CommandParameter为命令传递了参数,命令的事件处理方法中就可以使用Parameter属性获取该参数:
private void Requery_Executed(object sender, ExecutedRoutedEventArgs e)
{
string parameters = e.Parameter.ToString();
}在不同的位置使用相同的命令
在WPF命令模型中,一个重要的思想是Scope。尽管每个命令只有一个副本,但是使用命令的效果却会根据触发命令位置而不同,例如,如果有两个文本框,它们都支持Cut、Copy、Paste命令,操作只会在当前具有焦点的文本框中发生。但是对于自定实现的命令如New、Open、Requery及Save命令就区分不出是哪一个文本框触发的命令,尽管ExecuteRoutedEventArgs对象提供了Source属性,但是该属性反映的是具有命令绑定的元素,也就是容器窗口。此问题的解决方法是使用文本框的CommandBindings集合为每个文本框分别绑定命令。跟踪和翻转命令
创建自己的用于支持命令翻转的数据结构,示例中定义一个名为CommandHistoryItem的类用于存储命令状态:public class CommandHistoryItem
{
public string CommandName { get; set; } //命令名称
public UIElement ElementActedOn { get; set; } //执行命令的元素
public string PropertyActedOn { get; set; } //在目标元素中被改变了的属性
public object PreviousState { get; set; } //用于保存受影响元素以前状态的对象
public CommandHistoryItem(string commandName)
: this(commandName, null, "", null)
{
} public CommandHistoryItem(string commandName, UIElement elementActedOn, string propertyActed, object previousState)
{
this.CommandName = commandName;
this.ElementActedOn = elementActedOn;
this.PropertyActedOn = propertyActed;
this.PreviousState = previousState;
} public bool CanUndo
{
get { return (ElementActedOn != null && PropertyActedOn != ""); }
} /// <summary>
/// 使用反射为修改过的属性应用以前的值
/// </summary>
public void Undo()
{
Type elementType = ElementActedOn.GetType();
PropertyInfo property = elementType.GetProperty(PropertyActedOn);
property.SetValue(ElementActedOn, PreviousState, null);
}
}需要自定义一个执行应用程序范围内翻转操作的命令,如下所示:
private static RoutedUICommand applicationUndo;
public static RoutedUICommand ApplicationUndo
{
get { return applicationUndo; }
} static ApplicationUndoDemo()
{
applicationUndo = new RoutedUICommand("Applicaion Undo", "ApplicationUndo", typeof(ApplicationUndoDemo));
}可以使用CommandManager类来跟踪任何命令的执行情况,它提供了几个静态事件:Executed及PreviewExecuted,无论何时,当执行任何一个命令时都会触发它们。 尽管CommandManager类挂起了Executed事件,但是仍然可以使用UIElement.AddHandler()方法关联事件处理程序,并且为可选的第三个参数传递true值,从而允许接收事件。下面的代码在窗口的构造函数中关联PreviewExecuted事件处理程序,且在关闭窗口时解除关联:
public ApplicationUndoDemo()
{
InitializeComponent();
this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandPreviewExecute),true);
} private void Window_Unloaded(object sender, RoutedEventArgs e)
{
this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandPreviewExecute));
}当触发PreviewExecute事件时,需要确定准备执行的命令是否是我们所关心的,如果是就创建CommandHistoryItem对象,且将其添加到历史命令集合中。
private void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
// Ignore menu button source.
if (e.Source is ICommandSource) return; // Ignore the ApplicationUndo command.
if (e.Command == MonitorCommands.ApplicationUndo) return; // Could filter for commands you want to add to the stack
// (for example, not selection events). TextBox txt = e.Source as TextBox;
if (txt != null)
{
RoutedCommand cmd = (RoutedCommand)e.Command; CommandHistoryItem historyItem = new CommandHistoryItem(
cmd.Name, txt, "Text", txt.Text); ListBoxItem item = new ListBoxItem();
item.Content = historyItem;
lstHistory.Items.Add(historyItem); // CommandManager.InvalidateRequerySuggested();
}
}使用CanExecute事件处理程序,确保只有当Undo历史中有一项时,才能执行翻转操作:
<Window.CommandBindings>
<CommandBinding Command="local:ApplicationUndoDemo.ApplicationUndo" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"></CommandBinding>
</Window.CommandBindings>private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
//不知lstHistory.Items[lstHistory.Items.Count - 1]为什么强制转化不成CommandHistoryItem,有待解决
CommandHistoryItem historyItem = (CommandHistoryItem)lstHistory.Items[lstHistory.Items.Count - ];
if (historyItem.CanUndo)
historyItem.Undo();
lstHistory.Items.Remove(historyItem);
} private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (lstHistory == null || lstHistory.Items.Count == )
e.CanExecute = false;
else
e.CanExecute = true;
}
八,WPF 命令的更多相关文章
- WPF基础学习笔记整理 (八) 命令
基础知识: 命令是应用程序的任务,并跟踪任务是否能够被执行. 命令不包含执行应用程序任务的代码. 命令是比事件更高级的元素.默认的命令目标是当前获得焦点的元素. 良好的Win应用程序,应用程序逻辑不应 ...
- WPF快速入门系列(5)——深入解析WPF命令
一.引言 WPF命令相对来说是一个崭新的概念,因为命令对于之前的WinForm根本没有实现这个概念,但是这并不影响我们学习WPF命令,因为设计模式中有命令模式,关于命令模式可以参考我设计模式的博文:h ...
- 【WPF学习】第三十一章 WPF命令模型
WPF命令模型由许多可变的部分组成.总之,它们都具有如下4个重要元素: 命令:命令表示应用程序任务,并且跟踪任务是否能够被执行.然而,命令实际上不包含执行应用程序任务的代码. 命令绑定:每个命令绑定针 ...
- WPF 命令基础
1命令的组成 命令源:就是谁发送的命令. 命令目标:就是这个命令发送给谁,谁接受的命令. 命令:就是命令的内容. 命令关联:就是把命令和外围的逻辑关联起来,主要用来判断命令是否可以执行和执行完以后干点 ...
- WPF命令
WPF的命令是经常使用的,在MVVM中,RelayCommand更是用得非常多,但是命令的本质究竟是什么,有了事件为什么还要命令,命令与事件的区别是什么呢?MVVM里面是如何包装命令的呢?命令为什么能 ...
- WPF 命令的简单总结
WPF的命令Command主要解决的问题,就是代码复用.一个很重要的应用意义,在于它将很多地方需要的调用的相同操作,以统一的方式管理,却又提供了不同的访问结果. 举个例子来说,我可能通过“点击butt ...
- WPF命令(Command)介绍、命令和数据绑定集成应用
要开始使用命令,必须做三件事: 一:定义一个命令 二:定义命令的实现 三:为命令创建一个触发器 WPF中命令系统的基础是一个相对简单的ICommand的接口,代码如下: public interfac ...
- WPF命令绑定 自定义命令
WPF的命令系统是wpf中新增加的内容,在以往的winfom中并没有.为什么要增加命令这一块内容.在winform里面的没有命令只使用事件的话也可以实现程序员希望实现的功能.这个问题在很多文章中都提到 ...
- WPF命令使用
What 命令包含以下部分: 命令:一个实现了ICommand接口的类,RoutedCommand是WPF里最常用的命令类,其它命令类大多派生自RoutedCommand 命令源:触发命令的对象,如b ...
随机推荐
- Nginx模块开发入门
前言 Nginx是当前最流行的HTTP Server之一,根据W3Techs的统计,目前世界排名(根据Alexa)前100万的网站中,Nginx的占有率为6.8%.与Apache相比,Nginx在高并 ...
- istringstream
编写程序,将来自一个文件中的行保存在一个vector<string>中,然后使用一个istringstream从vector读取数据成员,每次读取一个单词 #include <ios ...
- [012]泛型--lambda表达式捕获
lambda表达式的捕获跟参数差不多,可以是值或者引用. 1.值捕获 与传值参数类似,采用值捕获的前期是变量可以拷贝:与参数不通透的是:被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝. ...
- Asp.Net 之 基本控件FileUpload上传控件
1.前台代码: <asp:FileUpload ID="FileUpload" runat="server" /> <asp:Button I ...
- 通过代码设置button中文字的对齐方式
// button.titleLabel.textAlignment = NSTextAlignmentLeft; 这句无效 button.contentHorizontalAlignment = U ...
- 微信、微博、qq图标服务实现
实现原理:变化前的图标和变化后的图标在一张图片上,用这张图片作为背景,通过定义背景的位置来实现显示哪个图标,其中还带着滑动的动画效果. <!DOCTYPE html> <html l ...
- Redis集群
一.Redis集群原理 集群技术是构建高性能网站架构的重要手段,试想在网站承受高并发访问压力的同时,还需要从海量数据中查询出满足条件的数据,并快速响应,我们必然想到的是将数据进行切片,把数据根据某种规 ...
- gluster 安装配置基本指南
基于网络上的多篇文章,做了一些调整. gluster安装 ### Installing Gluster wget -P /etc/yum.repos.d http://download.gluste ...
- plupload+struts2实现文件上传下载
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" ...
- html-----018----HTML Web Server/HTML URL 字符编码
HTML Web Server 如果希望向世界发布您的网站,那么您必须把它存放在 web 服务器上. 托管自己的网站 在自己的服务器上托管网站始终是一个选项.有几点需要考虑: 硬件支出 如果要运行“真 ...