在软件工程中,撤销/重做功能是用户界面中常见的一个功能,它允许用户撤销他们之前的操作,并在需要时重新执行这些操作。实现这一功能的传统方法是使用命令模式(Command pattern)或备忘录模式(Memento pattern)。然而,这些模式可能需要对现有应用程序进行较大的修改。本文介绍的框架提供了一种简单的方法来实现撤销/重做功能,而不需要对应用程序进行大的改动。
最近,一个朋友需要在现有的应用程序中实现撤销/重做功能。如果采用命令模式或备忘录模式,那么就需要对应用程序进行重大的修改。本文提供的解决方案展示了一种简单的方法来引入撤销/重做功能,可以在属性、操作或多个操作的粒度级别上实现。
要使用这个库,关键是在相关的操作点引入撤销行为。以一个非常简单的例子为例,如果对象有一个属性如下:
public int Age
{
get
{
return _age;
}
set
{
_age = value;
NotifyPropertyChanged("Age");
}
}
如果想在这个属性级别实现撤销行为(而不是在UI级别),那么应该添加如下代码:
UndoRedoManager.Instance().Push(a => this.Age = a, _age, "Change age");
_age = value;
NotifyPropertyChanged("Age");
这行代码可以分解如下:
因此,当调用撤销操作后设置Age属性时,上述指定的lambda表达式将被调用,有效地将age重置为原始值。基本上,在需要撤销的地方创建撤销记录。没有必要使用lambda表达式 - 可以将撤销方法创建为非匿名方法。
UndoRedoManager除了上述描述的Push操作外,还提供了以下操作和事件:
重做操作由UndoRedoManager类自动处理。用户只需要将撤销操作推入UndoRedoManager栈。
为了测试UndoRedoManager类,下载并修改了Code project上的DrawTools代码,并为提供的代码添加了撤销-重做功能。下面是一个示例,展示了如何为DrawLine.cs类的Move操作添加撤销功能:
public override void Move(int deltaX, int deltaY)
{
UndoRedoManager.Instance().Push((dummy) => Move(-deltaX, -deltaY), this);
startPoint.X += deltaX;
startPoint.Y += deltaY;
endPoint.X += deltaX;
endPoint.Y += deltaY;
Invalidate();
}
另一个示例:
public override void Normalize()
{
UndoRedoManager.Instance().Push(r => DrawRectangle.GetNormalizedRectangle(r), rectangle);
rectangle = DrawRectangle.GetNormalizedRectangle(rectangle);
}
想象一下,在两个不同的调用中设置了人的Name和Age,如下所示:
Person p = new Person();
p.Name = "new name";
p.Age = p.Age + 1;
假设Name和Age的setter都创建了撤销记录,上述代码将在撤销栈中生成两个撤销记录。如果想将这些合并为一个撤销记录,那么可以将其包围在一个事务中:
using (new UndoTransaction("optional description"))
{
p.Name = "new name";
p.Age = p.Age + 1;
}
这将导致这两个撤销记录被视为一个撤销记录。另一个例子是在撤销操作可能调用另一组可撤销操作的情况下,例如:
private Person AddPerson(Person person)
{
// Do not add if the person is already in the list
Person personInList = _personList.Find(p => p.ID == person.ID);
if (personInList != null)
{
return personInList;
}
UndoRedoManager.Instance().Push(p => RemovePerson(p), person, "Add Person");
personListBindingSource.Position = personListBindingSource.Add(person);
return person;
}
private void btnAddTran_Click(object sender, EventArgs e)
{
using (new UndoTransaction("Add Person"))
{
Person p = new Person() {};
AddPerson(p);
p.Name = "";
p.Age = 0;
}
}