当前位置: 首页 > news >正文

Qt事件系统学习笔记

文章目录

  • 前言
    • ✅ 1. 事件生成与投递(起点)
    • ✅ 2. 事件过滤器(可选拦截)
    • ✅ 3. QWidget::event() 统一入口
    • ✅ 4. 具体事件处理函数(最终生效点)
    • ✅ 5. 事件接受/忽略机制
    • ✅ 6. 事件冒泡(若未被接受)
    • ✅ 一句话总结
  • 1. Qt中的事件实例
  • 2. 事件过滤器
  • 3. 事件循环


前言

在 Qt 中,事件作为一个对象,继承自 QEvent 类,常见的有键盘事件 QKeyEvent、鼠标事件 QMouseEvent 和定时器事件 QTimerEvent 等,与 QEvent 类的继承关系图如下所示。本章会详细讲解这 3 个常见的事件,还会涉及事件过滤器和自定义事件的知识。关于本章的相关内容,可以在 Qt 帮助中通过 The Event System 关键字查看。
在这里插入图片描述

在 Qt 中,一个事件(如鼠标点击、键盘输入)在部件(widget)中真正“生效”,必须经历一个分层传递+处理链的过程。这个链条由以下几级组成:

✅ 1. 事件生成与投递(起点)

事件由系统或 Qt 内部生成,最终通过 QCoreApplication::notify() 投递给目标对象(通常是某个 QWidget)。

✅ 2. 事件过滤器(可选拦截)

如果该对象或其父对象安装了事件过滤器(installEventFilter()),事件会优先传给过滤器的 eventFilter() 函数:
若返回 true,事件被拦截,不再向下传递;
若返回 false,事件继续传给目标对象。

✅ 3. QWidget::event() 统一入口

事件进入目标部件的 event(QEvent *e) 函数,这是事件分发的总调度中心:
根据事件类型(如 QEvent::MouseButtonPress),调用对应的处理函数;
若你在 event() 中返回 true,事件被视为已处理,不再向下传递。

✅ 4. 具体事件处理函数(最终生效点)

event() 内部会根据事件类型,调用如下函数

事件类型处理函数
鼠标按下mousePressEvent(QMouseEvent *)
键盘按下keyPressEvent(QKeyEvent *)
重绘paintEvent(QPaintEvent *)
大小变化resizeEvent(QResizeEvent *)

这些函数通常是你重写以让事件“生效”的地方。

✅ 5. 事件接受/忽略机制

每个事件对象都有 accept() 和 ignore() 方法:
accept():表示事件已处理,不再向上传递给父组件;
ignore():表示事件未处理,继续冒泡给父组件。

✅ 6. 事件冒泡(若未被接受)

如果当前部件未处理事件(或显式 ignore()),事件会向父组件逐级传递,直到被处理或到达顶层窗口。
✅ 示例流程图(以点击按钮为例)

用户点击按钮↓
QApplication::notify()↓
按钮的 eventFilter()(如果安装了)↓
按钮的 event()↓
按钮的 mousePressEvent()↓(若 ignore())
父组件的 mousePressEvent()

✅ 一句话总结

事件在部件生效 = 过滤器放行 + event() 分发 + 具体处理函数执行 + accept() 标记。
任何一个环节拦截或接受,事件就不再继续传递。

1. Qt中的事件实例

在每个程序的 main() 函数的最后都会调用 QApplication 类的 exec() 函数,它会使 Qt 应用程序进人事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt 便会构建一个相应的 QEvent 子类的对象来表示,然后将它传递给相应的 QObject 对象或其子对象。下面通过例子来看一下 Qt 中的事件传递过程。

新建 Qt Gui 应用,项目名称为 myEvent,基类选择 QWidget,然后类名保持 Widget 不变。建立完成后向项目中添加新文件,模板选择 C++ 类,类名为 MyLineEdit,基类手动填写为 QLineEdit,自定义了一个 MyLineEdit 类。

mylineEdit. h 文件:

