Qt的事件循环机制源码分析

 Qt 使用了一个事件模型来与操作系统沟通,处理诸如鼠标点击、键盘输入等用户动作。以下步骤详细解释了从操作系统捕捉鼠标点击事件到 Qt 处理该事件的过程:

1、事件处理过程

1.1、Qt 的事件循环

Qt 应用程序运行时会启动一个事件循环(QEventLoop)。该事件循环不断查询操作系统的消息队列以检查是否有新事件。一旦检测到鼠标点击事件,Qt 的应用程序框架从操作系统的消息队列中取得这个消息,并将其转换为 Qt 内部的事件格式(比如 QMouseEvent)。

1.2、 事件传递给目标窗口

在 Qt 中,每个窗口部件(QWidget)都是一个可能的事件目标。Qt 根据鼠标点击的位置确定具体的 QWidget,并将 QMouseEvent 发送到这个部件。这个部件通常是一种 QPushButton,QLineEdit 等,取决于鼠标点击的具体区域。

1.3、 事件过滤器的应用

事件可以被安装了事件过滤器的对象捕获。如果按钮等对象有事件过滤器(通过 QObject::installEventFilter() 安装),这些过滤器有机会在事件被目标 widget 处理之前预处理这个事件。如果事件过滤器决定接受(而不是忽略)该事件,则这个事件至此结束,不会被传递到原本的目的地。

1.4、 事件的处理

如果事件未被过滤器消耗,那么它将被送到目标 QWidget 进行处理。对于鼠标点击事件,QWidget 可能会触发一个信号(例如 QPushButton 的 clicked()),或改变其内部状态。

1.5、 事件的冒泡

 在 Qt 中,如果事件在当前的 QWidget 处未被完全处理,它可以继继传递给父组件。这个所谓的“事件冒泡”接着会一直持续,直到找到一个可以处理该事件的 widget 或者传递到顶层窗口。

1.6、最终处理

事件最终会被某个组件处理完成或者传递至顶层 QWidget 如果这些都不处理,事件就会被忽略,结束其生命周期。

2、源码分析

这个过程有效地确保了 Qt 应用程序可以灵活地响应各类输入事件,并允许开发者通过各种机制(如事件过滤器、重写事件处理函数等)自定义响应这些事件的行为。下面为qt处理事件的伪代码分析(windows平台为例)

// Section 1: 主函数
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Widget window;   // Widget继承自QWidget
    window.show();
    // 进入QApplication事件循环
    return app.exec();
}

// Section 2: QApplication事件循环
int QApplication::exec()
{
    // 将事件循环的处理委托给QCoreApplication
    return QCoreApplication::exec();
}

// Section 3: QCoreApplication事件循环处理
int QCoreApplication::exec()
{
    QThreadData *threadData = self->d_func()->threadFunc();
    // 确保当前方法是在主线程中调用
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }
    // 检查是否已有事件循环
    if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    // 调用QEventLoop处理事件队列
    int returnCode = eventLoop.exec();

    return returnCode;
}

// Section 4: QEventLoop事件循环执行
int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    try {
        // 循环处理事件直到遇见退出指令
        while (!d->exit)
            processEvents(flags | WaitForMoreEvents | EventLoopExec);
    } catch (...) {}
}

// Section 5: 事件处理
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher)
        return false;
    if (flags & DeferredDeletion)
        QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
    // 将事件派发给平台相关的事件分发器
    return d->threadData->eventDispatcher->processEvents(flags);
}

// Section 6: 平台相关的事件处理,这里以Windows为例
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherWin32);
    if (!d->internalHwnd)
        createInternalHwnd();
    d->interrupt = false;
    bool retVal = false;

    MSG msg;
    while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
        if (!filterEvent(&msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);  // 分发消息给窗口程序处理
        }
    }
    return retVal;
}

// Section 7: Windows窗口程序的消息回调函数
LRESULT CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)   
{  
    MSG msg = {hwnd, message, wParam, lParam};
    QWidget *widget = nullptr;
    QETWidget *qetwidget = nullptr;

    // 检查message是否属于Qt可转义的鼠标事件
    if (qt_is_translatable_mouse_event(message)) {  
        if (QApplication::activePopupWidget() != nullptr) {              
            POINT curPos = msg.pt;  
            // 取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例
            QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);  
            if (w)  
                qetwidget = dynamic_cast<QETWidget*>(w);  
        }  
        // 如果鼠标事件没有被“压制处理”
        if (!qt_tabletChokeMouse) {  
            // Windows的回调函数将鼠标事件分发回给了Qt Widget
            bool result = qetwidget->translateMouseEvent(msg);
        }
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

// Section 8: 平台依赖的鼠标事件转化
bool QETWidget::translateMouseEvent(const MSG &msg)  
{  
    // 这里省略了具体的鼠标事件解析代码
    QPoint point = QPoint(msg.lParam); // 假定lParam中存储了鼠标坐标
    QMouseEvent event(QEvent::MouseButtonPress, point, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);  // 示例化一个鼠标事件

    // 将事件派发至QWidget
    QApplicationPrivate::sendMouseEvent(this, &event, nullptr, this, &qt_button_down, qt_last_mouse_receiver);
    return true;
}

// Section 9: 发送鼠标事件
bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event, QWidget *alienWidget,
                                         QWidget *nativeWidget, QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver)
{
    if (event->type() == QEvent::MouseButtonPress) {
        // 判断事件是否为自发的
        bool spontaneous = QCoreApplication::testAttribute(Qt::AA_SynthesizedMouseEvents);
        if (spontaneous)
            return QApplication::sendSpontaneousEvent(receiver, event);
        else
            return QApplication::sendEvent(receiver, event);
    }
    return false;
}

