Qt Undo Framework
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的更多相关文章
- Qt Undo Framework Demo
Qt Undo Framework Demo eryar@163.com Abstract. Qt’s Undo Framework is an implementation of the Comma ...
- Qt's Undo Framework
Overview of Qt's Undo Framework Introduction Qt's Undo Framework is an implementation of the Command ...
- Qt Installer Framework 使用说明(三)
目录 6.Qt Installer Framework 示例 7.参考 Reference 配置文件 Configuration File 配置文件元素的简要说明 Summary of Configu ...
- Qt Installer Framework翻译(7-4)
组件脚本 对于每个组件,您可以指定一个脚本,来准备要由安装程序执行的操作.脚本格式必须与QJSEngine兼容. 构造 脚本必须包含安装程序在加载脚本时创建的Component对象. 因此,脚本必须至 ...
- Qt Installer Framework翻译(7-6)
工具 Qt Installer Framework包含以下工具: > installerbase > binarycreator > repogen > archivegen ...
- 使用Qt installer framework制作安装包
一.介绍 使用Qt库开发的应用程序,一般有两种发布方式:(1)静态编译发布.这种方式使得程序在编译的时候会将Qt核心库全部编译到一个可执行文件中.其优势是简单单一,所有的依赖库都集中在一起,其缺点也很 ...
- Qt Installer Framework的学习
Qt Installer Framework是Qt默认包的发布框架.它很方便,使用静态编译Qt制作而成.并且使用了压缩率很高的7z对组件进行压缩.之所以有这些好处,我才觉得值得花一点儿精力研究一下这个 ...
- qt: qt install framework使用问题;
qt提供了qt install framework用于程序打包,方便.快捷,并且可以对界面和功能进行自定义. 但是, 如果使用默认的打包配置,不进行安装页面功能自定义的话, 在修改安装路径时,在对程序 ...
- Qt Installer Framework 使用说明(二)
目录 4.教程: 创建一个安装程序 创建软件包目录 创建配置文件 创建程序包信息文件 指定组件信息 指定安装程序版本 添加许可证 选择默认内容 创建安装程序内容 创建安装程序二进制文件 5.创建安装程 ...
随机推荐
- 关于easyii 无法退出登录的情况
问题描述:easyii 后台原先自己就写好了退出登录,如下图所示.点击了退出登录后,页面也会自动跳转到登录的页面.但是问题是,在浏览器点击返回的时候,还是依旧能进入到后台中,退出登录根本就没有起到作用 ...
- LightningChart解决方案:XY和3D图表(Polymer Char GPC-IR®-工程案例)
LightningChart解决方案:XY和3D图表(Polymer Char GPC-IR-工程案例) 所在行业:石化公司成立时间:1992年LightningChart解决方案:XY和3D图表 P ...
- C# 锁与死锁
什么是死锁: 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进. 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再 ...
- HashMap的循环姿势你真的掌握了吗?
hashMap 应该是java程序员工作中用的比较多的一个键值对处理的数据的类型了.这种数据类型一般都会有增删查的方法,今天我们就来看看它的循环方法以前写过一篇关于ArrayList的循环效率问题&l ...
- MM-采购模块相关业务
采购模块主要业务流程: 1.收集采购需求(采购申请单),系统采购申请单单据可以由需求部门手工产生,也可以由系统的MRP(物料需求计划)来产生. 2,货源确定,用来确定所申请的物料,通过何种方式向供应商 ...
- Deep Neural Networks for YouTube Recommendations YouTube的经典推荐框架
https://zhuanlan.zhihu.com/p/52169807 王喆大佬的讲解
- 页面中嵌套iframe,微信浏览器长按二维码识别不了
问题:在微信浏览器内,页面中嵌套iframe,iframe中用户触发事件后有个弹框会显示二维码,用户长按二维码可以识别并跳转.尝试了一下,安卓是正常的,但是ios是识别不了的. 解决过程: 1.这里客 ...
- 使用sqoop将mysql数据导入到hive中
首先准备工具环境:hadoop2.7+mysql5.7+sqoop1.4+hive3.1 准备一张数据库表: 接下来就可以操作了... 一.将MySQL数据导入到hdfs 首先我测试将zhaopin表 ...
- pixi.js 图像资源(svg)转纹理
当Pixi使用WebGL去调用GPU渲染图像时,需要先将图像转化为GPU可以处理的版本.而能够被GPU处理的图像就叫做纹理,在pixi中使用纹理缓存来存储和引用所有纹理.通过将纹理分配给精灵,再将精灵 ...
- 多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?
重难点梳理 知识点梳理 学习目标 1.能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义) 2.能够掌握线程常见API的使用 3.能够理解什么是线程安全问题 4.能够知道什么是锁 5 ...