#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H#include <QLineEdit>class MyLineEdit : public QLineEdit
{Q_OBJECT
public:explicit MyLineEdit(QWidget *parent = nullptr);// event()函数获取事件的类型bool event(QEvent *event);    protected:// MyLineEdit类的键盘按下事件void keyPressEvent(QKeyEvent *event);
};#endif // MYLINEEDIT_H

这里添加了 keyPressEvent() 函数和 event() 函数的声明。

mylineEdit. cpp 文件:

#include "mylineedit.h"
#include <QKeyEvent>
#include <QDebug>MyLineEdit::MyLineEdit(QWidget *parent) :QLineEdit(parent)
{}// MyLineEdit类的键盘按下事件
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{qDebug() << tr("MyLineEdit键盘按下事件");// 让MyLineEdit输入栏能输入字符QLineEdit::keyPressEvent(event);          // 执行QLineEdit类的默认事件处理event->ignore();                          // 忽略该事件
}//event()函数获取事件的类型
bool MyLineEdit::event(QEvent *event)  
{// 判断触发事件类型是否为键盘按下事件if(event->type() == QEvent::KeyPress)qDebug() << tr("MyLineEdit的event()函数");return QLineEdit::event(event);   // 执行QLineEdit类event()函数的默认操作
}

这里自定义了一个 MyLineEdit 类,它继承自 QWidget,并且实现了 MyLineEdit 类的 keyPressEvent() 函数和 event() 函数。event() 函数中使用了 event->type() 来获取事件的类型。如果是键盘按下事件 QEvent::KeyPress,则输出信息,另外返回父类的 event() 函数的操作结果。

widget.h 文件:

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
class MyLineEdit;
namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();// Widget类的事件过滤器bool eventFilter(QObject *obj, QEvent *event);    private:Ui::Widget *ui;MyLineEdit *lineEdit;protected:// Widget类的键盘按下事件void keyPressEvent(QKeyEvent *event);
};#endif // WIDGET_H

这里也添加了keyPressEvent()函数的声明。

widget.cpp 文件:

#include "widget.h"
#include "ui_widget.h"
#include "mylineedit.h"
#include <QKeyEvent>
#include <QDebug>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);lineEdit = new MyLineEdit(this);lineEdit->move(100, 100);
}Widget::~Widget()
{delete ui;
}// Widget类的键盘按下事件
void Widget::keyPressEvent(QKeyEvent *event)
{qDebug() << tr("Widget键盘按下事件");
}// Widget类的事件过滤器
bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器
{// 如果是lineEdit部件上的事件if(obj == lineEdit){              if(event->type() == QEvent::KeyPress)qDebug() << tr("Widget的事件过滤器");}return QWidget::eventFilter(obj, event);
}

这里也实现了 Widget 类的 keyPressEvent() 函数,并且会调用 MyLineEdit 类的 keyPressEvent() 函数。在事件过滤器中,先判断该事件的对象是不是 lineEdit,如果是,再判断事件类型,最后返回 QWidget 类默认的事件过滤器的执行结果。

运行程序,然后按下键盘的任意键,比如这里按下 a 键,执行结果如下图所示。
在这里插入图片描述

可以看到,事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的 event() 函数,最后是焦点部件的事件处理函数,例如这里的键盘按下事件函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数,如上图所示。注意,event() 函数和事件处理函数,是在该部件内进行重新定义的,而事件过滤器却是在该部件的父部件中进行定义的。
在这里插入图片描述

2. 事件过滤器

Qt中提供了事件过滤器来实现在一个部件中监控其他多个部件的事件。事件过滤器与其他部件不同,它不是一个类,只是由两个函数组成的一种操作,用来完成一个部件对其他部件的事件的监视。这两个函数分别是installEventFilter()和eventFilter(),都是QObject类中的函数。下面通过具体的例子来进行讲解。

在widget.h文件中添加public函数声明:

bool eventFilter(QObject* obj,QEvent *event):

然后在widget.cpp文件中添加头文件

#include < QKeyEvent >
#include< QWheelEvent >

在构造函数中添加代码:

ui->textEdit->installEventFilter(this);//为编辑器部件在本窗口上安装事件过滤器
ui->spinBox->installEventFilter(this);