// Section 10: 实际派发事件
bool QApplication::notify(QObject *receiver, QEvent *event)
{
    switch (event->type()) {
    case QEvent::MouseButtonPress:
    case QEvent::MouseButtonRelease:
    case QEvent::MouseButtonDblClick:
    case QEvent::MouseMove:
        return d->notify_helper(receiver, event);
        break;
    default:
        return QCoreApplication::notify(receiver, event);
    }
}

// Section 11: 实际处理事件的帮助函数
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent *e)
{
    //这里可以看到先调用事件过滤器,如果在EventerFilter拦截事件,那么事件就不会继续传递 
    if (sendThroughObjectEventFilters(receiver, e))
        return true;

    bool consumed = receiver->event(e);  // 事件最终送达receiver的event()方法
    e->setSpontaneous(false);
    return consumed;
}

// Section 12: QWidget事件处理实现
bool QWidget::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::MouseButtonPress:
        mousePressEvent(static_cast<QMouseEvent*>(event));
        break; // 进一步处理鼠标按下事件
    }
    return true;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript:实现内容显示隐藏(展开收起)功能

一、场景 点击按钮将部分内容隐藏&#xff08;收起&#xff09;&#xff0c;再点击按钮时将内容显示&#xff08;展开&#xff09;出来。 二、技术摘要 js实现实现内容显示隐藏js动态给ul标签添加li标签js遍历数组 三、效果图 四、代码 js_block_none.js代码 var group1 doc…

MySQL高级-SQL优化-小结

文章目录 1、insert 优化2、主键优化3、order by 优化4、group by 优化5、limit 优化6、count 优化7、update 优化 1、insert 优化 insert&#xff1a;批量插入、手动控制事务、主键顺序插入 大批量插入&#xff1a;load data local infile 2、主键优化 主键长度尽量短、顺序插…

webpack【实用教程】

基础配置 配置的拆分和合并 通常 webpack 的配置文件会有3个 webpack.common.js 公共配置&#xff08;会被另外两个配置文件导入并合并&#xff09;webpack.dev.js 开发环境的配置webpack.prod.js 生产环境的配置 开发环境的本地服务 在 webpack.dev.js 中配置 devServer:…

探索未来的AI革命:GPT-5的即将登场

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

柔性数组(flexible array)

柔性数组从C99开始支持使用 1.柔性数组的概念 概念&#xff1a; 结构体中&#xff0c;结构体最后一个元素允许是未知大小的数组&#xff0c;这就叫[柔性数组]的成员 struct S {int n;char arr[]; //数组大小未知(柔性数组成员) }; 柔性数组的特点&#xff1a; 结构体中柔性…

右键新建没有TXT文本文档的解决办法

电脑右键新建&#xff0c;发现没有txt了&#xff0c;我查网上办法都有点复杂&#xff0c;诸如注册表的&#xff0c;但是其实很简单&#xff0c;重启windows资源管理器就可以了。 点击重新启动&#xff0c;之后新建就有txt文档了。

Windows server 由于没有远程桌面授权服务器可以提供许可证,远程会话连接已断开。

问题现象&#xff1a; 解决办法 临时远程方式1: 打开 mstsc 时带上 /admin 等参数&#xff0c;如下图所示&#xff1a; 使用“mstsc /admin /v:目标ip”来强制登录服务器&#xff0c;但只能是管理员身份。 远程方式2&#xff1a; 通过VM远程登陆系统后&#xff0c;运行输入R…

安卓速度下载v1.0.5/聚合短视频解析下载

功能特色 短视频下载与高级管理 – 支持短视频下载&#xff0c;为您提供一系列高级视频管理功能包括视频内容提取、智能防重复技术、视频体积压缩以及视频转换成GIF图片等&#xff1b; 磁-力链接下载升级 – 现支持磁力链接下载&#xff0c;实现边下载边播放的便捷体验&#x…

