Qt事件机制

这篇具有很好参考价值的文章主要介绍了Qt事件机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述


 Qt程序是事件驱动的, 程序的每个动作都是由内部某个事件所触发。事件系统在Qt总扮演了十分重要的角色,其事件的生成与事件的派发对GUI 程序起到了核心的作用。Qt事件处理流程图和时序图大致如下, 下面对于整个事件循环系统进行详细的讲解。

qt事件,qt,开发语言,ui,linux,Powered by 金山文档
qt事件,qt,开发语言,ui,linux,Powered by 金山文档

1. 事件生成


Qt中事件主要来源于两类,一类是平台插件,另一类是用户自己发送的事件。

  • 平台插件事件

用鼠标事件举例,一个鼠标事件,首先肯定是从驱动中传递上来,uos采用Xorg作为图形服务器,Xorg会加载驱动程序库,并从中获取事件转发到client上,Qt中通过QXcbConnect 连接XServer,并且在prossXcbEvents中使用了while 循环,不断的处理xcb消息并转发出去,Qt客户端中最原始的鼠标事件便来自于这里。那么我们从这里出发看看鼠标事件是怎么从平台插件接收到消息并传递到上层的。

qt事件,qt,开发语言,ui,linux,Powered by 金山文档

该函数比较简单,首先进入函数先判断连接有错误就退出程序,然后是一个while 循环中不断的从 m_eventQueue 中读取事件,compressEvent是压缩事件的意思,最后通过调用handleXcbEvent函数处理事件。在handleXcbEvent中会根据对应事件类型 调用qxcbwindow中具体的事件处理函数 QXcbWindow::handleMouseEvent 。在该函数中调用了静态函数QWindowSystemInterface::handleMouseEvent,代码如下:

QWindowSystemInterface 类可以看作一个中间层,用于隔离上层的Application和下层的平台插件。Qt是跨平台的,上层需要对下层的具体平台API屏蔽,采用提供QPlatform* 类采用虚接口的方式调用,下层应该尽量减少对上层的依赖。消息就通过QWindowSystemInterface这样一个中间层进行传递。

qt事件,qt,开发语言,ui,linux,Powered by 金山文档

继续看windowSysteminterface的代码,在handleMouseEvent函数中构造了MouseEvent对象并且通过handleWindowSystemEvent 函数发送,值得注意的是这里虽然构造了鼠标消息,单这并不是真正的QMouseEvent消息,我们继续往下看。

在 handleWindowSystemEvent 函数中,调用了模板类型的 handleWindowSystemEvent,通过synchronousWindowSystemEvents 判断是调用同步事件处理还是异步事件处理,异步处理方式就加入到事件队列中,同步处理就直接采用函数调用的方式。我们继续分析这两种方式的处理流程。