要对一个部件使用事件过滤器,那么就要先使用其的 installEventFilter()函数为其安装事件过滤器,这个函数的参数表明了监视对象。

这里就为textEdit部件和 spin-Box部件安装了事件过滤器,其参数this表明要在本部件(即 Widget)中监视 textEdit和spinBox的事件。

这样,就需要重新实现 Widget类的eventFilter()函数,在其中截获并处理两个子部件的事件。

下面实现事件过滤器函数

bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器
{if (obj == ui->textEdit) {                        // 判断部件if (event->type() == QEvent::Wheel) {         // 判断事件// 将event强制转换为发生的事件的类型QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);if (wheelEvent->delta() > 0) ui->textEdit->zoomIn();else ui->textEdit->zoomOut();return true;                              // 该事件已经被处理} else {return false;                // 如果是其他事件,可以进行进一步的处理}}else if (obj == ui->spinBox) {if (event->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);if (keyEvent->key() == Qt::Key_Space) {ui->spinBox->setValue(0);return true;} else {return false;}} else {return false;}}else return QWidget::eventFilter(obj, event);
}

在这个事件过滤器中先判断部件的类型,然后再判断事件的类型,如果是需要的事件,那么就将其进行强制类型转换static_cast<*>,然后进行相应的处理。

这里需要说明,如果要对一个特定的事件进行处理,而且不希望它在后面的传递过程中再被处理,那么就返回true,否则返回false。

  return true;                              // 该事件已经被处理} else {return false;                // 如果是其他事件,可以进行进一步的处理}

这个函数实现了在textEdit部件中使用滚轮进行内容的放大或缩小,在spinBox部件中使用空格来使数值设置为0。现在运行程序查看效果。
在这里插入图片描述
可以看到,使用事件过滤器可以很容易地处理多个部件的多个事件,如果不使用它,那么就得分别子类化各个部件,然后重新实现它们对应的各个事件处理函数﹐那样就会很麻烦了。

Qt中也提供了发送一个事件的功能,它由QCoreApplication类的

bool QCoreApplication: : sendEvent(Q0bject * receiver,QEvent * event)void QCoreApplication; : postEvent(Q0bject * receiver,QEvent * event,int priority =Qt::NormalEventPriority)

函数来实现。

这两个函数的主要区别是: sendEvent()会立即处理给定的事件,而postEvent()则会将事件放到等待调度队列中,当下一次Qt的主事件循环运行时才会处理它。

这两个函数还有其他一些区别,比如 sendEvent()中的QEvent对象参数在事件发送完成后无法自动删除,所以需要在栈上创建QEvent对象;而postEvent()的QEvent对象参数必须在堆上进行创建(例如使用new),当事件被发送后事件队列会自动删除它。这两个函数更多的介绍可以参考它们的帮助文档。

下面再widget.cpp文件中的构造函数中添加代码来向spinBox部件发送一个向上方向键被按下的事件:

 QKeyEvent myEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);qApp->sendEvent(ui->spinBox, &myEvent);           // 发送键盘事件到spinBox部件

这里使用了sendEvent()函数,其中,QKeyEvent对象是在栈上创建的。

这里的qApp是QApplication对象的全局指针,每一个应用程序只能使用一个 QApplication对象,等价于使用QApplication::sendEvent()。

现在运行程序可以发现, spinBox部件中初始值变为了1,这说明已经在这个部件按下了向上方向键。

Qt中还可以使用自定义的事件,这个需要继承QEvent类,可以通过帮助中TheEvent System关键字中的相关内容查看。

3. 事件循环

一句话结论
整个进程只有一个事件循环(QCoreApplication::exec())在跑;
主窗口、子窗口、对话框、定时器、网络、线程……都只是把事件投递到同一个循环;
所谓“局部事件循环”只是递归地再开一个 QEventLoop,它仍然共享同一个事件分发器,而不是第二套循环。


真正的“事件循环”长什么样

int main(int argc, char *argv[])
{QApplication app(argc, argv);MainWindow w; w.show();return app.exec();          // ← 这里开始唯一的事件循环
}

app.exec() 内部简化伪代码:

