Qt undo/redo 框架

  • 基于Command设计模式
  • 支持命令压缩和命令合成
  • 提供了与工具包其他部分融合很好的widgets和actions

术语(Terminology)

  • Command - 对文档的一个作用行为,比如

    • 图像编辑器的模糊操作
    • 文本处理器的剪切操作
      • 采样编辑器的最大化操作
  • Undo-stack - commands的堆栈
  • Document - 被应用程序编辑的内部数据,比如
    • 音频编辑器中的waveform(波形)
    • 图像编辑器中的bitmap(位图)

基本的undo stack操作

  • Push

  • Undo

  • Redo

注意,push可能会删掉一些操作,如图

类们

只有四个类!

  • QtUndoCommand - 用于修改document的对象的基类
  • QtUndoStack - QtUndoCommand对象的堆栈
  • QtUndoGroup - undo堆栈的组。很多应用程序允许用户同时打开超过一个文档,该类允许你把一组undo堆栈按一单个stack对待。
  • QtUndoView - 继承自QListWidget,用来展示undo堆栈的内容,以字符串形式

实例

前提说明:下面这个例子,我们将为一个文本编辑器实现undo/redo;文档我们就用一个简单的QString来代表;我们先实现文档中插入字符这样一个command

commands的实现

插入字符操作

class InsertChars : public QUndoCommand
{
public:
InsertChars(int index, const QString &chars, QString *document)
: QUndoCommand("Insert characters") {
m_index = index;
m_chars = chars;
m_document = document;
} virtual void redo() {
m_document->insert(m_index, m_chars);
} virtual void undo() {
m_document->remove(m_index, m_chars.length());
} private:
int m_index;
QString m_chars;
QString *m_document;
};

删除字符操作

class RemoveChars : public QUndoCommand
{
public:
RemoveChars(int index, int count, QString *document)
: QUndoCommand("Remove characters") {
m_index = index;
m_count = cout;
m_document = document;
} virtual void redo() {
m_removedChars = m_document->mid(m_index, m_count);
m_document->remove(m_index, m_count);
} virtual void undo() {
m_document->insert(m_index, m_removedChars);
} private:
int m_index, m_count;
QString m_removedChars;
QString *m_document;
};

在文本编辑器中使用

MyEditor::MyEditor(QWidget *parent) : QWidget(parent) {
// …
m_document = new QString;
m_stack = new QUndoStack(this);
m_toolBar->addAction(m_stack->createUndoAction);
m_toolBar->addAction(m_stack->createRedoAction);
// …
} void MyEditor::keyPressEvent(QKeyEvent *event) {
QString chars = events->text();
int index = cursorIndex(); switch (event->key()) {
case Qt::Key_Backspace:
if (index > 0)
m_stack->push(new RemoveChars(index-1, 1, m_document));
break;
case Qt::Key_Delete:
if (index < m_document.length())
m_stack->push(new RemoveChars(index, 1, m_document));
break;
default:
if (!chars.isEmpty())
m_stack->push(new InsertChars(index, chars, m_document));
break;
}
}

command的压缩(compression)

命令压缩,是一种把若干个commands压成一个command的行为。 典型的案例就是文本编辑器中输入一大堆文字,撤销,把这一大堆都撤销了。

主要用到了QUndoCommand的id()和mergeWith()方法。代码如下

static const int InsertCharsId = 1000;
static const int RemoveCharsId = 1001;
//... int InsertChars::id() const {
return InsertCharsId;
} bool InsertChars::mergeWith(const QCommand *command) {
// 该类型转换是安全的,因为stack检查过id()了
InsertChars *other = static_cast<InsertChars* > (command); // 只有当其他插入的字符在我的字符后面时,才merge
if (m_index + m_chars.length() != other->m_index)
return false; // 把它merge了
m_chars.append(other->m_chars);
return true;
}

command的合成(composition)

也就是传说中的宏(macros)

通过合并一系列简单的commands,从而创建复杂的commands

主要是用到了QUndoStack的beginMacro()和endMacro()方法。代码如下