同步调用比较简单,在同步调用函数首先判断是否为主线程调用,如果非主线程调用那么就走异步调用,如果是当前线程是主线程,那么调用QGuiApplicationPrivate::processWindowSystemEvent(ev 戒指传递到上层,代码如下。

下面是关键函数调用堆栈(还是以mouseEvent为例).

在QGuiApplicationPrivate::processMouseEvent 函数中调用 sendSpontaneousEvent函数,进行发送事件这里可以和上面的用户事件堆栈代码进行对比。sendSpontaneousEvent的和sendEvent 的区别是 前者将事件标记为 Spontaneous,意思是事件起源于应用程序外部,一般来说由平台插件调用。我们自己发送事件就调用sendEvent 。sendSpontaneousEvent函数调用后调用notify 函数然后直接发送到对应类的event函数中。notifyInternal 函数的作用是通知全局回调函数。回调函数被定义为typedef bool (*qInternalCallback)(void **); 该函数定义可以很方便的与其他的语言进行交互,例如脚本、java等。当然也可以通过这个函数捕获所有的Qt事件。notify函数是一个虚函数,自定义的Application可以通过重新实现该函数,达到事件过滤的效果。 notify_helper 的任务同样也是事件过滤的作用,在notify_helper 函数中,会分别给安装在 Application 和receiver 对象中的事件过滤器发送事件。可以看到Qt 可以过滤事件的地方还是很多。

在异步调用中,把事件添加到Qt 的事件循环队列中,然后通过事件循环处理事件,然后发送到上层(也就是图上的QGuiApplication层),异步调用代码如下:

总结一下,结合前面的流程图这里的平台事件整个流程就比较清晰了。有两个地方是值得注意的。第一个就是QXcbConnect::prossXcbEvents。这里是有一个while循环不断的读取xcb事件,我们知道一个事件循环一般就是一个while循环不断的取事件并进行分发处理,那么这里是就是整个Qt的事件循环吗? 第二个值得注意的是,handleWindowSystemEvent的异步调用,它将事件加入到事件循环中,这个循环是整个Qt的事件循环吗,那它又是在哪里派发事件? 我们先记住这两点,再来看看用户事件。

  • 用户事件
    用户事件比较简单,在单元测试中尤为常见,通过QCoreapplication的postEvent和sendEvent 发送事件。值得注意的是postEvent 是把事件添加到事件循环中,而sendEvent则是直接发送事件。

先看postEvent函数,部分代码如下,第一个参数是消息接收者,第二个参数为具体消息,第三个参数为消息优先级。postEvent 主要是把事件添加到 postEventList 这样一个容器中,然后调用事件分发器的wakeUp。再往下走就走不通了,那么,这个消息进入事件循环后,在哪里进行分发的呢? 稍后我们再讨论事件循环的时候再讲它。

sendEvent就没啥好说了,函数参数比postEvent少了一个优先级,我们直接看它的调用堆栈就一目了然.前面讲平台事件的时候已经介绍了这些函数的作用,就不多讲了

到此位置,Qt事件的发生也就是图中红框这一部分基本都介绍完了。

qt事件,qt,开发语言,ui,linux,Powered by 金山文档

2. 事件循环与分发


  • 事件循环

我们都知道,创建一个Qt程序的时候,总会在main函数中调用QGuiapplication::exec 函数,只有当调用了QGuiapplication::exit 函数的时候,它才会退出,否这永远会”卡”在这个函数中。这个函数被称为事件循环函数。

在这一节我们先来看看事件循环长什么样子,然后结合前面事件的发生来分析事件分发的过程。先来看看exec函数堆栈。

从main函数开始,依次调用了QApplication、GUI、CORE的exec函数。这三者区别在Qt 官方文档中有详细的描述。我们主要关注两个点 QEventLoop和 QXcbGlibEventDispatcher。

QEventLoop是 事件循环的关键,从堆栈中可以发现在QEventLoop的processEvents中调用了QXcbGlibEventDispatcher::processEvents。而QXcbGlibEventDispatcher继承于QAbstractEventDispatcher类,QAbstractEventDispatcher是事件分发的抽象类,根据不同的平台,有不同的事件分发派生类,例如在windows下使用的就是标准的 Windows 消息机制,在linux下一般是采用glib进行事件分发,关键代码如下:

这个函数应该很熟悉的,在我们调试一些简单的Qt程序的时候,按下暂停断点一般都会停在这里,该函数也是一个while循环一直被循环执行,直至退出。而该函数内部则使用了Glib的GMainContext对象,并调用g_main_context_iteration()函数,该函数将能够遍历一次GMainContext提供的主循环,并有参数确定如果没有事件准备好是否需要的等待。那么GMainContext对象包含什么内容,又是如何创建的呢?

首先在QEventDispatcherGlibPrivate构造函数中,新建GMainContext对象。 然后使用函数g_source_new()创建GSource事件源对象,这里需要传入一组回调函数指针GSourceFuncs,一般包括prepare/check/dispatch这3种回调函数,用于获知事件状态。主循环调用 prepare/check 接口, 询问事件是否准备好如果 prepare 与 check接口的返回值均为 TRUE, 那么此时主事件循环会调用 dispatch 接口分发消息。并使用g_source_attach()将GSource对象绑定到GMainContext对象。 最后如前面所讲,g_main_context_iteration()函数会对GMainContext对象进行检查,会调用前面定义的回调函数, 这里涉及到glib中事件循环的机制,不多展开描述。

在Qt中,QXcbGlibEventDispatcher 创建了XcbEventSource 事件源对象用于处理xcb事件,另外该类继承QEventDispatcherGlib,在父对象的构造函数中 分别创建了post event,socket notification,normal timer,和idle timer这几种事件源对象,总共也就是 5类事件源对象,并将其绑定到GMainContext对象中。

qt事件,qt,开发语言,ui,linux,Powered by 金山文档
  • 事件分发

也就是这里,在Qt中利用了Glib的事件分发机制,将事件与g_source 绑定,使用一个while循环的机制不断的分发出去。那么Glib的事件是怎么和Qt中对应起来的,又是怎么从事件队列中分发的,以前面用户事件QCoreApplication::postEvent 为例。

回顾一下之前的postEvent 函数的代码,当调用 data->postEventList.addEvent(QPostEvent(receiver, event, priority)); 之后,将事件和对应的接收者打包放在postEventList 容器中,最后调用了一个事件调度器的wakeUp函数 dispatcher->wakeUp();

在该函数中讲 postEventSource 的 序列号serialNumber 增加 ref表示原子性+1 和 serialNumber++ 类似,并调用了 g_main_context_wakeup函数唤醒poll。这时候g_main_context_iteration 轮询各事件源,检查有没有需要调用的事件,主循环调用 prepare/check 接口, 询问事件是否准备好如果 prepare 与 check接口的返回值均为 TRUE, 那么此时主事件循环会调用 dispatch 接口分发消息。我们查看在prepare 与 check的代码,postEventSourceFuncs 的postEventSourcePrepare函数关键实现如下:

如果当前序列号与最后一次调用的序列号不同的话,就把 wakeUpCalled 设置为 true 并返回。canWait 表示如果没有挂起的事件,则等待。check 函数同样也调用的 prepare 函数,那么如果我们有postEvent函数加入进来,prepare 和 check 返回为true。接下来Glib库就对调用 dispatch 函数分发事件。

在dispatch 函数中调用了 QCoreApplication::sendPostedEvents 发送事件,并且把最后一次序列号 lastSerialNumber 设置为当前的序列号。

在 QCoreApplication::sendPostedEvents 函数中,也是一个while 循环,将我们开始放在 postEventList 容器中的事件,然后调用 QCoreApplication::sendEvent(r, e)全部发送出去。sendEvent就前面已经给详细讲解过了,该函数是直接的函数调用,和事件循环无关了。关键代码如下:

所以在linux下整个事件循环的流程图如下,通过上面5中接口,向事件循环中添加事件,事件队列由自己维护,gllib事件分发机制通过回调函数查询是否有需要处理的事件,并调用下面的5种函数接口,对事件队列中的事件pop然后进行处理。

qt事件,qt,开发语言,ui,linux,Powered by 金山文档

回到开始将平台插件的地方,平台插件的事件异步调用的时候在QWindowSystemInterface 的时候被加入到事件循环中,我们看回顾开始的代码,可以看到该事件循环就是获取的QCoreApplication 中的事件循环。那它的事件分发在哪里呢? 前面讲到的5类事件源对象其中的 XcbEventSource 就是处理这里的事件。在Prepare/check查询函数中永远返回了true。也就是只要有xcb事件就会进行处理。

在 dispatch事件分发函数中调用了QWindowSystemInterface::sendWindowSystemEvents函数。然后在该函数调用了 QGuiApplicationPrivate::processWindowSystemEvent 将事件传递到上层处理

值得注意的是在dispatch 函数中还调用了 processXcbEvents 函数,这个函数是xcb消息起源的函数。前面已经提到了,该函数中也是有个while 循环从 一个队列中取数据,并转化为 xcb 消息。那么该事件队列的数据来自于哪里? Qt主线程负责界面绘制、刷新、事件等任务,而平台消息基于socket,实时性要求较高,所以在平台插件的内部有专门的一个线程QXcbEventQueue负责处理xcb的消息。QXcbEventQueue 继承于QThread,在QxcbConnect中创建,run函数中调用 xcb_wait_for_event 函数等待 xcb消息,并将其加入到 QXcbEventQueue中供 processXcbEvents 处理。

3. QA


到此结束,事件循环就讲解完了,有几个问题

Q: 需要等待100 ms,又不想阻塞主线程,下面代码的原理是什么?

A:当执行到exec 的代码的时候,程序不再往下继续执行,因为进入了 事件分发器dispatch 函数的while 循环中,如果这时候事件队列中有事件,那么就继续处理事件,如果这时候没有事件,就暂停在这里 100ms 后继续执行后面代码。

Q:QDialog的exec 也调用了exec。其原理是什么?为什么会阻塞其他widget的消息

A:QDialog的exec 原理和第一个问题一样。同样是创建了QEventloop对象,这时候xcb事件队列中仍然会收到QWidget 的消息,并且进入事件循环,QWidget的消息转发到上层的时候,在QGuiapplication中被 blockedByModalWindow变量拦截。不再进行传递、文章来源地址https://www.toymoban.com/news/detail-610924.html

到了这里,关于Qt事件机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Qt 底层之事件驱动系统】深入理解 Qt 事件机制:主事件循环与工作线程的交互探究,包括 QML 的视角

    在探讨 Qt 的世界时,我们不仅是在讨论一种编程框架,更是在探索一种将复杂技术细节隐藏于幕后、让开发者专注于创造性工作的艺术形式。正如著名的计算机科学家 Edsger Dijkstra 所言:“简洁是复杂性的先决条件。” 在这一章节中,我们将探讨 Qt 事件机制的基础概念,这

    2024年02月22日
    浏览(52)
  • DAY4,Qt(事件处理机制的使用,Qt中实现服务器的原理)

    ---chatser.h---头文件 ---chatser.cpp---函数实现文件 ---main.cpp---测试文件 结果展示---     

    2024年02月15日
    浏览(55)
  • QT 之基础(一) 详解UI文件设计与运行机制

    一、项目文件组成 1.1 创建一个项目文件        建立好项目如下   (1)项目组织文件【untitled.pro】 存储项目设置文件 (2)主程序入口文件【main.cpp】 实现函数main()函数程序文件   (3)窗体界面文件【mainwindow.ui】 一个XML格式存储的窗体上的元件及其布局文件。 任何界面窗

    2024年02月16日
    浏览(40)
  • QT基础之——ui文件以及信号和槽机制

            上一节我们讲了除界面文件ui文件其他的所有文件,这一节我们来介绍一下ui文件:在文件目录中我们能看到界面文件这一栏,点击展开我们可以看到一个以\\\".ui\\\"结尾的文件,双击我们会看到如下界面:         我们在右侧选中label和PushButton拖入到工作台上的窗口中,

    2024年04月08日
    浏览(65)
  • Qt UI上的按钮和创建的按钮绑定 click 点击事件

    如果在ui 上 的按钮 绑定点击事件,按钮鼠标右键转到槽,点击clicked(),即可创建函数。 动态创建的按钮需要 用 connect 连接

    2024年02月15日
    浏览(49)
  • Qt开发-鼠标事件

    个人认为,事件机制是Qt最难以理解且最为精妙的一部分。事件主要分为两种: 在与用户交互时发生 。比如按下鼠标(mousePressEvent),敲击键盘(keyPressEvent)等。 系统自动发生 ,比如计时器事件(timerEvent)等。 在发生事件时(比如说上面说的按下鼠标),就会产生一个

    2024年02月09日
    浏览(43)
  • C++ Qt开发:Charts折线图绑定事件

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍 QCharts 折线图的常用方法及灵活运用。 在上一篇文章中笔者介绍了关于 QChart

    2024年02月04日
    浏览(47)
  • Qt实现全局鼠标事件监听器-Linux

    更多精彩内容 👉个人内容分类汇总 👈 👉Qt自定义模块、工具👈 Qt版本:V5.12.5 兼容系统: Windows:这里测试了Windows10,其它的版本没有测试; Linux:这里测试了ubuntu18.04、20.04,其它的没有测试; Mac:等啥时候我有了Mac电脑再说。 有时候我们想获取到【系统全局鼠标事件】

    2024年02月11日
    浏览(39)
  • 【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.5 键盘事件

    本章要实现的整体效果如下: QEvent::KeyPress ​ 键盘按下时,触发该事件,它对应的子类是 QKeyEvent QEvent::KeyRelease ​ 键盘抬起时,触发该事件,它对应的子类是 QKeyEvent 本节通过两个案例来讲解这 2 个事件: 键盘按下、释放事件的基本使用 通过键盘的上下左右箭头,控制标签

    2024年02月07日
    浏览(47)
  • Qt教程 — 2.1 如何使用Qt Designer 开发UI程序

    目录 1 Qt Designer简介 2 编辑UI界面 2.1 在 UI 界面添加一个Label 2.2 在 UI 界面添加一个按钮 2.3 在 UI 文件里连接信号与槽 方法一:通过信号和槽编辑栏 方法二:通过导航区信号和槽编按钮 方法三:通过跳转编辑代码实现—通过按钮输出文字 Qt Designer 是属于 Qt Creator 的一个功能

    2024年03月22日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包