while (!exit)if (!postedEvents.empty())processPostedEvents();      // 先处理 sendPostedEvents 队列if (!windowEvents.empty())processWindowSystemEvents(); // 处理 OS 事件if (canWait)                    // 没有事件就阻塞waitForMoreEvents();        // select/epoll/GetMessage

所有 QWidget、QObject 都只是事件的接收者,而不是再开一条循环。


  1. 子窗口 / 对话框的“局部循环”是什么
    QDialog::exec()、QMenu::exec() 会临时创建一个嵌套的 QEventLoop:
int QDialog::exec()
{QEventLoop loop;connect(this, &QDialog::finished, &loop, &QEventLoop::quit);show();return loop.exec();   // 递归进入
}

这个 loop 仍然调用 同一个 QCoreApplication::notify() 来分发事件;
它只是让 当前函数卡在这里,外层循环暂时“挂起”;当 loop.quit() 时,栈回到原来的 app.exec(),继续跑。
示意图:

app.exec()  ——→  事件A → MainWindow↑│ 局部loop.exec()  ← 事件B → Dialog│            ↑└------------

  1. 线程才拥有独立事件循环
    每个 QThread 可以有自己的 QEventLoop(通过 QThread::exec() 启动)。
    GUI 相关对象必须在主线程里,因此主窗口和子窗口永远共享主线程的唯一循环;
    子线程的事件循环用来跑信号槽、定时器、网络 IO 等,与 GUI 循环互不干扰。

  1. 快速判断“几个循环?”
场景事件循环个数备注
普通主窗口 + 若干子窗口1全部在主线程
弹出 QDialog::exec()1(嵌套)临时第二层 loop,返回后只剩顶层
新建 QThreadmoveToObject2主线程 + 子线程各一个
QApplication::processEvents()0只是手动触发一次分发,不新建循环

一句话再总结
主窗口、子窗口、对话框共用同一个全局事件循环;所谓“对话框阻塞”只是递归嵌套地再跑一遍这个循环,事件仍由 QApplication 统一分发。

http://www.dtcms.com/a/316551.html

相关文章:

  • 嵌入式软件架构设计之七:双机通信及通信协议之字符串协议
  • 大语言模型安全攻防:从提示词注入到模型窃取的全面防御浅谈
  • 与功能包相关的指令ros2 pkg
  • 女性成长赛道:现状与发展趋势|创客匠人
  • NumPy 中的取整函数
  • 如何在Android设备上删除多个联系人(3种方法)
  • Java项目:基于SSM框架实现的公益网站管理系统【ssm+B/S架构+源码+数据库+毕业论文+答辩PPT+远程部署】
  • 解锁高效敏捷:2025年Scrum项目管理工具的核心应用解析
  • 智慧社区物业管理平台登录流程全解析:从验证码到JWT认证
  • 关于熵减 - 双线线圈
  • 前端性能测试:从工具到实战全解析
  • 类内部方法调用,自注入避免AOP失效
  • Flutter 国际化
  • OpenSpeedy绿色免费版下载,提升下载速度,网盘下载速度等游戏变速工具
  • spring boot 加载失败 异常没有曝漏出来
  • 基于Java AI(人工智能)生成末日题材的实践
  • 2. JS 有哪些数据类型
  • 【网络运维】Linux:系统启动原理与配置
  • 虚幻GAS底层原理解剖一(开篇)
  • ⭐CVPR2025 用于个性化图像生成的 TFCustom 框架
  • python可视化--Seaborn图形绘制方法和技巧,Bokeh图形绘制方法和技巧
  • 虚幻GAS底层原理解剖二 (GE)
  • 安全策略一体化落地指南:从定义到执行的闭环架构
  • 自适应爬虫代理高频数据抓取
  • 数据大集网:全链路赋能下的获客渠道革新与行业实践
  • 王阳明心学笔记
  • 【软考中级网络工程师】2021年下半年上午真题及答案解析
  • C++进阶—特殊类设计
  • Java面试宝典:深入解析JVM运行时数据区
  • ArrayList 深度剖析:从底层原理到性能优化的实战指南