java 如何在文本编辑器中设计撤消和重做?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3583901/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to design undo & redo in text editor?
提问by Sajad Bahmani
Part of my project is to write a text editor that is used for typing some rules, compiling my application and running it. Writing compiler was end and release beta version. In the final version we must add undo and redo to the text editor. I use a file and save it periodically for the text editor. How to design undo and redo to my text editor? What is changed in the structure of persistent of file?
我的项目的一部分是编写一个文本编辑器,用于输入一些规则、编译我的应用程序并运行它。编写编译器已结束并发布测试版。在最终版本中,我们必须在文本编辑器中添加撤消和重做。我使用一个文件并定期为文本编辑器保存它。如何设计对我的文本编辑器的撤消和重做?文件持久化的结构发生了什么变化?
回答by Jord?o
You can model your actions as commands, that you keep in two stacks. One for undo, another for redo. You can composeyour commands to create more high-level commands, like when you want to undo the actions of a macro, for example; or if you want to group individual keystrokes of a single word, or phrase, in one action.
您可以将您的操作建模为命令,并保存在两个堆栈中。一个用于撤消,另一个用于重做。您可以编写命令以创建更高级的命令,例如,当您想要撤消宏的操作时;或者,如果您想在一个操作中对单个单词或短语的各个击键进行分组。
Each action in your editor (or a redo action) generates a new undo command that goes into the undo stack (and also clears the redo stack). Each undo action generates the corresponding redo command that goes into the redo stack.
编辑器中的每个操作(或重做操作)都会生成一个新的撤消命令,该命令进入撤消堆栈(并清除重做堆栈)。每个撤消操作都会生成进入重做堆栈的相应重做命令。
You can also, as mentioned in the comments by derekerdmann, combine both undo and redo commands into one type of command, that knows how to undo and redo its action.
您还可以如derekerdmann的评论中所述,将撤消和重做命令组合成一种命令,该命令知道如何撤消和重做其操作。
回答by NoozNooz42
There are basically two good ways to go about it:
基本上有两种好方法:
the "Command" design pattern
using onlyOO over immutable objects, where everything is just immutable objects made of immutable objects made themselves of immutable objects (this is less common but wonderfully elegant when done correctly)
“命令”设计模式
仅在不可变对象上使用OO,其中一切都只是由不可变对象组成的不可变对象,由不可变对象组成(这不太常见,但如果正确完成,则非常优雅)
The advantage of using OO over immutable objects over the naive command or the naive undo/redo is that you don't need to think much about it: no need to "undo" the effect of an action and no need to "replay" all the commands. All you need is a pointer to a huge list of immutable objects.
使用面向对象而不是不可变对象而不是朴素的命令或朴素的撤消/重做的优势在于,您无需考虑太多:无需“撤消”动作的效果,也无需“重播”所有命令。你所需要的只是一个指向大量不可变对象的指针。
Because objects are immutable all the "states" can be incredibly lightweight because you can cache/reuse most objects in any state.
因为对象是不可变的,所以所有“状态”都可以非常轻量级,因为您可以在任何状态下缓存/重用大多数对象。
"OO over immutable objects" is a pure jewel. Probably not gonna become mainstream before another 10 years that said ; )
“面向不可变对象的面向对象”是一颗纯粹的宝石。可能不会再过 10 年就成为主流了;)
P.S: doing OO over immutable objects also amazingly simplifies concurrent programming.
PS:对不可变对象执行 OO 还惊人地简化了并发编程。
回答by lins314159
If you don't want anything fancy, you can just add an UndoManager. Your Documentwill fire an UndoableEditevery time you add or remove text. To undo and redo each change, simply call those methods in UndoManager.
如果你不想要任何花哨的东西,你可以添加一个UndoManager。每次添加或删除文本时,您Document都会触发一个UndoableEdit。要撤消和重做每个更改,只需在 UndoManager 中调用这些方法。
The downside of this is UndoManager adds a new edit each time the user types something in, so typing "apple" will leave you with 5 edits, undoable one at a time. For my text editor, I wrote a wrapper for edits that stores the time it was made in addition to text change and offset, as well as an UndoableEditListenerthat concatenates new edits to previous ones if there is only a short period of time between them (0.5 seconds works well for me).
这样做的缺点是每次用户输入内容时,UndoManager 都会添加一个新编辑,因此键入“apple”将为您留下 5 个编辑,一次可撤消一个。对于我的文本编辑器,我为编辑编写了一个包装器,除了文本更改和偏移外,还存储了它的时间,UndoableEditListener如果它们之间只有很短的时间(0.5秒对我来说效果很好)。
This works well for general editting, but causes problems when a massive replace is done. If you had a document with 5000 instances of "apple" and you wanted to replace this with "orange", you'd end up with 5000 edits all storing "apple", "orange" and an offset. To lower the amount of memory used, I've treated this as a separate case to ordinary edits and am instead storing "apple", "orange" and an array of 5000 offsets. I haven't gotten around to applying this yet, but I know that it'll cause some headaches when multiple strings match the search condition (eg. case insensitive search, regex search).
这适用于一般编辑,但在完成大量替换时会导致问题。如果您有一个包含 5000 个“apple”实例的文档,并且您想用“orange”替换它,那么您最终会得到 5000 次编辑,所有这些都存储“apple”、“orange”和一个偏移量。为了减少使用的内存量,我将其视为普通编辑的一个单独案例,而是存储“apple”、“orange”和 5000 个偏移量的数组。我还没有开始应用它,但我知道当多个字符串匹配搜索条件(例如不区分大小写的搜索,正则表达式搜索)时,它会引起一些麻烦。
回答by reinierpost
You can do it in two ways:
你可以通过两种方式做到这一点:
- keep a list of editor states and a pointer in the list; undo moves the pointer back and restores the state there, redo moves forward instead, doing something throws away everything beyond the pointer and inserts the state as the new top element;
- do not keep states, but actions, which requires that for every action you have a counteraction to undo the effects of that action
- 在列表中保留一个编辑器状态列表和一个指针;undo 将指针移回并恢复那里的状态,而 redo 则向前移动,执行某些操作会丢弃指针之外的所有内容并将状态作为新的顶部元素插入;
- 不保留状态,而是保留动作,这要求您对每个动作都有一个反作用来撤消该动作的影响
In my (diagram) editor, there are four levels of state changes:
在我的(图表)编辑器中,有四个级别的状态更改:
- action fragments: these are part of a larger action and not separately undoable or redoable (e.g. moving the mouse)
- actions: one or more action fragments that form a meaningful change which can be undone or redone, but which are not reflected in the edited document as changed on disk (e.g. selecting elements)
- document changes: one or more actions that change the edited document as it would be saved to disk (e.g. changing, adding or deleting elements)
- document saves: the present state of the document is explicitly saved to disk - at this point my editor throws away the undo history, so you can't undo past a save
- 动作片段:这些是更大动作的一部分,不能单独撤消或重做(例如移动鼠标)
- 动作:一个或多个动作片段,形成一个有意义的变化,可以撤消或重做,但不会在编辑的文档中反映为磁盘上的变化(例如选择元素)
- 文档更改:更改已编辑文档的一项或多项操作,因为它将保存到磁盘(例如更改、添加或删除元素)
- 文档保存:文档的当前状态明确保存到磁盘 - 此时我的编辑器丢弃了撤消历史记录,因此您无法撤消过去的保存
回答by Nico Burns
Wow, what a conicidence - I have literally in the last hour implemented undo/redo in my WYSIWYG text editor:
哇,真是太巧了——我在最后一个小时里在我的 WYSIWYG 文本编辑器中实现了撤消/重做:
The basic idea is to either save the entire contents of the text editor in an array, or the difference between the last edit.
基本思想是要么将文本编辑器的全部内容保存在一个数组中,要么将上次编辑的差异保存在一个数组中。
Update this array at significant points, i.e. every few character (check the length of the content each keypress, if its more than say 20 characters different then make a save point). Also at changes in styling (if rich text), adding images (if it allows this), pasting text, etc. You also need a pointer(just an int variable) to point at which item in the array is the current state of the editor)
在重要点更新此数组,即每隔几个字符(检查每个按键的内容长度,如果超过 20 个字符不同,则进行保存点)。同样在样式更改(如果是富文本)、添加图像(如果允许的话)、粘贴文本等。您还需要一个指针(只是一个 int 变量)来指向数组中的哪个项目是当前状态编辑)
Make the array have a set length. Each time you add a save point, add it to the start of the array, and move all of the other data points down by one. (the last item in the array will be forgotten once you have so many save points)
使数组具有设定的长度。每次添加保存点时,将其添加到数组的开头,并将所有其他数据点向下移动一个。(保存点太多后,数组中的最后一项会被遗忘)
When the user presses the undo button, check to see if the current contents of the editor are the same as the latest save (if they are not, then the user has made changes since the last save point, so save the current contents of the editor (so it can be redo-ed), make the editor equal to the last save point, and make the pointer variable = 1 (2nd item in array ). If they are they same, then no changes have been made since the last save point, so you need to undo to the point before that. To do this, increment the pointer value + 1, and make the contents of the editor = the value of pointer.
当用户按下撤消按钮时,检查当前编辑器的内容是否与最近保存的相同(如果不同,则用户自上次保存点以来进行了更改,因此保存当前编辑器的内容)编辑器(因此可以重做),使编辑器等于最后一个保存点,并使指针变量 = 1(数组中的第二项)。如果它们相同,则自上次以来没有进行任何更改保存点,所以你需要撤消到之前的点。为此,增加指针值+1,并使编辑器的内容=指针的值。
To redo simply decrease the pointer value by 1 and load the contents of the array (make sure to check if you have reached the end of the array).
要重做,只需将指针值减 1 并加载数组的内容(确保检查是否已到达数组的末尾)。
If the user makes edits after undoing, then move the pointed value array cell up to cell 0, and move the rest up by the same amount (you dont want to redo to other stuff once they've made different edits).
如果用户在撤消后进行编辑,则将指向值数组单元格向上移动到单元格 0,并将其余部分向上移动相同的量(一旦他们进行了不同的编辑,您就不想重做其他内容)。
One other major catch point - make sure you only add a save point if the contents of the text editor have actually changed (otherwise you get duplicate save points and it will seem like undo is not doing anything to the user.
另一个主要关注点 - 确保仅在文本编辑器的内容实际发生更改时才添加保存点(否则您会得到重复的保存点,并且撤消似乎对用户没有任何作用。
I can't help you with java specifics, but I'm happy to answer any other questions you have,
我无法为您提供 Java 细节方面的帮助,但我很乐意回答您的任何其他问题,
Nico
妮可
回答by DanDan
This is a job for the command pattern.
这是命令模式的工作。
回答by Andreas Dolk
Here is a snippet that shows how SWT supports Undo/Redo operations. Take it as practical example (or use it directly, if your editor is based on SWT):
这是一个片段,显示了 SWT 如何支持撤消/重做操作。把它作为实际例子(或者直接使用它,如果你的编辑器是基于 SWT 的):
回答by Vladislav Rastrusny
Read a book Design Patterns: Elements of Reusable Object-Oriented Software. As far as I remember, there is a pretty good example.
阅读一本书Design Patterns: Elements of Reusable Object-Oriented Software。据我所知,有一个很好的例子。

