C# Can';I don’’我不想实现撤销/重做功能,我应该使用堆栈吗?

C# Can';I don’’我不想实现撤销/重做功能,我应该使用堆栈吗?,c#,winforms,stack,undo,redo,C#,Winforms,Stack,Undo,Redo,我现在有点困惑,我想是有那么一天吧 我需要为表单实现撤销和重做功能。为了简单起见,假设我只保存修改过的控件及其离开焦点时的值 我如何保存这些信息,以便在“时间线”中来回移动 我想用一个堆栈,但当我测试我的小演示时,我有一个轻微的动脉瘤,我在这里 代码是必需的,不是真的,但会有所帮助。我对需要实现的算法更感兴趣。有什么建议吗?如果你将一个“更改”推到堆栈上,然后在撤消时从堆栈中弹出一个“更改”,那么堆栈就是完美的。然后将弹出的更改推送到另一个表示重做的堆栈中。在将来的某个时候,希望在保存时,您可以

我现在有点困惑,我想是有那么一天吧

我需要为表单实现撤销和重做功能。为了简单起见,假设我只保存修改过的控件及其离开焦点时的值

我如何保存这些信息,以便在“时间线”中来回移动

我想用一个堆栈,但当我测试我的小演示时,我有一个轻微的动脉瘤,我在这里

代码是必需的,不是真的,但会有所帮助。我对需要实现的算法更感兴趣。有什么建议吗?

如果你将一个“更改”推到堆栈上,然后在撤消时从堆栈中弹出一个“更改”,那么堆栈就是完美的。然后将弹出的更改推送到另一个表示重做的堆栈中。在将来的某个时候,希望在保存时,您可以清除这两个堆栈

事实上,这并不是那么简单,因为您需要记录更改的类型,了解旧值和新值等。因此,当您从撤消堆栈弹出时,弹出的内容必须描述先前的值是什么以及它设置为什么控件

相反,对于重做堆栈,它需要了解新值是什么以及它去了哪里。但是,是的,两个堆栈的想法是自制撤销重做的一个良好开端

基于业务对象的撤销的一个很好的例子是CSLA.NET,它具有
UndoableBase


但是,这会记录对象状态的快照,因此它比基于表单的概念更高级。但是,CSLA.NET提供了完整的数据绑定支持,因此从
UndoableBase
继承的数据绑定对象自然会在UI中支持撤销(而不是重做)。

是的,您可以使用堆栈。有两种方法可以做到这一点;阅读以下参考资料:


每个都有其优点/缺点。

我会使用IUndoableAction界面。这些实现可以存储任何需要执行和撤消的数据。然后是的,我会用一堆来装它们

interface IUndoableAction
{
    void Do();
    void Undo();
}
Stack<IUndoableAction> Actions;
至于在action类中存储什么,有些action可以只存储旧的值。然而,有一次我在电子表格中交换了两行。我没有在两行中存储每个单元格的值——我只是存储了行索引,以便它们可以交换回来。如果您存储了每个操作的所有状态,那么很容易填满大量内存


然后您还需要一个重做堆栈,当您撤消一个操作时,它会被推送到重做堆栈上。在执行新操作时,需要清除重做堆栈,以便不会出现问题

最简单的方法可能是使用undo/redo堆栈组合

另一种方法是使用一个数组或操作列表,只增加/减少指向数组中索引的指针。当一个操作撤消时,索引向后移动1,当该操作重新执行时,索引向前移动1。 这里的优点是,您不需要每个动作都有一个先弹出后推的序列

需要考虑的事项:

  • 如果多次撤消,然后执行某个操作,则所有 必须消除重做操作
  • 在尝试执行撤消/重做之前,请确保检查边界并确保有可撤消/重做的操作

@Sergio Yes当您进行更改时,您将其推送到撤消堆栈,当人们撤消操作时,您从撤消堆栈弹出并推送到重做堆栈。如果他们执行除重做以外的任何操作,您将清除重做堆栈,因为您可能会得到无效状态。@Adam,我不喜欢两个堆栈的想法。当用户在撤销后进行“新更改”时,您不认为这会有问题吗。我认为此时重做列表将被清除。因此,我个人会用一个列表和一个pointer@musefan我认为这一点已成为个人品味的问题。堆栈是一种非常有效的容器选择,您不需要了解它们的位置。列表也是一个不错的选择,但是你可以通过了解你当前在列表中的位置来获得上下文。我用两种方法都做过,我更喜欢使用两个堆栈。当用户执行新操作并且需要清除重做堆栈时,
RedoStack.clear()
while(UndoList.Count>undointer)UndoList.RemoveAt(UndoList.Count-1)更简单、可读性更强、更明显地正确。它还可以方便地启用和禁用撤消和重做按钮——CanUndo就像
UndoStack一样简单。Any()
,CanRedo就是
RedoStack。Any()
@Joe,好的方面——我会尽量记住这一切,因为我认为使用Memento模式更适合我的用例(UI值更改等等)。另外,我喜欢这部电影,在编写代码时会感觉很好。出于各种原因,我真的更喜欢UI的命令模式(例如,更少的内存,更好地将UI与数据模型分离,以及将多个值更改合并到一个步骤中的能力……还可以减少UI和数据之间的耦合……),但这可能只是个人偏好。是的,这是一部很棒的电影!:)Memento很容易实现,因为您只需复制数据模型,而不需要创建命令对象。但是,如果数据集很大(内存限制),它并不总是一个选项。另一方面,如果执行每个命令的时间很长,那么memento可能是最佳选择。在您的应用程序中,我猜这并不重要,除非您正在制作一个图像编辑器。
    void PerformAction(IUndoableActionaction)
    {
        Actions.Push(action);
        action.Do();
    }

    void Undo()
    {
        var action = Actions.Pop();
        action.Undo();
    }