Qt事件循环队列剖析!!!

目录

[一、事件(Event)vs 信号(Signal)](#一、事件(Event)vs 信号(Signal))

[1. 事件(QEvent)](#1. 事件(QEvent))

[2. 信号(Signal)](#2. 信号(Signal))

[二、Qt 事件循环是干嘛的?](#二、Qt 事件循环是干嘛的?)

三、一次"鼠标点击"大概流程

[四、怎么"拦截 / 过滤"事件?](#四、怎么“拦截 / 过滤”事件?)

方法一:重写控件自己的事件函数(简单直接)

[方法二:事件过滤器(Event Filter)(更灵活)](#方法二:事件过滤器(Event Filter)(更灵活))

五、把面试要点压缩成几句话


✅ 槽函数本身不是事件,鼠标点击这种才是事件(QEvent)。

✅ 事件由事件循环从队列里取出,发给控件处理;控件在事件处理函数里可能 emit 信号。

✅ 信号触发槽函数时:

  • 同线程 + AutoConnection:直接调用槽函数;

  • 跨线程 + Auto/QueuedConnection:把"调用槽函数"的请求丢到接收者线程的事件队列里,由那个线程执行槽。

一、事件(Event)vs 信号(Signal)

先记一句话:

事件是"系统给对象发消息",信号是"对象给别人发通知"。

1. 事件(QEvent)

  • 来源:操作系统 / Qt 内部

    比如:鼠标移动、按键、窗口大小改变......

  • 走向:发给某个具体对象(一个控件、一个窗口)。

  • 处理方式:覆盖虚函数:

    复制代码
    bool MyWidget::event(QEvent *e) override;        // 总入口
    void MyWidget::mousePressEvent(QMouseEvent *e);  // 专门处理鼠标按下

2. 信号(Signal)

  • 来源:对象自己发

    比如:按钮检测到点击后,发 clicked()。

  • 走向:谁连我我就通知谁(广播)。

  • 处理方式:connect 到槽函数:

    复制代码
    connect(button, &QPushButton::clicked,
            this,   &MyWindow::onButtonClicked);

可以这样类比:

  • 事件:快递员把快递送到你家门口(发给"你"这个对象)。

  • 信号:你在小区群里发一条消息"我到家啦"(谁关心就监听)。


二、Qt 事件循环是干嘛的?

Qt 程序本质上就是一个大循环:不停地拿事件、分发事件。

伪代码大概是:

复制代码
int main() {
    QApplication app(argc, argv);

    MyWindow w;
    w.show();

    return app.exec();  // 事件循环在这里转圈
}

app.exec() 做的事可以理解为:

复制代码
while (程序没退出) {
    // 1. 从操作系统拿消息(鼠标、键盘等)
    // 2. 转成对应的 QEvent(QMouseEvent、QKeyEvent......)
    // 3. 找到应该接收的那个控件
    // 4. 调用 QApplication::notify() 把事件发给它
}

三、一次"鼠标点击"大概流程

以你点一下按钮为例,简化成几步:

  1. 操作系统:窗口系统发现你在某个坐标点按下鼠标 → 产生原始消息。

  2. Qt 平台插件 :把系统消息转成一个 QMouseEvent。

  3. 事件循环 (app.exec()):从队列里取出这个 QMouseEvent。

  4. QApplication::notify() :

    找到那个被点中的控件(比如 QPushButton),准备把事件发给它。

  5. 事件过滤器(可选):

    • 如果别的对象给这个按钮装了 eventFilter,会先问一嘴:

      "这个事件你要拦下来吗?"

    • eventFilter() 返回 true → 事件就到此为止,不再往下走。

    • 返回 false → 继续发给按钮本身。

  6. 控件接收事件:

    • button->event(e) 被调用;

    • event() 里发现这是鼠标按下,就继续调用 mousePressEvent(e);

    • QPushButton::mousePressEvent() 里判断:按下 + 抬起 → 触发 clicked() 信号。

  7. 信号发出:

    • button 发出 clicked();

    • 所有 connect 过的槽函数被依次调用,比如 onButtonClicked()。


四、怎么"拦截 / 过滤"事件?

你想在事件"正常处理前"动手脚,有两个常见方式:

方法一:重写控件自己的事件函数(简单直接)

复制代码
class MyButton : public QPushButton {
protected:
    void mousePressEvent(QMouseEvent *e) override {
        // 这里先做你自己的事
        // ...

        // 继续让父类处理(保持原有按钮行为)
        QPushButton::mousePressEvent(e);
    }
};

适合你能改这个控件的类的时候。


方法二:事件过滤器(Event Filter)(更灵活)

适合"我不想改控件的类,但想听它的所有事件"。

  1. 写一个过滤器类,重写 eventFilter:

    复制代码
    class MyFilter : public QObject {
    protected:
        bool eventFilter(QObject *watched, QEvent *event) override {
            if (watched == targetButton && event->type() == QEvent::KeyPress) {
                // 拦截:不让这个按钮响应键盘
                return true;   // true = 我已经处理,不要再往下发了
            }
            return false;      // false = 放行,照常处理
        }
    };
  2. 在某处安装到目标对象上:

    复制代码
    MyFilter *filter = new MyFilter(this);
    targetButton->installEventFilter(filter);

记住一句话:

eventFilter 返回 true = 拦截;返回 false = 放行。


五、把面试要点压缩成几句话

你可以最后这样记:

  1. 事件 :系统 → 对象,event()/xxxEvent() 处理;
    信号 :对象 → 其他对象,slot 处理。

  2. 程序核心是 app.exec() 事件循环:拿系统消息 → 转 QEvent → notify() 分发。

  3. 一次鼠标点击:系统消息 → QMouseEvent → eventFilter(可选) → event() → mousePressEvent() →(可能)发 clicked() 信号。

  4. 想拦截事件:

    • 能改控件:重写 event() 或 mousePressEvent() 等;

    • 改不了控件:写 eventFilter + installEventFilter(),true 拦截,false 放行。

如果你愿意,我下一步可以帮你把这些内容浓缩成一页"小抄",专门给你背八股用 😄

你这句话大体是对的,只是可以再精细一点点 👍

先回答你的问题:

事件循环是不是就是事件队列循环,队列就是先进先出?

可以这么理解:

  • Qt 里确实有一个事件队列;

  • app.exec() 就是在不停地:

    • 从队列里取出一个事件

    • 分发给对应对象处理

  • 队列大致上可以按 FIFO 理解,虽然内部还有优先级之类的细节,但入门可以不用管。


再看你说的流程:

点击按钮,触发这一流程

系统 → 事件 → 通知 → 按钮事件 → 发送信号 → 处理槽函数

把 Qt 版"官话"稍微补全一下就是:

  1. 系统:鼠标点击消息 →

  2. Qt 平台层 :转成 QMouseEvent,丢进 Qt 的事件队列 →

  3. 事件循环 :app.exec() 从队列里取出这个 QMouseEvent →

  4. notify :QApplication::notify() 把事件发给对应的按钮对象

    • (期间可能先经过 eventFilter 过滤器)
  5. 按钮事件处理 :按钮的 event() / mousePressEvent() 里处理这个点击

  6. 按钮内部判断为"真正点了一下" → emit clicked() 发出信号

  7. 槽函数 :所有 connect 了 clicked() 的槽函数被调用


再补一个小但重要的点:

  • 默认情况下(在同一线程,Qt::AutoConnection),
    信号发出后,槽函数是立刻在当前调用栈里执行的 ,

    而不是再丢回事件队列里排队(除非你用的是 Qt::QueuedConnection)。

所以可以用一句"口语版总结":

Qt:事件循环负责从系统拿事儿 → 找到控件 → 调控件的事件函数 ;

控件:在事件函数里觉得该通知别人了,就发信号 → 调槽函数。

相关推荐
程序猿乐锅6 小时前
从0-1一文详解RabbitMQ
java·分布式·后端·中间件·rabbitmq·ruby
考虑考虑9 小时前
JDK26中的List.ofLazy()
java·后端·java ee
小蒜学长9 小时前
基于SpringBoot的公寓报修管理系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·公寓报修管理系统·多角色协同
for_ever_love__9 小时前
MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离
java·数据库·mysql·binlog·读写分离·原理·主从复制
小π军12 小时前
操作系统面试题
java·linux·服务器
倔强的石头10612 小时前
应用账号最小权限实践:读写账号、报表账号、运维账号分层
java·运维·开发语言
Shaoshing12 小时前
ThreadLocal
java·jvm·threadlocal
写后端的胖头鱼13 小时前
一文详解CAS
java·jvm·spring·cas·乐观锁·自旋
鱼宵13 小时前
Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用
java·人工智能·spring·few-shot·提示词工程·springai