17

我项目的一部分是编写一个文本编辑器,用于输入一些规则、编译我的应用程序并运行它。编写编译器已结束并发布测试版。在最终版本中,我们必须在文本编辑器中添加撤消和重做。我使用一个文件并定期保存它以供文本编辑器使用。如何为我的文本编辑器设计撤消和重做?文件持久化的结构有什么变化?

4

8 回答 8

16

您可以将您的操作建模为命令,并将其保存在两个堆栈中。一个用于撤消,另一个用于重做。您可以组合命令来创建更多高级命令,例如当您想要撤消宏的操作时;或者,如果您想将单个单词或短语的单个击键组合在一个操作中。

编辑器中的每个操作(或重做操作)都会生成一个新的撤消命令,该命令会进入撤消堆栈(并且还会清除重做堆栈)。每个撤消操作都会生成进入重做堆栈的相应重做命令。

正如derekerdmann的评论中提到的,您还可以将撤消和重做命令组合成一种类型的命令,该命令知道如何撤消和重做其操作。

于 2010-08-27T12:27:33.320 回答
8

基本上有两种很好的方法来解决它:

  • “命令”设计模式

  • 在不可变对象上使用OO,其中一切都只是由不可变对象组成的不可变对象,由不可变对象组成(这不太常见,但如果正确完成,则非常优雅)

使用面向对象而不是不可变对象而不是天真的命令或天真的撤消/重做的优势在于,您无需考虑太多:无需“撤消”操作的效果,也无需“重播”所有命令。你所需要的只是一个指向一个巨大的不可变对象列表的指针。

因为对象是不可变的,所以所有“状态”都可以非常轻量级,因为您可以缓存/重用任何状态下的大多数对象。

“OO over immutable objects”是一颗纯粹的宝石。再过 10 年可能不会成为主流;)

PS:在不可变对象上做 OO 也惊人地简化了并发编程。

于 2010-08-27T12:42:03.683 回答
6

如果你不想要任何花哨的东西,你可以添加一个UndoManager。每次添加或删除文本时Document都会触发一次。UndoableEdit要撤消和重做每个更改,只需在 UndoManager 中调用这些方法。

这样做的缺点是 UndoManager 会在用户每次输入内容时添加一个新的编辑,因此键入“apple”将为您留下 5 个编辑,一次可撤消一个。对于我的文本编辑器,我为编辑编写了一个包装器,它存储除了文本更改和偏移之外的时间,以及UndoableEditListener如果它们之间只有很短的时间(0.5秒对我来说效果很好)。

这适用于一般编辑,但在完成大量替换时会导致问题。如果您有一个包含 5000 个“apple”实例的文档,并且您想用“orange”替换它,那么您最终会得到 5000 个编辑,所有这些都存储“apple”、“orange”和一个偏移量。为了减少使用的内存量,我将此视为普通编辑的一个单独案例,而是存储“apple”、“orange”和一个包含 5000 个偏移量的数组。我还没有开始应用它,但我知道当多个字符串匹配搜索条件(例如,不区分大小写的搜索、正则表达式搜索)时,它会引起一些麻烦。

于 2010-08-27T12:55:16.243 回答
4

哇,真是巧合——我在最后一小时在我的 WYSIWYG 文本编辑器中实现了撤消/重做:

基本思想是要么将文本编辑器的全部内容保存在一个数组中,要么将上次编辑的差异保存在数组中。

在重要的点更新这个数组,即每隔几个字符(检查每个按键的内容长度,如果超过 20 个字符不同,则创建一个保存点)。还可以更改样式(如果是富文本)、添加图像(如果允许的话)、粘贴文本等。您还需要一个指针(只是一个 int 变量)来指向数组中的哪个项目是当前状态编辑)

使数组具有设定的长度。每次添加保存点时,将其添加到数组的开头,并将所有其他数据点向下移动一个。(一旦你有这么多的保存点,数组中的最后一项就会被遗忘)

当用户按下撤消按钮时,检查编辑器当前的内容是否与最近一次保存的相同(如果不是,那么用户自上次保存点以来已经进行了更改,所以保存当前的内容编辑器(因此可以重做),使编辑器等于最后一个保存点,并使指针变量= 1(数组中的第二项)。如果它们相同,则自上次以来没有进行任何更改保存点,所以你需要撤消到之前的点。为此,增加指针值+1,并使编辑器的内容=指针的值。

要重做,只需将指针值减 1 并加载数组的内容(确保检查是否已到达数组末尾)。

如果用户在撤消后进行编辑,则将指向值数组单元格向上移动到单元格 0,并将其余部分向上移动相同的量(一旦他们进行了不同的编辑,您就不想重做其他内容)。

另一个主要的捕获点 - 确保仅在文本编辑器的内容实际发生更改时才添加保存点(否则您会得到重复的保存点,并且看起来撤消对用户没有任何作用。

我无法帮助您了解 Java 细节,但我很乐意回答您的任何其他问题,

尼科

于 2010-08-27T12:23:16.573 回答
4

你可以通过两种方式做到这一点:

  • 保留编辑器状态列表和列表中的指针;undo 将指针向后移动并恢复那里的状态,redo 向前移动,做某事会丢弃指针之外的所有内容并将状态作为新的顶部元素插入;
  • 不要保留状态,而是保留动作,这要求对于每个动作,您都有一个反动作来撤消该动作的效果

在我的(图表)编辑器中,有四个级别的状态变化:

  • 动作片段:这些是更大动作的一部分,不能单独撤消或重做(例如移动鼠标)
  • 动作:一个或多个动作片段,形成有意义的更改,可以撤消或重做,但不会反映在已编辑的文档中作为磁盘上的更改(例如选择元素)
  • 文档更改:一项或多项更改已编辑文档的操作,就像将其保存到磁盘一样(例如更改、添加或删除元素)
  • 文档保存:文档的当前状态显式保存到磁盘 - 此时我的编辑器会丢弃撤消历史记录,因此您无法撤消保存后
于 2010-08-27T12:17:38.317 回答
3

这是命令模式的工作。

于 2010-08-27T12:23:09.197 回答
3

这是一个片段,展示了 SWT 如何支持撤消/重做操作。将其作为实际示例(或直接使用,如果您的编辑器基于 SWT):

SWT 撤消 重做

于 2010-08-27T12:50:49.373 回答
2

阅读《设计模式:可重用的面向对象软件的元素》一书。据我记得,有一个很好的例子。

于 2010-08-27T12:21:16.157 回答