Qt实现undo和redo功能--连续后退

news2024/11/28 2:46:54

刚开始想做这个的时候,我专门去找了Qt官方的测试例子,运行起来点了点,代码翻了翻。然后照猫画虎般的写了个测试例子。

不明白,为什么每个例子旁边会有个命令的显示列表,还巨丑的那种,这如果要放在别的程序里面,岂不是很掉面。写完了那个测试例子的时候,我还是不明白它究竟是怎么运行的,为什么ctrl+z就能后退到上一步的操作,ctrl+y就能前进呢?

我看懂了官方例子中的为什么会有个命令栈,但委实没看懂他的那些命令派生。

后面多琢磨了几次,对着例子一遍一遍的撸了下代码,突然明白了,这不就是在某个需要触发的时刻记录下当前的状态和这一状态前面的那个状态,然后ctrl+z的时候设置为当前状态的前一的状态,ctrl+y的时候设置为当前状态的后一状态。

看明白了之后,你就会感觉原来他的那些命令派生的类对你没啥卵用,因为你还是要实现适合你自己状态的命令。

以一个简单的例子:

有一个绘图的需求,但是需要实现每次绘图之后可以通过快捷键快速绘图的功能。

解法相当简单,就是每一次在鼠标按下、移动、弹起的时候,将鼠标弹起之后的这一刻的图像和鼠标按下前一刻的图像通过某种方式保存起来,在需要回退的时候,直接替换成上一状态的图像即可。

这种需求,其实用两个有序的列表就可以实现,一个放当前的图像,一个放上一刻的图像,游标永远指向最后一张图像,这两个有序列表的游标其实是一样的。只不过Qt已经实现了 QUndoStack 用来存储。

The QUndoStack class is a stack of QUndoCommand objects.

先来看一下实现的效果:
在这里插入图片描述

首先定义一个命令的类,我这边将其定义成了一个单例,主要是因为虽然我有多个绘图的界面,但整个过程中只需要有一个回退功能就能满足,因为我在定义命令的时候多加了一个区分某个绘图控件的字段。

class CommandStack : public QObject
{
	Q_OBJECT
public:
	static CommandStack& instance()
	{
		static CommandStack instance;
		return instance;
	}
    void push(const QImage& imgL, const QImage& imgC, const QVariant& data);

	void keyEvent(QKeyEvent* event) override;

private:
	CommandStack();
	~CommandStack();

private:
	QUndoStack* m_undoStack{ Q_NULLPTR };
	QAction* m_undoAction{ Q_NULLPTR };
	QAction* m_redoAction{ Q_NULLPTR };
	QUndoView *m_undoView{ Q_NULLPTR };
};

m_undoStack QUndoStack对象指针,用来存储命令的栈;
m_undoActionm_redoAction 是undo和redo action 指针
m_undoView QUndoView类的指针,主要用来在调试的时候显示每步操作的命令,当程序需要运行的时候,需要将其关闭或者不进行创建。

CommandStack::CommandStack()
{
	m_undoStack = new QUndoStack(this);
	m_undoAction = m_undoStack->createUndoAction(this, tr(""));
	m_undoAction->setShortcuts(QKeySequence::Undo);

	m_redoAction = m_undoStack->createRedoAction(this, tr(""));
	m_redoAction->setShortcuts(QKeySequence::Redo);

#if TEST_WITH_STACK_VIEW

	m_undoView = new QUndoView(m_undoStack);
	m_undoView->setWindowTitle(tr("Command List"));
	m_undoView->show();
	m_undoView->setAttribute(Qt::WA_QuitOnClose, false);

#endif
}

上面的构造函数中,首先我们创建了一个 QUndoStack 对象,紧接着在该对象创建了redo和undo的action。并且用一个宏来控制是否需要创建 QUndoView 对象,该对象主要用来辅助测试。

Qt 帮助文档上说:

New commands are pushed on the stack using push().

所以我们申明一个push方法, Command 类是我们自定义的命令类,后面详解。

void CommandStack::push(const QImage& imgL, const QImage& imgC, const QVariant& data)
{
    m_undoStack->push(new Command(imgL, imgC, data));
}

为什么要重写 keyEvent 呢?这是因为我的绘制图像的控件使用了QWidget,并且在这个Widget中已经重写了keyEvent方法,导致我们的按键事件不会响应,所以我们需要手动去调用一下。

void CommandStack::keyEvent(QKeyEvent* event)
{
	if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Z)
	{
		m_undoAction->trigger();
	}
	if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Y)
	{
		m_redoAction->trigger();
	}
}

ctr+Z 组合键触发undo功能,ctrl+Y 组合键触发redo功能。调用 trigger方法之后,会自动调用我们自定义命令中的 undo 或者 redo方法。

