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. 关于easyii 无法退出登录的情况

    问题描述:easyii 后台原先自己就写好了退出登录,如下图所示.点击了退出登录后,页面也会自动跳转到登录的页面.但是问题是,在浏览器点击返回的时候,还是依旧能进入到后台中,退出登录根本就没有起到作用 ...

  2. LightningChart解决方案:XY和3D图表(Polymer Char GPC-IR&#174;-工程案例)

    LightningChart解决方案:XY和3D图表(Polymer Char GPC-IR-工程案例) 所在行业:石化公司成立时间:1992年LightningChart解决方案:XY和3D图表 P ...

  3. C# 锁与死锁

    什么是死锁: 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进. 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再 ...

  4. HashMap的循环姿势你真的掌握了吗?

    hashMap 应该是java程序员工作中用的比较多的一个键值对处理的数据的类型了.这种数据类型一般都会有增删查的方法,今天我们就来看看它的循环方法以前写过一篇关于ArrayList的循环效率问题&l ...

  5. MM-采购模块相关业务

    采购模块主要业务流程: 1.收集采购需求(采购申请单),系统采购申请单单据可以由需求部门手工产生,也可以由系统的MRP(物料需求计划)来产生. 2,货源确定,用来确定所申请的物料,通过何种方式向供应商 ...

  6. Deep Neural Networks for YouTube Recommendations YouTube的经典推荐框架

    https://zhuanlan.zhihu.com/p/52169807 王喆大佬的讲解

  7. 页面中嵌套iframe,微信浏览器长按二维码识别不了

    问题:在微信浏览器内,页面中嵌套iframe,iframe中用户触发事件后有个弹框会显示二维码,用户长按二维码可以识别并跳转.尝试了一下,安卓是正常的,但是ios是识别不了的. 解决过程: 1.这里客 ...

  8. 使用sqoop将mysql数据导入到hive中

    首先准备工具环境:hadoop2.7+mysql5.7+sqoop1.4+hive3.1 准备一张数据库表: 接下来就可以操作了... 一.将MySQL数据导入到hdfs 首先我测试将zhaopin表 ...

  9. pixi.js 图像资源(svg)转纹理

    当Pixi使用WebGL去调用GPU渲染图像时,需要先将图像转化为GPU可以处理的版本.而能够被GPU处理的图像就叫做纹理,在pixi中使用纹理缓存来存储和引用所有纹理.通过将纹理分配给精灵,再将精灵 ...

  10. 多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

    重难点梳理 知识点梳理 学习目标 1.能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义) 2.能够掌握线程常见API的使用 3.能够理解什么是线程安全问题 4.能够知道什么是锁 5 ...