Qt事件机制

目录

一、事件基本概念

1.1 事件基本概念和产生

1.2 事件类 

1.3 事件发送

二、事件的捕获处理

2.1 事件处理流程

2.2事件分发(event)处捕获事件

2.3 事件过滤器

2.4 重新实现事件处理函数

三、 QEventLoop

3.1事件循环基本概念

3.2 QEventLoop基本使用  

四、update异步刷新流程

 4.1 刷新事件的异步投递

4.2 刷新事件处理流程

4.3 重绘到内存Image

4.4 BackingStore和双缓冲

4.5 树形绘制


 

一、事件基本概念

1.1 事件基本概念和产生

        事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。

        Qt 程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始 Qt 的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。 

在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如  

这些函数都是 protected virtual 的,也就是说,我们可以在子类中重新实现这些函数。

  1. keyPressEvent()
  2. keyReleaseEvent()
  3. mouseDoubleClickEvent()
  4. mouseMoveEvent()
  5. mousePressEvent()
  6. mouseReleaseEvent() 等。

1.2 事件类 

        一个事件就是一个类对象 ,每一个事件类都有个唯一身份值:type值; type 值的作用就是我们在处理事件时用于识别事件类的代号 , 我们在官方文档可以看到,Qt 自带的事件类的 type 值都已经在 QEvent::type 中有了,数值范围在 0 - 999 之间 ; 而我自己定义的事件类也有个 type 值。为了保证我的这个值不和 Qt 的冲突,所以数值要大于 999。Qt 给我们规定了两个边界值:QEvent::User 和 QEvent::MaxUser,即 1000 - 65535。

Qt 提供了一个函数 registerEventType() 专门用于自定义事件的注册。如下:

class MyEvent : public QEvent
{
public:MyEvent();MyEvent(int x, int y, int z);static const Type type;int x;int y;int z;
};

1.3 事件发送

bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
void QCoreApplication::sendPostedEvents(QObject *receiver = Q_NULLPTR, int event_type = 0)

直接发送:sendEvent

        两个参数中一个是要发给谁,另一个是发送什么事件。这种方式的发送我们要等待对方处理,所以返回值是一个 bool 量来判断。对于许多事件类,有一个名为 isAccepted() 来告诉你是否被处理。因为事件被发送的时候,event 对象并不会被销毁,因此我们要在栈上创建 event 对象

发到队列:postEvent

        我们创建一个 Qt 程序的时候,一般在 main 下面会看到 QCoreApplication a(argc, argv) 以及 return a.exec() 的字样。这其实就是开启了 Qt 事件循环来维护一个事件队列,exec 的本质就是不停的调用 processEvent() 函数从队列中获取事件来处理。而 postEvent() 的作用就是把事件发送到这个队列中去。

        这种方式不需要等待处理结果,只要把事件发到队列中就可以了,所以返回值是 void。由于事件队列会持有发送的事件对象,在 post 的时候会将事件 delete 掉,所以我们必须在堆上创建 event 对象。 它将分发所有发布的事件,并进行了一些优化。例如,如果有多个resize事件,它们会被压缩为一个。这同样适用于绘制事件:QWidget::update()调用postEvent(),通过避免多次重绘来消除闪烁并提高速度。

在队列中立即处理:sendPostedEvents

        看参数我们就可以知道,这个函数的作用就是立刻、马上将队列中的 event_type 类型的事件立马交给 receiver 进行处理。需要注意的是,来自窗口系统的事件并不由这个函数进行处理,而是 processEvent()。

二、事件的捕获处理

2.1 事件处理流程

      2.2事件分发(event)处捕获事件

      事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler),event()函数主要用于事件的分发,具体类似以下代码