构建完了 CommandStack,我们需要定义一个命令的类,这个类需要继QUndoCommand,然后通过重写 undo()redo() 函数来实现自己的命令操作。

class Command : public QUndoCommand
{
public:

    Command(const QImage& imgL, const QImage& imgC, const QVariant& data, QUndoCommand *parent = 0);
	~Command();

	void undo() override;
	void redo() override;

private:
	QImage m_imgLast{ QImage() };
	QImage m_imgCurr{ QImage() };
    QVariant m_data{ QVariant() };
};

因为要做的是对一个保存一个绘图过程中的状态,所以,这个命令类中我定义了三个成员变量。

m_imgLast 用来保存这一次鼠标pressed之前的图片,在调用undo的时候进行图片设置

m_imgCurr 用来保存这一次鼠标release之后的图片,在调用redo 的时候进行图片设置

m_data 在这个命令中我使用存储了一个 ImagePainterQWidget指针,表示这一次的图像具体是在哪一个Widget上进行替换,当然这个对象的类型可以直接声明为 void* 类型。

QVariant 储存 指针类型数据,通过下面的方式进行转换。

auto data = QVariant::fromValue(static_cast<void*>(ptr));
auto ptr = static_cast<CXXX*>(m_data.value<void*>());

自定义命令类的undo和redo方法其实非常简单,无非就是对指定的Wdiget设置当前状态或者上一状态的图像。

void Command::undo()
{
    static_cast<CXXX*>(m_data.value<void*>())->updateImage(m_imgLast);
}

void Command::redo()
{
    static_cast<CXXX*>(m_data.value<void*>())->updateImage( m_imgCurr);
}

至此,一个简单的 undo 和 redo功能已经完成了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/537815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DJ6-4 文件存储空间的管理

目录 6.4.1 空闲表 1、存储空间的分配与回收 2、空闲表法的优缺点 6.4.2 空闲链表 1、空闲盘块链 2、空闲盘区链 6.4.3 位示图 1、位示图的表示 2、存储空间的分配 3、存储空间的回收 4、位示图法的优缺点 6.4.4 成组链接 1、空闲盘块的组织 plus 个人理解图…

前端web入门-HTML-day01

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 HTML初体验 HTML 定义 标签语法 总结&#xff1a; HTML 基本骨架 基础知识&#xff1a; 总结&#…

20年磨一剑,数慧时空推出智能遥感云平台DIEY及自然资源多模态大模型“长城”

5月17日&#xff0c;主题为“时空智能 从感知到决策”的第十二届全球地理信息开发者大会&#xff08;WGDC2023&#xff09;在北京昆泰酒店举行。大会聚集了千余位产业专家、行业用户、创新企业等业界精英&#xff0c;共话时空智能时代下的技术发展与应用创新。中国科学院院士、…

初始计算机操作系统——进程与线程,多线程以及Thread类的创建和属性

目录 通过前半篇文章需要了解 1.进程&#xff08;process/task&#xff09;&#xff1a;运行起来的可执行文件。 为啥要有进程&#xff1f; 如何解决这个问题&#xff1f; &#xff08;1&#xff09;进程池&#xff1a; &#xff08;2&#xff09;使用线程&#xff1a; 为啥线…

国考省考行测:判断推理,类比推理1,概念关系,包含关系,交叉关系,并列关系,全同关系

国考省考行测&#xff1a;判断推理&#xff0c;类比推理1&#xff0c;概念关系&#xff0c;包含关系&#xff0c;交叉关系&#xff0c;并列关系&#xff0c;全同关系 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#x…

chatgpt赋能Python-libreoffice_python扩展

LibreOffice Python扩展: 提升办公效率的利器 如果你一直在寻找一种提高办公效率的方法&#xff0c;那么你肯定会喜欢LibreOffice Python扩展。作为LibreOffice的一个特性&#xff0c;它可以让你使用Python编写宏程序自动化你的日常办公任务。 什么是LibreOffice Python扩展&…

力扣sql中等篇练习(二十一)

力扣sql中等篇练习(二十一) 1 最大数量高于平均水平的订单 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # Write your MySQL query statement below WITH t1 as (SELECT order_id,avg(quantity) AquantityFROM OrdersDeta…

chatgpt赋能Python-numpy数据预处理

Numpy数据预处理综述 介绍 Numpy是Python中最流行的数学库之一&#xff0c;可以用于高效的处理大型数据。Numpy提供了各种强大的数据结构和函数&#xff0c;使得数据分析和处理变得更加容易和直观。本文将介绍numpy中的一些数据预处理技术&#xff0c;包括数据清洗、缩放、归…

chatgpt赋能Python-mingw编译python

