首页 > 代码库 > C#使用命令模式实现撤销和恢复功能
C#使用命令模式实现撤销和恢复功能
第一次写关于设计模式的随笔,最近在使用C#做一个WinForm的项目,其中要求需要支持撤销和恢复功能,想到了以前看过Command模式支持撤销和恢复操作,就在项目中使用了。对命令模式理解的不够深,各位看客请指正。
Gof23种设计模式中的Command模式,其意图是这么描述的“将一个请求封装为一个对象,从而是你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作”;另外个人的理解就是可以将调用者和接受者解耦出来。下图为Comand的类图:
。首先理解将调用者和接受者解耦出来:Invoker中只需要持有一个Command接口的引用即可,但是具体是其哪一个子类,Invoker并不知道。而真正执行动作的接受者,Invoker并不用知道,这样就讲调用者和接收者解耦出来。另外理解实现撤销和恢复操作。程序需要将所有操作过的命令对象存储起来,存储的每一个命令对象保存了其当前状态和上一个状态,因此可用来做撤销和恢复操作。下面用代码进行说明,本文使用C#语言实现的。
首先,我们定义一个Command的接口。定义如下:
interface Command{ void execute(); void undo(); }
其中仅仅声明了两个函数,其如果在c++的实现的话,使用纯虚类。
我们要实现的具体功能为,可以为一个TextBox控件实先撤销和恢复功能。因此,我们实先一个TextChangedCommand的类,定义如下:
1 public class TextChangedCommand: Command 2 { 3 private TextBox ctrl; 4 private String newStr; 5 private String oldStr; 6 7 public TextChangedCommand(TextBox ctrl, String newStr, String oldStr) 8 { 9 this.ctrl = ctrl;10 this.newStr = newStr;11 this.oldStr = oldStr;12 }13 14 public void execute()15 {16 this.ctrl.Text = newStr;17 this.ctrl.SelectionStart = this.ctrl.Text.Length;18 }19 20 public void undo()21 {22 this.ctrl.Text = oldStr;23 this.ctrl.SelectionStart = this.ctrl.Text.Length;24 }25 }
其中有三个成员变量,分别为TextBox类型的ctrl,和String类型的newStr、oldStr。这里的ctrl就可以理解为类图中的Invoker,而newStr和oldStr即为当前状态和上一个状态。
由于我们需要实现其撤销和恢复功能,因此这里创建两个栈对象,分别用来存储已经执行了的命令对象和撤销后的命令对象。即在撤销动作中,从栈中弹出一个Command对象,然后执行对象的undo操作,执行完成后将其存储到恢复的栈中,在点击恢复时,用从恢复栈弹出一个Command对象,执行其execute动作。两个栈的定义如下:
Stack<Command> undoStack = new Stack<Command>();Stack<Command> redoStack = new Stack<Command>();
下面实现类图中的Client部分,其主要功能是创建一个具体的Command对象,同时为为其设置接收者。对于一个TextBox来说,在其Text熟悉变化的时候,将其上一个状态和当前状态保存下来。具体试下如下:
private void textBox1_TextChanged(object sender, EventArgs e) {
if(flag){ TextChangedCommand com = new TextChangedCommand((TextBox)textBox1, ((TextBox)textBox1).Text, oldStr); undoStack.Push(com); oldStr = ((TextBox)textBox1).Text;
} }
这个函数是控件textBox1的Text属性变化时的回调函数。在其中创建了Command基类的实力,并将其存到栈对象undoStack中。下面来做恢复和撤销操作的代码:
撤销操作:
private void button1_Click(object sender, EventArgs e) //撤销{ if (undoStack.Count == 0) return;
flag = false;
Command com = undoStack.Pop(); com.undo(); redoStack.Push(com);}
恢复操作:
private void button2_Click(object sender, EventArgs e) //恢复 { if (redoStack.Count == 0) return;
flag = false;
Command com = redoStack.Pop(); com.execute(); undoStack.Push(com); }
我将两个操作分别放到两个button的操作里面,一个用于撤销,另外一个用于恢复。由于我们在进行撤销和恢复时,在TextChangedCommand中是对TextBox控件进行赋值,因此也会触发textBox1控件的Text属性变化的回调函数,我这这里使用了一个标志flag,如果是在撤销和恢复的动作中,则不做Command对象的压栈动作。到这里,已经完全可以实现了TextBox的撤销和恢复功能,当然在我自己的项目中,出了TextBox控件为,还有ComboBox、DateTimePicker、CheckBox控件都实现了撤销和恢复功能,但是ComboBox控件的回调函数需要使用SelectIndexChanged作为回调,不要使用SelectTextChanged,因为当Index变化时也会触发Text属性的变化。
这里简单介绍一下在撤销恢复时,不触发textBox1的Text的属性回调函数的另外一种方法。我在这里将其称之为原子操作。可以将textBox1的回到函数textBox1_TextChanged作为一个参数,传入到Command对象中。在对TextBox的Text属性变化前先做这个动作:ctrl.TextChanged -=动作,赋值完成后,再做Ctrl.TextChanged +=动作。下面用一个函数简单模拟一下:
void setTextBox(TextBox ctrl, String str, EventHandler eventhandler)
{
ctrl.TextChanged -= eventhandler;
ctrl.Text = str;
ctrl.TextChanged += eventhandler;
}
附上源码:http://files.cnblogs.com/files/youyipin/test_Charp.zip
C#使用命令模式实现撤销和恢复功能