//!!! Qt5
bool QObject::event(QEvent *e)
{switch (e->type()) {case QEvent::Timer:timerEvent((QTimerEvent*)e);break;case QEvent::ChildAdded:case QEvent::ChildPolished:case QEvent::ChildRemoved:childEvent((QChildEvent*)e);break;// ...default:if (e->type() >= QEvent::User) {customEvent(e);break;}return false;}return true;
}

所以,如果你希望在事件分发之前做一些操作,就可以重写这个event()函数了。

bool CustomTextEdit::event(QEvent *e)
{if (e->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);if (keyEvent->key() == Qt::Key_Tab) {qDebug() << "You press tab.";return true;//结束时间转发}}return QWidget::event(e);//必要的,转发事件给父类处理
}

2.3 事件过滤器

        Qt 创建了QEvent事件对象之后,会调用QObject的event()函数处理事件的分发。显然,我们可以在event()函数中实现拦截的操作。由于event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个event()函数。这当然相当麻烦,更不用说重写event()函数还得小心一堆问题。

        QObject有一个eventFilter()函数,用于建立事件过滤器。函数原型如下:

virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );

        这个函数是一个“事件过滤器”,会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时机是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。

事件过滤器使用举例:

class MainWindow : public QMainWindow{public:MainWindow();protected:bool eventFilter(QObject *obj, QEvent *event);//重写事件过滤器private:QTextEdit *textEdit;};bool MainWindow::eventFilter(QObject *obj, QEvent *event){if (obj == textEdit) {if (event->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);qDebug() << "Ate key press" << keyEvent->key();return true;} else {return false;//}} else {// pass the event on to the parent classreturn QMainWindow::eventFilter(obj, event);}}MainWindow::MainWindow(){textEdit = new QTextEdit;setCentralWidget(textEdit);textEdit->installEventFilter(this);//安装事件过滤器}

        installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,因此,我们可以向QApplication或者QCoreApplication添加事件过滤器。这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大,但这种行为会严重降低整个应用程序的事件分发效率。因此,除非是不得不使用的情况,否则的话我们不应该这么做。

        事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

2.4 重新实现事件处理函数

        对于一些简单事件,我们可以直接继承实现该事件的处理函数,在事件处理流程的最终环节给自定义处理,如以下代码,重新实现了鼠标的移动、按压和释放等事件。

class EventLabel : public QLabel
{
protected:void mouseMoveEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);
};void EventLabel::mouseMoveEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Move: (%1, %2)
</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));
}void EventLabel::mousePressEvent(QMouseEvent *event)
{this->setText(QString("<center><h1>Press:(%1, %2)
</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));
}void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{QString msg;msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",event->x(), event->y());this->setText(msg);
}int main(int argc, char *argv[])
{QApplication a(argc, argv);EventLabel *label = new EventLabel;label->setWindowTitle("MouseEvent Demo");label->resize(300, 200);label->show();return a.exec();
}

三、 QEventLoop

3.1事件循环基本概念

        事件循环本质上就是一个无限循环,不停地去获取下一个事件,然后做出处理;直到 quit 事件发生,循环结束 。

3.2 QEventLoop基本使用  

 

 

四、update异步刷新流程

 4.1 刷新事件的异步投递

1.检查条件:检查控件是否隐藏或者刷新禁用,则直接返回。

2.计算更新区域:无参update则需要刷新整个区域,对于代参的则更新指定矩形区域。

3.如果控件支持BackingStore,则标记控件所属的顶层窗口(TLW)区域为“脏”,表示该部分需要重绘。

BackingStore:Qt框架中用于优化界面重绘的机制,Qt自动管理,主要作为TLW做缓冲区,暂存绘制内容;当某个区域需要更新时首先在缓冲区上重绘,完成后在复制到屏幕,提高重绘效率减少屏幕闪烁,支持局部更新。

4.事件投递:Qt向顶层窗口投递一个QEvent::UpdateRequest事件,将其放入事件队列;表示顶层窗口某个区域要重绘。

 

标脏函数markDirty:  

 

 

4.2 刷新事件处理流程

1.事件分发:notify函数会会处理事件,将UpdateRequest分发给receiver 即TLW;

2.事件处理:TLW接受事件后会在event函数中处理,对于UpdateRequest事件调用syncBackingStore,触发重绘操作。

 

 

 

 

 

4.3 重绘到内存Image

1.syncBackingStore 过程中会在绘制设备一般时内存Image,绘制背景(drawWidget)、绘制前景(文本、图标 通常时paint事件)、绘制子控件(递归drawWidget);

2.绘制完之后调用系统API将内存上的Image刷新到屏幕。windows调用BitBlt。

 

 

 

4.4 BackingStore和双缓冲

        BackingStore:Qt框架中用于优化界面重绘的机制,Qt自动管理,主要作为TLW做缓冲区,暂存绘制内容;当某个区域需要更新时首先在缓冲区上重绘,完成后在复制到屏幕,提高重绘效率减少屏幕闪烁,支持局部更新。

        双缓冲:常用的图形处理技术,主要应用于频繁更新屏幕内容场景,如动画、游戏和GUI中。使用两个缓冲分别存储正在显示的图像和即将显示的图像,避免在绘制中直接修改屏幕内容导致闪烁和撕裂现象。

  4.5 树形绘制