Mingw编译Python&#xff1a;一种常用的解决方案 在Python开发中&#xff0c;为了获得更好的性能&#xff0c;我们通常会选择编译Python源代码。而在Windows平台上&#xff0c; Mingw编译器是一种常用的解决方案。本文将介绍Mingw编译Python的过程&#xff0c;并探讨其优缺点。…

chatgpt赋能Python-numpy创建

Numpy&#xff1a;Python中的数学计算利器 作为Python中进行数学计算和科学计算最重要的库之一&#xff0c;Numpy已经成为了Python编程中的标配。Numpy以其出色的数组处理能力和矩阵运算效果&#xff0c;让Python用户的数学计算和科学计算变得更加简单高效。在本篇文章中&…

【半监督学习】Match系列.4

介绍几篇关于半监督学习的论文&#xff1a;CLS&#xff08;arXiv2022&#xff09;&#xff0c;Ada-CM&#xff08;CVPR2022&#xff09;&#xff0c;SemiMatch&#xff08;CVPR2022&#xff09;. CLS: Cross Labeling Supervision for Semi-Supervised Learning, arXiv2022 解…

mysql增量备份

目录 一、修改配置文件&#xff0c;开启增量备份功能 &#xff08;1&#xff09;查看是否已经开启了 &#xff08;2&#xff09;修改配置文件开启 &#xff08;3&#xff09;增量记录文件 二、还原增量备份 &#xff08;1&#xff09;修改了数据 &#xff08;2&#xff…

使用thrift进行RPC通信(附c程序示例)

前言 为了实现不同语言的程序跨进程、跨主机通信&#xff0c;一般可以采用mq或rpc框架来实现。 对于异步通知的场景可以使用mq&#xff0c;如zeroMQ。 但对于某些实时性较强且同步的应用场景&#xff0c;使用成熟的rpc框架来实现也是一种比较更好的选择。 开源的rpc框架有很…

MySQL---游标,异常处理,循环构建表

1. 游标 游标(cursor)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用光标对结果集进行 循环的处理。光标的使用包括光标的声明、OPEN、FETCH 和 CLOSE. -- 声明语法 declare cursor_name cursor for select_statement -- 打开语法 open cursor_name -- 取值语…

由浅入深Netty基础知识NIO三大组件原理实战

目录 1 三大组件1.1 Channel & Buffer1.2 Selector1.3 多线程版设计1.4 多线程版缺点1.5 线程池版设计1.6 线程池版缺点1.7 selector 版设计 2 ByteBuffer2.1 ByteBuffer 正确使用姿势2.2 ByteBuffer 结构2.3 调试工具类2.4 ByteBuffer 常见方法2.4.1 分配空间2.4.2 向 buf…

chatgpt赋能Python-numpy查找

Numpy查找 - 了解numpy中的查找功能 什么是Numpy&#xff1f; Numpy是Python语言中的一种开源的数学计算库&#xff0c;允许开发者轻松高效地进行数学运算。它提供了一整套矩阵运算方式&#xff0c;支持各种各样的数学函数和数据类型&#xff0c;并且可以与其他Python库良好地…

chatgpt赋能Python-macbook怎么用python

使用MacBook进行Python编程的完全指南 如果您是一名Python编程工程师&#xff0c;那么您需要一台性能良好的电脑来进行编程工作。今天&#xff0c;我们将探讨如何使用MacBook来编写Python代码&#xff0c;以及如何使您的Mac运行最佳状态。 安装Python 在开始使用Python之前&…

还在老一套?STM32使用新KEIL5的IDE,全新开发模式RTE介绍及使用

Keil新版本出来了&#xff0c;推出了一种全新开发模式RTE框架( Run-Time Environment)&#xff0c;更好用了。然而网上的教程资料竟还都是把Keil5当成Keil4来用&#xff0c;直接不使用这个功能。当前正点原子或野火的教程提供的例程虽有提到Keil5&#xff0c;但也是基本上当Kei…

Qt Quick系列(1)—开发界面以及相关文件介绍

作者&#xff1a;CCAccept 专栏&#xff1a;Qt Quick 文章目录 开发界面相关文件介绍.pro文件.pri文件&#xff08;这个一般要稍微大一点的Qt项目才会用到&#xff09;main.cppmain.qml 开发界面 如何具体的写代码实现Qt Quick的UI界面&#xff0c;首先我们需要新建一个空的…

Java面向对象程序设计实验报告(实验三 继承的练习)

✨作者&#xff1a;命运之光 ✨ 专栏&#xff1a;Java面向对象程序设计实验报告 ​ 目录 ✨一、需求设计 ✨二、概要设计 ✨三、详细设计 ✨四、调试结果 ✨五、测试结果 ✨附录&#xff1a;源程序代码&#xff08;带注释&#xff09; demo3类 Person类 Student类 …