void MyEditor::replace(const QString &oldChars, const QString &newChars) {
if(!m_document->contains(oldChars))
return;
QString title = QString("Replace '%1' with '%2'").arg(oldChars).arg(newChars); m_stack->beginMacro(title);
int index = 0; for(;;) {
index = m_document->indexOf(oldChars, index);
if(index == -1)
break;
m_stack->push(new RemoveChars(index,oldChars.length(), m_document));
m_stack->push(new InsertChars(index, newChars, m_document)); index += newChars.length();
}
m_stack->endMacro();
}

高级command合成

你大部分的需要,beginMacro()和endMacro()都能充分满足。

每个command可以有很多子commands

通过添加子command,构成一个复杂的command

自定义的command合成有很大益处,你可以在push到stack之前,逐步构建command

合成命令的undo顺序如下

合成命令的redo顺序如下

QUndoCommand* MyEditor::createReplaceCommand(const QString &oldChars, const QString &newChars) {
QUndoCommand *replaceCommand = new QUndoCommand(QString("Replace '%1' with '%2'").arg(oldChars).arg(newChars)); int offset = 0;
int index = 0; for(;;) {
index = m_document->indexOf(oldChars, index);
if (index == -1)
break;
new RemoveChars(index + offset, oldChars.count(), m_document, replaceCommand);
new InsertChars(index + offset, newChars, m_document, replaceCommand);
index += newChars.cout();
offset += newChars.count() - oldChars.cout();
}
return replaceCommand;
}

QUndoGroup

一个应用程序,一般有若干个打开的文档,每个都拥有他们自己的undo stack。

这些undo stack们可以放到一个undo group里

该组group里的stack可以使用QUndoStack的setActive ()方法将自己设置为active stack。

在同一时间,只能有一个stack是active的。

void MyEditor::MyEditor(QWidget *parent) : QWidget(parent)
{
//...
m_undoGroup = new QUndoGroup(this);
m_toolBar->addAction(m_undoGroup->createUndoAction(this));
m_toolBar->addAction(m_undoGroup->createRedoAction(this));
//...
} Document *MyEditor::createDocument() {
Document *doc = new Document(this);
m_documents.append(doc);
m_undoGroup->addStack(doc->undoStack());
return doc;
} bool Document::event(QEvent *event) {
if( event->type() == QEvent::WindowActivate)
m_undoStack->setActive(true);
// ..
return QWidget::event(event);
}

Tips

  • 按照commands来设计实现你的应用程序功能---后期很难增加undo/redo
  • Undo commands不应该储存指向document中实际对象的指针---储存其拷贝或者储存足够必要的用于重创建新对象的信息
  • 如果你非得想让commands里储存指向document中对象的指针时,你必须做到如下:
    • 当这些对象在document中被删除的时候,获得对象的多有权
    • 当该command实例被销毁时,delete掉你拥有的那个对象
  • 如果你十分渴望能改变或者移除stack里已经被push的command的话,你很可能会犯以下错误:
    • 你尝试在不只一个文档的情况下使用一个undo堆栈来代表。(原文You are trying to use one undo stack for something that needs to be represented as more than one document)
    • 你的command不是atomic(应该就是说该命令是有若干命令合成或压缩的,不是最最基本的命令)
  • 当命令修改了文档,立马更新该文档的state,使用QUndoStack的indexChanged()信号
    • 该更新信号不应该从command里发射。

摘自:https://www.cnblogs.com/muyr/p/3621385.html

