我现在有点困惑,我猜有这样的日子。
我需要为表单实现撤消和重做功能。为简单起见,假设我只保存了被修改的控件以及它离开 Focus 时的值。
如何以让我在“时间线”中来回切换的方式保存这些信息。
我考虑过使用 Stack,但在测试我的小演示时,我有一个轻微的动脉瘤,我就在这里。
需要代码,不是真的,但会有所帮助。我对需要实现的算法更感兴趣。有什么建议么?
我会使用 IUndoableAction 接口。这些实现可以存储他们需要完成和撤消的任何数据。那么是的,我会用一个堆栈来保存它们。
interface IUndoableAction
{
void Do();
void Undo();
}
Stack<IUndoableAction> Actions;
每种动作都会实现 Do 和 Undo 方法。
然后,某处会有这两种方法:
void PerformAction(IUndoableActionaction)
{
Actions.Push(action);
action.Do();
}
void Undo()
{
var action = Actions.Pop();
action.Undo();
}
至于在动作类中存储什么,一些动作可以只存储旧值。但是,一旦我有一个操作来交换电子表格中的两行。我没有在两行中存储每个单元格的值——我只是存储了行索引,以便可以将它们交换回来。如果您为每个操作存储所有状态,则可能很容易填满大量内存。
然后你也想要一个重做堆栈,当你撤消一个动作时,它会被压入重做堆栈。执行新操作时需要清除重做堆栈,这样事情就不会乱了套。
如果您将“更改”推送到堆栈上,并且撤消时从中弹出“更改”,则堆栈是完美的。然后,您将弹出的更改推送到另一个表示重做的堆栈中。在未来的某个时候,希望在保存时,您清除两个堆栈。
其实并不是这么简单,因为你需要记录变化的类型,了解新旧值等等。所以当你从撤销堆栈中弹出时,你弹出的东西必须描述之前的值是什么以及控制它被设置为。
对于重做堆栈,它需要了解新值是什么以及它去了哪里。但是,是的,两个堆栈的想法是自制撤消重做的良好开端。
基于业务对象的撤销的一个很好的例子是 CSLA.NET,它有UndoableBase
:
http://www.lhotka.net/cslanet/
http://www.koders.com/csharp/fidCF6AB2CF035B830FF6E40AA22C8AE7B135BE1FC0.aspx?s=serializationinfo
但是,这会记录对象状态的快照,因此它会比基于表单的概念更先进。但是,CSLA.NET 提供完整的数据绑定支持,因此继承自的数据绑定对象UndoableBase
自然会支持 UI 中的撤消(而不是重做)。
是的,你会使用堆栈。有几种方法可以做到这一点;阅读这些参考资料:
http://en.wikipedia.org/wiki/Command_pattern
http://en.wikipedia.org/wiki/Memento_pattern
每个都有其优点/缺点。
可能最直接的方法是拥有撤消/重做堆栈组合。
另一种方法是拥有一个数组或动作列表,只需递增/递减指向数组中索引的指针。当一个动作被撤销时,索引向后移动一个,当一个动作被重做时,索引向前移动一个。此处的优点是您不需要为每个操作执行弹出然后推送序列。
需要考虑的事项: