简易撤销/重做功能实现

在软件工程中,撤销/重做功能是用户界面中常见的一个功能,它允许用户撤销他们之前的操作,并在需要时重新执行这些操作。实现这一功能的传统方法是使用命令模式(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");

这行代码可以分解如下:

  • UndoRedoManager.Instance() - 获取UndoRedoManager单例对象的实例。
  • Push - 将撤销记录推入撤销栈,数据包括:
  • a => Age = a - 要调用以执行撤销的方法。在这种情况下,声明了一个就地lambda表达式,调用Age的set属性访问器。
  • _age - 要传递给方法的数据。在这种情况下,这个成员变量包含在更改之前age的当前值。
  • "Change age" - 撤销操作的描述(可选)。

因此,当调用撤销操作后设置Age属性时,上述指定的lambda表达式将被调用,有效地将age重置为原始值。基本上,在需要撤销的地方创建撤销记录。没有必要使用lambda表达式 - 可以将撤销方法创建为非匿名方法。

UndoRedoManager操作

UndoRedoManager除了上述描述的Push操作外,还提供了以下操作和事件:

  • Undo() - 调用此方法执行撤销操作。在调用此方法之前,应检查撤销栈中是否有撤销操作。
  • Redo() - 用于执行重做操作。在调用此方法之前,应检查重做栈中是否有重做操作。
  • HasUndoOperations / HasRedoOperations - 这些可以被调用以确定撤销栈中是否有任何撤销/重做记录。
  • MaxItems - 设置/获取撤销栈中要存储的项目的最大数量。
  • UndoStackStatusChanged / RedoStackStatusChanged - 当撤销栈中添加/移除项目时触发这些事件。例如,这些事件可以用来设置撤销/重做菜单项的状态。

重做操作由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; } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485