一般的文档软件,图形设计工具,都会有Redo/Undo (即重做/撤消)功能,可是如何也在自己的应用程序当中实现这样的功能,而且是没有操作次数地Redo/Undo?
此时“软件设计模式”就显得很重要。
这里实现的Redo/Undo主要应用的“命令模式”与“备忘录模式”。
《C#设计模式》一书里讲到只用备忘录模式来实现,我看了之后觉得那种方法太烦琐了,我们可以利用
C#2.0的新特性来使之简化。
首先,如果要让我们的每一步操作都是可撤消、重做的,那么我们必须要将这些操作涉及的代码“封装
”到一个方法里去,以供以后调用。而且有撤消、重做,那么必须有两部分代码:
1.正向操作的代码 DoOperation
2.反向(撤消)的代码 UndoOperation.
然后,要实现无次数限制的撤消、重做,我们应该要用两个栈(Stack)来保存操作。
下面的UML 类图:

下面是实现代码:
1.ICommand.cs
public interface ICommand

{

ActionHandler DoOperation
{ get;set;}

ActionHandler UndoOperation
{ get;set;}

void Do();
void Undo();
}
2. 操作委托:
public delegate void ActionHandler();
3. Command.cs
public class Command:ICommand

{
private ActionHandler doOperation;

private ActionHandler undoOperation;


ICommand Members#region ICommand Members

public ActionHandler DoOperation

{
get

{
return doOperation;
}
set

{
doOperation = value;
}
}

public ActionHandler UndoOperation

{
get

{
return undoOperation;
}
set

{
undoOperation = value;
}
}

public void Do()

{
doOperation();
}

public void Undo()

{
undoOperation();
}

#endregion
}
4. CommandManager.cs
public static class CommandManager

{
private static Stack<ICommand> redoMementos;

private static Stack<ICommand> undoMementos;

static CommandManager()

{
redoMementos = new Stack<ICommand>();
undoMementos = new Stack<ICommand>();
}


/**//// <summary>
/// 先清空Redo栈,将Command对象压入栈
/// </summary>
/// <param name="command">Command对象</param>
public static void AddNewCommand(ICommand command)

{
redoMementos.Clear();
undoMementos.Push(command);
}


/**//// <summary>
/// 从Redo栈中弹出一个Command对象,执行Do操作,并且将该Command对象压入Undo栈
/// </summary>
public static void Redo()

{
try

{
if (redoMementos.Count > 0)

{
ICommand command = redoMementos.Pop();
if (command != null)

{
command.Do();
undoMementos.Push(command);
}
}
}
catch (InvalidOperationException invalidOperationException)

{
Console.WriteLine(invalidOperationException.Message);
}
}


/**//// <summary>
/// 从Undo栈中弹出一个Command对象,执行Undo操作,并且将该Command对象压入Redo栈
/// </summary>
public static void Undo()

{
try

{
if (undoMementos.Count > 0)

{
ICommand command = undoMementos.Pop();
if (command != null)

{
command.Undo();
redoMementos.Push(command);
}
}
}
catch (InvalidOperationException invalidOperationException)

{
Console.WriteLine(invalidOperationException.Message);
}
}


/**//// <summary>
/// 清空Redo栈与Undo栈
/// </summary>
public static void ClearAll()

{
ClearRedoStack();
ClearUndoStack();
}


/**//// <summary>
/// 清空Redo栈
/// </summary>
public static void ClearRedoStack()

{
redoMementos.Clear();
}


/**//// <summary>
/// 清空Undo栈
/// </summary>
public static void ClearUndoStack()

{
undoMementos.Clear();
}


/**//// <summary>
/// 当前在Redo栈中能重做的步骤数
/// </summary>
public static int RedoStepsCount

{
get

{
return redoMementos.Count;
}
}


/**//// <summary>
/// 当前在Undo栈中能撤消的步骤数
/// </summary>
public static int UndoStepsCount

{
get

{
return undoMementos.Count;
}
}
}
5. Redo/Undo测试UI程序(Form), 在Form上添加三个Button控件:btnNew, btnRedo, btnUndo, 逻辑代
码如下:
public partial class Form1 : Form

{
private int x=10;
private int y=10;
public Form1()

{
InitializeComponent();
}

private void btnNew_Click(object sender, EventArgs e)

{
btnRedo.Enabled = false;
Command command = new Command();
Label label = new Label();
Random r = new Random();
label.Text = ((char)r.Next(65, 99)).ToString();
label.Font = new Font("Arial", 16,FontStyle.Bold);
label.AutoSize = true;
label.Location = new Point(x, y);

/**//*
* 使用匿名委托,更加简单,而且匿名委托方法里还可以使用外部变量。
*/
command.DoOperation = delegate()

{
//Do或者Redo操作会执行到的代码
x += label.Width + 10;
y += label.Height + 10;
this.Controls.Add(label);
};
command.UndoOperation = delegate()

{
//Undo操作会执行到的代码
x -= label.Width + 10;
y -= label.Height + 10;
this.Controls.Remove(label);
};
command.Do();//执行DoOperation相应代码
CommandManager.AddNewCommand(command);
}

private void btnRedo_Click(object sender, EventArgs e)

{
btnUndo.Enabled = true;
CommandManager.Redo();
if (CommandManager.RedoStepsCount == 0)

{
btnRedo.Enabled = false;
}
}

private void btnUndo_Click(object sender, EventArgs e)

{
btnRedo.Enabled = true;
CommandManager.Undo();
if (CommandManager.UndoStepsCount == 0)

{
btnUndo.Enabled = false;
}
}
}
这是我第一次在Blog上写关于设计模式的心得体会,难免有写得不对之处,希望与大家多交流,多指正不当之处。。。。
posted on 2007-02-13 23:02
Stanley.Luo 阅读(3556)
评论(23) 编辑 收藏 所属分类:
设计模式
评论
发布这篇东东,估计都弄到我的脑细胞快要造反了:一点发布就报告IE的XXX插件没有经过验证,查了一下是超级兔子的事,把卸了,反而弹出一个什么提示也没有的警告,晕了,还是把超级兔子装回来,终于好了。看来这个兔子还有“脾气”。。。。
Redo/Undo一般都带有数据的操作吧
执行一个Command之后怎么创造一个能够UndoCommand是个问题,我感觉设计好这部分是个挑战。
文中也没有提到这方面。
对,假如我进行一系列的文件创建、修改、删除操作,这样可以记住作了什么操作,但如果不记住操作作了什么,还是Undo不了,也Redo不了啊
codeporject上有很多这样的demo,SD里就有这个理念。
这篇文章还太浅显,核心的内容没有谈到. 只谈了如何在container进行移动和删除操作.
但是对于每个 Command来说,实际是会关心到data的操作的, 假如是一个CAD软件,类似如下:
class Shape
{
......
}
class Circle : public Shape
{
}
class CAddObjectCommand
{
Shape *m_pObj;
UnDo();
ReDo();
}
这个命令可能比较简单, undo的时候从db删除掉对应的object,redo就再增加进去.
但是实际做的时候是比这复杂多的, 设计到数据库里面object的id,以及modify的时候也包含modify前的object以及modify后的object, 如果涉及到关联关系,也就是一个object关联到另外一个object,情况会更复杂.
并且,还应该有 comand 集合,一次性执行多个command
我不知道怎么undo我在数据库删除/更改了的记录。能告诉我吗?
我觉得Undo/Redo核心不在设计模式,在于数据结构吧。。。。?
@sunriseyuen
数据库删除/更改了的记录
俺有兴趣,哪个XD搞过?