Setparient setchilden raise lower

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20250212:sigmastar系列2-获取UUID进行授权

距离上一篇Sigmastar文章已经过去3年了。最近基于Sigmastar-330 开发人脸识别SDK,需要进行授权管理,所以需要获取UUID作为激活、授权的凭证。 本文记录2个事情:授权逻辑 + sigmastar获取UUID 1:授权流程 step1:算法SDK在设备上电,算法初始化环节,校验本地是否有加密存…

STM32F407通过FSMC扩展外部SRAM和NAND FLASH

1 扩展外部SRAM 1.1 地址情况 FSMC控制器的存储区分为4个区(Bank)&#xff0c;每个区256MB。其中&#xff0c;Bank1可以用于连接SRAM、NOR FLASH、PSRAM&#xff0c;还可以连接TFT LCD。Bank1的地址范围是0x60000000&#xff5e;0x6FFFFFFF。Bank1又分为4个子区&#xff0c;每…

MySQL Workbench工具 导出导入数据库

第一步 数据库导出 1、打开workbench->连接数据库->Server->Data Export 2、选择要导出的数据库&#xff0c;Export Self-Contained File ->更改导出位置和数据库名->Start Export 3、提示“sql has finished”&#xff0c;没有error表示导出成功 第二步 数据…

我用AI做数据分析之四种堆叠聚合模型的比较

我用AI做数据分析之四种堆叠聚合模型的比较 这里AI数据分析不仅仅是指AI生成代码的能力&#xff0c;我想是测试AI数据分析方面的四个能力&#xff0c;理解人类指令的能力、撰写代码的能力、执行代码的能力和解释结果的能力。如果这四个能力都达到了相当的水准&#xff0c;才可…

广告深度学习计算:阿里妈妈大模型服务框架HighService

一、背景 HighService(High-Performance Pythonic AI Service) 是在支持阿里妈妈业务过程中&#xff0c;不断提炼抽象出的高性能Python AI服务框架&#xff0c;支持视频、图文、LLM等多种模型&#xff0c;能够显著加快模型的推理速度&#xff0c;提高集群的资源利用效率。随着S…

深度学习框架探秘|TensorFlow vs PyTorch:AI 框架的巅峰对决

在深度学习框架中&#xff0c;TensorFlow 和 PyTorch 无疑是两大明星框架。前面两篇文章我们分别介绍了 TensorFlow&#xff08;点击查看&#xff09; 和 PyTorch&#xff08;点击查看&#xff09;。它们引领着 AI 开发的潮流&#xff0c;吸引着无数开发者投身其中。但这两大框…

企语企业管理系iFair(F23.2_a0)在Debian操作系统中的安装

起因&#xff1a;在安装了F24.8版本后&#xff0c;发现生产用环境和测试、开发用环境还是分开的好。 旧版的用来实验、测试&#xff0c;新版的一步一步小心的配置、使用是比较稳妥的操作。因此&#xff0c;决定在KVM虚拟机上搭建一个F23.2版本的企语系统。 一、 存在的问题 而…

Redis 数据类型 Hash 哈希

在 Redis 中&#xff0c;哈希类型是指值本⾝⼜是⼀个键值对结构&#xff0c;形如 key "key"&#xff0c;value { { field1, value1 }, ..., {fieldN, valueN } }&#xff0c;Redis String 和 Hash 类型⼆者的关系可以⽤下图来表⽰。 Hash 数据类型的特点 键值对集合…

Elasticsearch:15 年来致力于索引一切,找到重要内容

作者&#xff1a;来自 Elastic Shay Banon 及 Philipp Krenn Elasticsearch 刚刚 15 岁了&#xff01;回顾过去 15 年的索引和搜索&#xff0c;并展望未来 15 年的相关内容。 Elasticsearch 刚刚成立 15 周年。一切始于 2010 年 2 月的一篇公告博客文章&#xff08;带有标志性的…

EF Core中实现值对象

目录 值对象优点 值对象的需求 值类型的实现 值类型GEO的实现 值类型MultilingualString的实现 案例&#xff1a;构建表达式树&#xff0c;简化值对象的比较 值对象优点 把有紧密关系的属性打包为一个类型把领域知识放到类的定义中 class shangjia {long id;string nam…