Qt Undo Framework的更多相关文章

  1. Qt Undo Framework Demo

    Qt Undo Framework Demo eryar@163.com Abstract. Qt’s Undo Framework is an implementation of the Comma ...

  2. Qt's Undo Framework

    Overview of Qt's Undo Framework Introduction Qt's Undo Framework is an implementation of the Command ...

  3. Qt Installer Framework 使用说明(三)

    目录 6.Qt Installer Framework 示例 7.参考 Reference 配置文件 Configuration File 配置文件元素的简要说明 Summary of Configu ...

  4. Qt Installer Framework翻译(7-4)

    组件脚本 对于每个组件,您可以指定一个脚本,来准备要由安装程序执行的操作.脚本格式必须与QJSEngine兼容. 构造 脚本必须包含安装程序在加载脚本时创建的Component对象. 因此,脚本必须至 ...

  5. Qt Installer Framework翻译(7-6)

    工具 Qt Installer Framework包含以下工具: > installerbase > binarycreator > repogen > archivegen ...

  6. 使用Qt installer framework制作安装包

    一.介绍 使用Qt库开发的应用程序,一般有两种发布方式:(1)静态编译发布.这种方式使得程序在编译的时候会将Qt核心库全部编译到一个可执行文件中.其优势是简单单一,所有的依赖库都集中在一起,其缺点也很 ...

  7. Qt Installer Framework的学习

    Qt Installer Framework是Qt默认包的发布框架.它很方便,使用静态编译Qt制作而成.并且使用了压缩率很高的7z对组件进行压缩.之所以有这些好处,我才觉得值得花一点儿精力研究一下这个 ...

  8. qt: qt install framework使用问题;

    qt提供了qt install framework用于程序打包,方便.快捷,并且可以对界面和功能进行自定义. 但是, 如果使用默认的打包配置,不进行安装页面功能自定义的话, 在修改安装路径时,在对程序 ...

  9. Qt Installer Framework 使用说明(二)

    目录 4.教程: 创建一个安装程序 创建软件包目录 创建配置文件 创建程序包信息文件 指定组件信息 指定安装程序版本 添加许可证 选择默认内容 创建安装程序内容 创建安装程序二进制文件 5.创建安装程 ...

随机推荐

  1. JDBC中SQL语句与变量的拼接

    变量为 keyWords String sql = "select id from t_user order by id + '"+keyWords+"';";

  2. vulstudy靶机搭建(kali)

    安装 安装docker apt-get install docker.io //# 安装docker pip install docker-compose //# 安装docker-compose g ...

  3. 记录一下网上找到的别人写的angualr总结

    感觉写的不错,所以讲链接保存下来 https://www.jianshu.com/p/f0f81a63cbcb

  4. html2canvas实现浏览器截图的原理(包含源码分析的通用方法)

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design Ng组件库:ng-devui(欢 ...

  5. 闲鱼上哪些商品抢手?Python 分析后告诉你

    1目 标 场 景 经常看到有朋友在闲鱼卖些小东西又或是自己擅长的一些技能,都能为他们带来不错的 睡后收入. 闲鱼上大量的商品,很难精准判断哪些受欢迎,哪些好卖:一个个录入数据去做数据分析,浪费时间的同 ...

  6. 顶会两篇论文连发,华为云医疗AI低调中崭露头角

    摘要:2020年国际医学图像计算和计算机辅助干预会议(MICCAI 2020),论文接收结果已经公布.华为云医疗AI团队和华中科技大学合作的2篇研究成果入选. 同时两篇研究成果被行业顶会收录,华为云医 ...

  7. Code-Review-Maven编译(第三方jar包引用)

    Code-Review-SpringBoot-Maven编译(第三方jar包引用) 在使用maven编译项目时,有时候咱们可能会使用一些第三方的jar包依赖库,比如第三方支付类的接入,大多出于安全考虑 ...

  8. Windows10搭建本地ftp服务器

    首先要有一个win10专业版的操作系统,连接上互联网... 一.首先打开控制面板选择程序,再选择"启用或关闭windows功能" 二.在"Windows功能"中 ...

  9. 表单绑定v-model

    表单控件在实际开发中是极为常见的.特别是对于用户信息的提交,需要大量的表单.Vue中使用v-model指令来实现表单元素和数据的双向绑定.此篇记录下其原理,以及当时学习时候的一些小尝试以及修饰符. v ...

  10. Spring Cloud Gateway应用篇(十三)

    一.概述 在微服务架构中,每个服务都是一个可以独立开发和运行的组件,而一个完整的微服务架构由一系列独立运行的微服务组成.其中每个服务都只会完成特定领域的功能,比如订单服务提供与订单业务场景有关的功能. ...