LLaMA2模型训练加速秘籍:700亿参数效率提升195%!

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID &#xff5c; 计算机视觉研究院 学习群 &#xff5c; 扫码在主页获取加入方式 开源地址&#xff1a;https://github.com/hpcaitech/ColossalAI 计算机视觉研究院专栏 Column of Computer Vision Ins…

【深度学习】服务器炼丹代码配置、Python使用指定gpu显卡运行代码

【显卡】服务器炼丹代码配置 写在最前面一、查看哪几块显卡能用二、使用指定gpu运行代码1、指定使用GPU0运行脚本&#xff08;默认是第一张显卡, 0代表第一张显卡的id,其他的以此类推&#xff09;2、指定使用多张显卡运行脚本 三、如何使用1、单块显卡使用2、多GPU训练使用Data…

亚太杯赛题思路发布(中文版)

导读&#xff1a; 本文将继续修炼回归模型算法&#xff0c;并总结了一些常用的除线性回归模型之外的模型&#xff0c;其中包括一些单模型及集成学习器。 保序回归、多项式回归、多输出回归、多输出K近邻回归、决策树回归、多输出决策树回归、AdaBoost回归、梯度提升决策树回归…

javaSE知识点整理总结(上)

目录 一、面向对象 1. 类、对象、方法 2.面向对象三大特征 &#xff08;1&#xff09;封装 &#xff08;2&#xff09;继承 &#xff08;3&#xff09;多态 二、常用类 1.Object类 2.Array类 3.基本数据类型包装类 4.String类 5.StringBuffer类 6.Math类 7.Random…

ONLYOFFICE 8.1 桌面编辑器测评:引领数字化办公新潮流

目录 前言 下载安装 新功能概述 1.PDF 编辑器的改进 2. 演示文稿中的幻灯片版式 3.语言支持的改进 4. 隐藏“连接到云”板块 5. 页面颜色设置和配色方案 界面设计&#xff1a;简洁大方&#xff0c;操作便捷 性能评测&#xff1a;稳定流畅&#xff0c;高效运行 办公环…

恭喜!Apache SeaTunnel2024开源之夏学生中选名单出炉!

经过严格的筛选&#xff0c;开源之夏组委会及导师已经选出并录取项目对应的学生&#xff0c;社区联合中科院开展的开源之夏活动也进入到了激动人心的中选公示阶段。 在这里&#xff0c;我们恭喜下面的同学&#xff0c;已成功匹配到Apache SeaTunnel社区的项目&#xff0c;即将开…

主从复制、哨兵以及Cluster集群

目录 1.Redis高可用 2.Redis主从复制 2.1 主从复制的作用 2.2 主从复制流程 2.3 搭建Redis主从复制 2.3.1 修改Redis配置文件&#xff08;Master节点操作&#xff09; 2.3.2 修改Redis配置文件&#xff08;Slave节点操作&#xff09; 2.3.2 验证主从复制结果 3.Redis哨…

数据分析三剑客-Matplotlib

数据分析三剑客 数据分析三剑客通常指的是在Python数据分析领域中&#xff0c;三个非常重要的工具和库&#xff1a;Pandas、NumPy和Matplotlib。Pandas主要负责数据处理和分析&#xff0c;NumPy专注于数值计算和数学运算&#xff0c;而Matplotlib则负责数据可视化。这三个库相…

聊聊啥项目适合做自动化测试

作为测试从业者&#xff0c;你是否遇到过这样的场景&#xff0c;某天公司大Boss找你谈话。 老板&#xff1a;小李&#xff0c;最近工作辛苦了 小李&#xff1a;常感谢您的认可&#xff0c;这不仅是对我个人的鼓励&#xff0c;更是对我们整个团队努力的认可。我们的成果离不开每…

【python】一篇文零基础到入门:快来玩吧~

本笔记材料源于&#xff1a; PyCharm | 创建你的第一个项目_哔哩哔哩_bilibili Python 语法及入门 &#xff08;超全超详细&#xff09; 专为Python零基础 一篇博客让你完全掌握Python语法-CSDN博客 0为什么安装python和pycharm&#xff1f; 不同于c&#xff0c;c&#xff0…

NFT Insider #136:韩国将为NFT市场带来严格监管,The Sandbox DAO举办Twitter Space AMA

引言&#xff1a;NFT Insider由NFT收藏组织WHALE Members &#xff08;https://twitter.com/WHALEMembers&#xff09;、BeepCrypto &#xff08;https://twitter.com/beep_crypto&#xff09;联合出品&#xff0c;浓缩每周NFT新闻&#xff0c;为大家带来关于NFT最全面、最新鲜…

已解决javax.transaction.InvalidTransactionException:事务无效的正确解决方法,亲测有效!!!

已解决javax.transaction.InvalidTransactionException&#xff1a;事务无效的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 报错原因 解决思路 解决方法 1. 确保事务的正确启动和结束 Spring中的事务管理 2. 避免嵌套事务问题…