需要帮助为文本编辑器程序编写撤消/重做功能
Posted
技术标签:
【中文标题】需要帮助为文本编辑器程序编写撤消/重做功能【英文标题】:Need Help writing an undo/redo function for text editor program 【发布时间】:2010-10-05 02:11:33 【问题描述】:我用 C++ 编写了一个文本编辑器程序,它具有简单的命令:LEFT、RIGHT、HOME、END、BACKSPACE、DELETE、INSERT,现在我需要执行 UNDO 和 REDO 功能。在我的程序中,用户必须能够撤消不超过最后十个命令。我想使用矢量实现来实现这一点,但我不知道如何设置它。我不确定如何将光标位置和字符存储到向量中。有人可以提供帮助吗?
#ifndef CURSOR_H
#define CURSOR_H
#include <stdlib.h>
#include <iostream>
template <class Object>
class Cursor;
// Incomplete Declaration
template <class Object>
class CNode
public:
CNode( const Object & theElement = Object( ), CNode * n = NULL ) : element( theElement ), next( n )
Object element;
CNode *next;
friend class Cursor<Object>;
;
template <class Object>
class Cursor
public:
Cursor( );
bool isEmpty( ) const;
void makeEmpty( );
void left ( );
void right ( );
void del ( ); //This is the delete operation. I named it del instead of delete as delete conflicts with a C++ keyword.
void back ( );
void insert( const Object & x );
void home ( );
void end ( );
void undo ( );
private:
void printText ( ) ;
CNode<Object> *header;
CNode<Object> *cursorPosition;
;
//#include "Cursor.cpp"
#endif
【问题讨论】:
【参考方案1】:您想使用deque
,以便您可以从前面或后面添加和删除;添加命令时,将其添加到后面,撤消时从后面删除它,当您达到 11 个命令时,从前面删除一个。
【讨论】:
环形缓冲区将允许相同的操作,无需任何分配/解除分配。 @Ben,非常正确。但是环形缓冲区不是标准的一部分,我发现了坚持使用标准容器的愿望。也许我错了。 Boost提供了一个:boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/… “检测到坚持使用标准容器的愿望”:CNode 是相反的有力证据,尽管提到了 vector。【参考方案2】:查看Memento Design Pattern 和GOF
它是针对这个非常具体的要求而存在的。您可能必须将它与其他设计模式(例如命令、迭代器、FlyWeight 等)结合使用
纪念品意图
在不违反封装的情况下, 捕捉和外化一个对象的 内部状态,以便对象可以 稍后恢复到这个状态。
命令意图
封装一个请求 作为一个对象,从而让你 参数化不同的客户端 请求、队列或日志请求,以及 支持可撤消的操作。
【讨论】:
【参考方案3】:需要考虑的其他事项:
一般来说,您不希望对光标移动应用撤消/重做(即它们不会影响十个命令的限制)。在撤消/重做文本的删除或插入时,当然您必须在执行操作之前将光标放在正确的位置。如果用户在没有执行任何光标移动或更正(退格)的情况下键入多个字符,则通常在应用撤消/重做时将这些字符视为一个单元。
【讨论】:
+1:花这么多时间/空间解释许多相同的问题让我觉得很傻...... 他们需要在这个网站上使用人工智能来识别其他人何时输入与您相同的答案并建议共同作者:)【参考方案4】:恭喜您加入了撤消/重做。在任何类型的编辑器中,它都是一个很棒的功能。它仍然会变得棘手。这里有一些想法给你(所有的挥手,没有代码)。
我建议了解Command Design Pattern。您要做的是设计一个“命令”类,该类的一个实例可以“执行”单个命令(例如插入字母“A”),以及“撤消”本身。
当用户调用某个命令(比如添加字母'A')时,你'new'一个命令,定义它的“Do”来插入'A',还定义它的“Undo”来删除A,然后添加它到撤消列表的顶部,然后“执行”它。
不要将您的撤消限制为仅 10 次。为什么不让它无限?
无论您使用何种结构来制作可撤消命令列表,通常的行为是,如果您已撤消到某个级别,然后在该点开始编辑,则应丢弃当前级别之上的所有重做.
【讨论】:
【参考方案5】:对于您希望能够撤消的每个操作(即可能是插入、退格和删除,但不是光标移动),我们可以列出“撤消”过程:
insert -> 将光标定位在该字符上并发出 del del -> 将光标定位在下一个字符上并发出插入指令 退格 -> 将光标定位在下一个字符上并发出插入指令不幸的是,您的游标使用指针,并且当您撤消删除/退格时,新分配的 CNode 可能与之前的地址不同,这可能会使先前尝试使用该指针地址的撤消步骤无效。选项包括:
一些额外的数据结构来跟踪哪些指针以这种方式无效,并在重新创建相应的元素时用新值重新填充它们(痛苦)
找到一种更确定的方法来在 CNode 列表中找到正确的索引
文档中的绝对索引(但可能难以计算且移动速度较慢) 将光标移动保存在撤消历史记录中 将 10 个实质性编辑(删除、退格、插入)与任意数量的散布光标移动混合需要一个动态大小的容器,而且您会携带相当多的“冗长包袱” 您可以将光标移动的动态列表挂在每个实质性编辑的固定大小列表中的每个元素上(不过也好不了多少)(实际上,您的 Cnode 列表没有出现双链接,所以我看不出如何在不通过标题元素中的“文档”进行非常痛苦的重复的情况下向左移动...?)
在解决了这个索引/光标移动问题后,您必须做出以下决定:
每次操作后,使用一个双端队列来保存撤消信息:
结构历史 指示要撤消的操作(例如 enum Op left, right, insert, del ... ) 仅用于插入操作:对象值
然后有一些on-undo处理函数来读取这些历史记录并协调它们描述的操作,或者
执行操作时,将 函数对象 推送到对撤销和重做操作进行编码的双端队列(根据对 Cursor 对象方法的调用),以便实际执行撤消或重做操作仅涉及执行该对象(即撤消/重做操作是在编辑时打包的“黑匣子”)/这更加优雅和灵活,但对于初级/中级程序员可能不太熟悉,因此可能更难做对。 boost库对此有很好的支持功能。
【讨论】:
【参考方案6】:我同意其他人关于在执行“do”命令时捕获可撤消命令的观点。
我还建议定期检查列表并结合撤消命令。
例如,如果您的撤消命令是:
删除 A、删除 B、删除 C、左光标、左光标、右光标、左光标。
将其转换为仅,
删除“ABC”,左光标(2)。
这样,当用户执行撤消操作时,他们不会看到每一次击键。相反,撤消发生在逻辑组中。
【讨论】:
以上是关于需要帮助为文本编辑器程序编写撤消/重做功能的主要内容,如果未能解决你的问题,请参考以下文章