ETHEREAL:使用压缩Tsetlin机器实现能效高吞吐量推理

论文标题 英文标题&#xff1a;ETHEREAL: Energy-efficient and High-throughput Inference using Compressed Tsetlin Machine 中文标题&#xff1a;ETHEREAL&#xff1a;使用压缩Tsetlin机器实现能效高吞吐量推理 作者信息 Shengyu Duan, Newcastle University, Newcastle…

PyCharm 批量替换

选择替换的内容 1. 打开全局替换窗口 有两种方式可以打开全局替换窗口&#xff1a; 快捷键方式&#xff1a; 在 Windows 或 Linux 系统下&#xff0c;按下 Ctrl Shift R。在 Mac 系统下&#xff0c;按下 Command Shift R。菜单操作方式&#xff1a;点击菜单栏中的 Edit&…

mars3d接入到uniapp的时候ios上所有地图的瓦片都无法加载解决方案

用的是【Mars3d】官网的uniapp的仓库&#xff0c;安卓没有问题&#xff0c;但是ios的不行 相关链接 mars3d-uni-app: uni-app技术栈下的Mars3D项目模板 解决方案&#xff1a;感觉所有图片请求全被拦截了 uniapp的ios内核不允许跨域&#xff0c;需要先把瓦片下载后转base64&…

SpringBoot速成(十)更新用户信息P11-P12

1.代码展示&#xff1a; 1.RequestBody 是 Spring 框架中用于处理 HTTP 请求体的注解&#xff0c;通常用于控制器&#xff08;Controller&#xff09;层的方法参数中。当客户端发送一个包含 JSON 或 XML 数据的 HTTP 请求时&#xff0c;可以使用 RequestBody 将这些数据绑定到一…

3.3.3 VO-O语法- 语法算子(二)

循环遍历 由于VO语言是面向数据集的&#xff0c;其所有隐含的语义中都已经带有了遍历并计算的数据逻辑。因此&#xff0c;VO语言只提供了一种支持循环语法的算子--Loop算子。 Loop算子 Loop算子是一个容器算子&#xff0c;其可以实现对其内部子流程的循环迭代运行。但Loop算…

java后端开发day13--面向对象综合练习

&#xff08;以下内容全部来自上述课程&#xff09; 注意&#xff1a;先有javabean&#xff0c;才能创建对象。 1.文字版格斗游戏 格斗游戏&#xff0c;每个游戏角色的姓名&#xff0c;血量&#xff0c;都不相同&#xff0c;在选定人物的时候&#xff08;new对象的时候&#…

RocketMQ和Kafka如何实现顺序写入和顺序消费?

0 前言 先说明kafka&#xff0c;顺序写入和消费是Kafka的重要特性&#xff0c;但需要正确的配置和使用方式才能保证。本文需要解释清楚Kafka如何通过分区来实现顺序性&#xff0c;以及生产者和消费者应该如何配合。   首先&#xff0c;顺序写入。Kafka的消息是按分区追加写入…

DeepSeek系统崩溃 | 极验服务如何为爆火应用筑起安全防线?

引言 极验服务让您的产品站在风口之时&#xff0c;不必担心爆红是灾难的开始&#xff0c;而是期待其成为驱动持续创新的全新起点。 01现象级狂欢背后&#xff0c;你的业务安全防线抗得住吗&#xff1f; “近期DeepSeek线上服务受到大规模恶意攻击&#xff0c;注册可能繁忙&am…

【故障处理】- RMAN-06593: platform name ‘Linux x86 64-bitElapsed: 00:00:00.00‘

【故障处理】- RMAN-06593: platform name Linux x86 64-bitElapsed: 00:00:00.00 一、概述二、报错原因三、解决方法 一、概述 使用xtts迁移&#xff0c;在目标端进行恢复时&#xff0c;遇到RMAN-06593: platform name Linux x86 64-bitElapsed: 00:00:00.00’报错。 二、报错…

日志结构化处理:PO对象toString日志转JSON工具

日志结构化处理&#xff1a;PO对象toString日志转JSON工具 1. 解决的问题2. 下载地址 在Java项目中&#xff0c;PO&#xff08;Plain Old Java Object&#xff09;对象遍布各个角落&#xff0c;且常常伴随着大量的日志记录需求。传统的做法是通过toString方法直接打印这些对象&…