WPF 跟踪命令和撤销命令(复原)
WPF 命令模型缺少一个特性是复原命令。尽管提供了一个 ApplicationCommands.Undo 命令,但是该命令通常被用于编辑控件(如 TextBox 控件),以维护它们自己的 Undo 历史。如果希望支持应用程序范围内的 Undo 特性,就需要在内部跟踪以前的状态,并且触发 Undo 命令时还原该状态。
遗憾的是,扩展 WPF 命令系统并不容易。相对来说没有几个入口点可以使用连接自定义逻辑。为了创建通用、可重用的 Undo 特性,需要创建一组全新的“能够撤销命令的”命令类,以及一个特定类型的命令绑定。我们需要设计自己的用于跟踪和复原命令的系统,使用 CommandManager 类保存命令历史。下图显示了本文的例子。在该例子中,窗口包含两个文本框和一个列表框,可以自由地在这两个文本框中输入内容,而列表框则一直跟踪在这两个文本框中发生的所有命令。可以通过单击 ‘复原’ 按钮还原最后一个命令。

该例使用一个名为 CommandHistoryItem 的类来存储信息状态,例如:刚刚删除的字符。
每个 CommandHistoryItem 对象跟踪以下几部分信息:
- 命令名称
- 执行命令的元素。
- 在目标元素中被改变了的属性
- 保存目标元素受影响以前状态的对象
这一设计非常巧妙,它为一个元素存储状态。如果存储整个窗口状态的快照,会显著增加内存的使用量。然而,如果具有大量数据(如文本框有几十行文本),Undo 操作的负担就很大了。解决方法是限制在历史中存储项的数量,或者使用更加智能(也更加复杂)的方法只存储被改变的数据信息,而不是存储所有数据。
CommandHistoryItem 类还提供了一个通用的 Undo() 方法。该方法使用反射为修改过的属性应用以前的值,用于恢复 TextBox 控件中的文本,但是对于更复杂的应用程序,需要用到 CommandHistoryItem 类的层次结构,每个类都可以使用不同方式还原不同类型的操作。
下面是 CommandHistoryItem 类的完整代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows; namespace _1028_UndoCommand
{
public class CommandHistoryItem
{
/// <summary>
/// 命令名称
/// </summary>
public string CommandName { get; set; } /// <summary>
/// 执行命令的元素
/// </summary>
public UIElement ElementActedOn { get; set; } /// <summary>
/// 在目标元素中被改变了的属性
/// </summary>
public string PropertyAcedOn { get; set; } /// <summary>
/// 保存目标元素受影响以前状态的对象
/// </summary>
public object PreviousState { get; set; } public CommandHistoryItem(string commandName)
: this(commandName, null, string.Empty, null)
{ } /// <summary>
/// 初始化CommandHistoryItem对象
/// </summary>
/// <param name="commandName">命令名称</param>
/// <param name="elementActedOn">执行命令的元素</param>
/// <param name="propertyAcedOn">在目标元素中被改变了的属性</param>
/// <param name="previousState">保存目标元素受影响以前状态的对象</param>
public CommandHistoryItem(string commandName, UIElement elementActedOn, string propertyAcedOn, object previousState)
{
this.CommandName = commandName;
this.ElementActedOn = elementActedOn;
this.PropertyAcedOn = propertyAcedOn;
this.PreviousState = previousState;
} /// <summary>
/// 获取是否能够撤销操作
/// </summary>
public bool CanUndo
{
get
{
return (ElementActedOn != null && PropertyAcedOn != string.Empty);
}
} /// <summary>
/// 复原
/// </summary>
public void Undo()
{
//使用反射技术将值还原回去
Type elementType = ElementActedOn.GetType();
PropertyInfo property = elementType.GetProperty(PropertyAcedOn);
property.SetValue(ElementActedOn, PreviousState, null);
}
}
}
还需要的下一个要素是执行应用程序范围内的 Undo 操作的命令。 ApplicationCommands.Undo 命令是不合适的,因为为了不同的目的,它已经被用于单独的文本框控件(复原最后的编辑变化)。所以我们需要创建一个新的命令,如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input; namespace _1028_UndoCommand
{
/// <summary>
/// 复原命令
/// </summary>
public class UndoCommands
{
static RoutedUICommand applicationUndo; /// <summary>
/// 复原命令
/// </summary>
public static RoutedUICommand ApplicationUndo
{
get { return UndoCommands.applicationUndo; }
} static UndoCommands()
{
applicationUndo = new RoutedUICommand("撤销操作", "Application Undo", typeof(UndoCommands));
}
}
}
到目前为止,除了执行 Undo 操作的反射代码有点儿意思之外,其他代码没有什么值得注意的地方。更困难的部分是将该命令历史集成到WPF命令模型中,所以这里我们需要使用到 CommandManager 类中的一个事件。 该类提供了几个静态事件,这些事件包括 CanExecute、PreviewCanExecute、Executed 以及 PreviewExecuted 。 在本示例中, Executed 和 PreviewExecuted 事件是最有趣的,因为无论在何时,当执行任何一个命令都会引发它们。然而, Executed 事件是在命令执行完之后被触发的,这时已经来不及在命令历史中保存被影响的控件状态了。所以我们需要响应 PreviewExecuted 事件,该事件在命令执行之前一刻被触发。
下面是关联 PreviewExecued 事件的 处理程序,并且当关闭窗口时接触关联。
public MainWindow()
{
InitializeComponent();
this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted));
} private void Window_Unloaded_1(object sender, RoutedEventArgs e)
{
this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted));
}
当触发事件时,我们需要将历史记录到项中(ListBox),于是有了下面的代码:
void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
//忽略按钮源
if (e.Source is ICommandSource)
return; //忽略自编写的ApplicationUndo命令
if (e.Command == UndoCommands.ApplicationUndo)
return; TextBox txt = e.Source as TextBox;
if (txt == null)
return; //记录修改之前的数据到集合中
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);
}
该示例在 ListBox 控件中存储了所有 CommandHistoryItem 对象。 ListBox 控件的 DisplayMember 属性被设置为 CommandName ,所以它会显示每个项目的 CommandHistoryImte.CommandName 属性。以上代码只为由文本框引发的命令提供 Undo 特性。然而,处理窗口中的任何文本框通常足够了。为了支持其他控件和属性,需要对代码进行扩展。
最后一个细节是执行应用程序范围内 Undo 操作的代码。使用 CanExecute 事件处理程序,可以确保只有当在 Undo 历史中至少有一项时,才能执行Undo操作:
//评估复原命令是否可用
private void CommandBinding_CanExecute_1(object sender, CanExecuteRoutedEventArgs e)
{
if (lstHistory == null || lstHistory.Items.Count == )
e.CanExecute = false;
else
e.CanExecute = true;
}
为了恢复最近的修改,只需要调用 CommandHistoryItem 的 Undo() 方法,然后从 ListBox 项中删除该项即可:
//执行复原命令
private void CommandBinding_Executed_1(object sender, ExecutedRoutedEventArgs e)
{
CommandHistoryItem historyImte = (CommandHistoryItem)lstHistory.Items[lstHistory.Items.Count - ]; if (historyImte.CanUndo)
historyImte.Undo(); lstHistory.Items.Remove(historyImte);
}
至此,该示例就结束了。尽管该示例演示了相关概念,并提供了一个简单的应用程序,但是要在实际的应用程序中使用这一方法,还需要进行许多改进。例如,需要花费大量的时间改进 CommandMamager.PreviewExecuted 事件的处理程序,以忽略哪些明显不需要跟踪的命令(当前,如使用键盘选择文本的事件以及单击空格键引发的命令等)。
源码下载:http://files.cnblogs.com/andrew-blog/1028_UndoCommand.rar
开发工具:VS2012
参考:http://www.wxzzz.com/WPF/WPF_GenZongMingLing_CheXiaoMingLing
WPF 跟踪命令和撤销命令(复原)的更多相关文章
- WPF命令绑定 自定义命令
WPF的命令系统是wpf中新增加的内容,在以往的winfom中并没有.为什么要增加命令这一块内容.在winform里面的没有命令只使用事件的话也可以实现程序员希望实现的功能.这个问题在很多文章中都提到 ...
- WPF学习笔记四之命令
1.概念 对于程序来说,命令就是一个个任务,例如保存,复制,剪切这些操作都可以理解为一个个命令.即当我们点击一个复杂按钮时,此时就相当于发出了一个复制的命令,即告诉文本框执行一个复杂选中内容的操作,然 ...
- WPF学习之深入浅出话命令
WPF为我们准备了完善的命令系统,你可能会问:"有了路由事件为什么还需要命令系统呢?".事件的作用是发布.传播一些消息,消息传达到了接收者,事件的指令也就算完成了,至于如何响应事件 ...
- WPF学习(7)命令
在上一篇中,我们学习了WPF的路由事件,而在本节将学习一个更为抽象且松耦合的事件版本,即命令.最明显的区别是,事件是与用户动作相关联的,而命令是那些与用户界面想分离的动作,例如我们最熟悉的剪切(Cut ...
- WPF 在事件中绑定命令(不可以在模版中绑定命令)
其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实现将命令绑定到事件中. 上一篇中我们介绍了MVVMLight中的命令的用法,那么仅仅知道命令是 ...
- WPF 在事件中绑定命令
导航:MVVMLight系列文章目录:<关于 MVVMLight 设计模式系列> 其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实 ...
- WPF使用RoutedCommand自己定义命令
主要代码例如以下所看到的: /// <summary> /// 声明并定义命令. /// </summary> RoutedCommand ClearCommand = new ...
- WPF入门(3)——命令
命令是ICommand类型的属性,binding到控件上,用于代替事件,个人认为事件也很好,命令只是轻度解耦前后端. 闲话少说,上代码,示例是ScreenToGif的源代码中的一个命令: public ...
- 【命令】kill命令
kill命令详解: <---用于向进程发送信号,以实现对进程的管理---> 语法格式:kill [-s signal|-SIGNAL] pid... kill -l [signal] ...
随机推荐
- angular学习笔记(九)-css类和样式1
本篇主要介绍通过数据绑定来给元素添加特定的类名,从而应用特定的样式 从一个最基本的例子来看: <!DOCTYPE html> <html ng-app> <head> ...
- Exception时信息的记录
系统总有出现异常的时候,那么出现异常时应该如何处理? 一直以来,我都以为这么处理就足够的: 在日志中打印Exception的堆栈信息,以便排查原因 反馈给用户系统xxx出现问题 package com ...
- 【图文教程】WebStorm下使用Github下载以及上传代码
1.从一个git路径下,下载代码到本地,选择VCS->Checkout from Version Control ->GitHub. 2.可能会弹出需要设置上传代码的密码,这 ...
- vue轮播图插件vue-awesome-swiper的使用与组件化
不管是APP还是移动端网页开发,轮播图在大部分项目当中都是存在的,这时候如果用vue开发项目,选择一款好的插件并且封装好是很重要的 1. 推荐使用vue-awesome-swiper 安装:cnpm ...
- 给singer的左侧添加fixedTitle,并显示向上滚动偏移效果;
1.将写好的dom绝对定位到顶部: 2.dom值为singerlist的currentIndex.title(通过计算属性获取),如果有则显示fixedTitle,没有则隐藏: 3.计算diff:当d ...
- 7款基于jquery的动画搜索框
无论是电商网站,还是媒体网,还是个人博客,每个网站都有属于自己个性化的搜索框.今天小编给大家带来7款基于jquery的动画搜索框.每个搜索框都采用了动画效果,一起看下效果图吧. 在线预览 源码下载 ...
- SpringMVC之学习(2)值得接收和传递
springmvc中 @Controller 来标识一个控制器 @RequestMapping来标识请求路径,可以写在类名上,也可以写在方法名上.写在类,表示所有的方法都在此路径下. package ...
- cocos2dx坐标系介绍
GL坐标系 Cocos2D以OpenglES为图形库,所以它使用OpenglES坐标系.GL坐标系原点在屏幕左下角.x轴向右.y轴向上. 屏幕坐标系 苹果的Quarze2D使用的是不同的坐标系统,原点 ...
- 【WPF】一组CheckBox的全选/全不选功能
需求:给一组CheckBox做一个全选/全不选的按钮. 思路:CheckBox不像RadioButton那样拥有GroupName属性来分组,于是我想的方法是将这组CheckBox放到一个布局容器中, ...
- valgrind: failed to start tool 'memcheck' for platform 'amd64-linux
valgrind运行错误 问题描述 valgrind运行时,无法找到相关工具文件,具体报错如下 valgrind: failed to start tool 'memcheck' for platfo ...