编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

    • Linux应用开发
    • QML基础入门

    • QT核心库实践

      • QT核心库实践
      • 核心功能基础
      • 并发与多线程
      • 文件与IO系统
      • 日期与时间处理
      • 网络与序列化
      • QT事件系统
        • 1. 案例引入
          • 1.1 一个"卡死"的按钮
        • 2. 事件系统概述
          • 2.1 Qt 事件系统架构
          • 2.2 核心类与职责
          • 2.3 事件 vs 信号槽:一张图说清区别
        • 3. 事件循环原理
          • 3.1 事件循环的工作流程
          • 3.2 事件循环的伪代码实现
          • 3.3 多级优先级队列
          • 3.4 平台对接:Qt 如何利用 epoll
        • 4. 事件处理的三层结构
          • 4.1 层一:事件处理函数(最精细)
          • 4.2 层二:event() 函数(通用入口)
          • 4.3 层三:事件过滤器(全局拦截)
          • 4.4 事件传播路径总结
        • 5. sendEvent vs postEvent
          • 5.1 两种投递方式的根本区别
          • 5.2 对比表
          • 5.3 实战中的选型
        • 6. 自定义事件
          • 6.1 定义自定义事件类型
          • 6.2 发送与接收自定义事件
          • 6.3 实战案例:CAN 总线数据到 UI 的事件驱动
        • 7. QEvent 类型速查
          • 7.1 常见事件类一览
        • 8. 事件循环的常见陷阱与调试
          • 8.1 阻塞事件循环的常见方式
          • 8.2 嵌套事件循环
          • 8.3 调试技巧
        • 9. 综合案例:嵌入式设备的事件驱动架构
        • 10. 速查表
      • 信号与槽机制
      • 多媒体的应用
      • 容器类和算法
      • 高级编程技巧
  • IoT智能硬件开发

  • Apps
  • Linux应用开发
  • QT核心库实践
杨充
2025-08-21
目录

QT事件系统

# 06.QT事件系统

Qt 的事件循环是如何"驱动"整个应用程序的?理解事件的产生、分发、处理与过滤机制,才能真正掌控 Qt 的运行脉搏。

# 1. 案例引入

# 1.1 一个"卡死"的按钮

想象一个场景:你正在开发一个工业 HMI 触摸屏,需要从串口读取传感器数据并更新 UI。

void MainWindow::onRefreshButtonClicked() {
    // 按钮点击后,直接读取串口(阻塞操作)
    QSerialPort serial;
    serial.setPortName("/dev/ttyS1");
    serial.open(QIODevice::ReadOnly);
    serial.waitForReadyRead(5000);   // 阻塞等待 5 秒!
    QByteArray data = serial.readAll();
    updateUI(data);
}

现象:点击刷新按钮后,整个界面"冻住" 5 秒——所有按钮点不动、动画停止、窗口无法拖动。

根因是什么? 按钮的点击事件在当前的事件循环迭代中被处理,但 waitForReadyRead() 阻塞了当前线程——事件循环被"卡住",后续的鼠标事件、重绘事件全部被堵在队列中。

解决方案:

  1. 用异步方式读取串口(readyRead 信号)
  2. 或将串口操作移到工作线程
void MainWindow::onRefreshButtonClicked() {
    // ✅ 异步方式:连接信号,不阻塞事件循环
    connect(&serial, &QSerialPort::readyRead, this, [this]() {
        QByteArray data = serial.readAll();
        updateUI(data);
    });
}

本文要回答的核心问题:Qt 的事件循环是如何工作的?事件从产生到最终被处理经历了哪些环节?如何避免阻塞事件循环?


# 2. 事件系统概述

# 2.1 Qt 事件系统架构

Qt 的事件系统是典型的事件驱动模型,其核心是事件循环(Event Loop)。所有用户输入、系统通知、定时器触发、网络数据到达,都被封装为 QEvent 对象,通过事件循环统一分发和处理。

┌─────────────────────────────────────────────────────┐
│                    事件生产者                          │
│  操作系统 · 硬件 · Qt内部 · 用户代码(QCoreApplication::postEvent) │
└────────────────────┬────────────────────────────────┘
                     │ QEvent 对象
                     ▼
┌─────────────────────────────────────────────────────┐
│                Qt 事件循环 (QEventLoop)                │
│  ┌───────────────────────────────────────────────┐  │
│  │  事件队列(多级优先级)                         │  │
│  │  High Priority → Normal → Low → DeferredDelete │  │
│  └───────────────┬───────────────────────────────┘  │
│                  │ dispatch                          │
│                  ▼                                    │
│  ┌───────────────────────────────────────────────┐  │
│  │  目标 QObject → event(QEvent*) → 具体处理函数   │  │
│  │  例如:QPushButton → mousePressEvent()          │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

# 2.2 核心类与职责

核心类 职责
QEvent 所有事件的基类。封装事件类型(QEvent::Type)和事件数据
QObject 事件处理的基础类。提供 event() 虚函数和 installEventFilter()
QCoreApplication 管理主事件循环。提供 exec() 启动和 postEvent() 发送
QEventLoop 事件循环本身。可嵌套(QEventLoop::exec() 内再创建 QEventLoop)
QAbstractEventDispatcher 平台相关的事件分发器。对接操作系统的 epoll/kqueue/select

# 2.3 事件 vs 信号槽:一张图说清区别

事件(QEvent)                vs        信号槽(Signal/Slot)
───────────────────────────────────────────────────────────
由操作系统/Qt内部产生                    由开发者定义
通过 event() 虚函数处理                  通过 connect() 连接
调用 sendEvent/postEvent 投递            调用 emit 发出
机制层次:更底层(事件循环级别)           机制层次:建立在事件循环之上的高级抽象
一对一目标对象                          一对多连接(一个信号可连多个槽)
必须继承 QObject 并重写 event()          任何 QObject 子类都可以

信号槽与事件的关系:

信号发射 → 生成 QMetaCallEvent(内部事件)→ 放入事件队列
                                              ↓
                                         事件循环分发
                                              ↓
                                     QObject::event() 处理
                                              ↓
                                       执行连接的槽函数

# 3. 事件循环原理

# 3.1 事件循环的工作流程

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    // 创建窗口、初始化...
    return app.exec();  // ← 启动事件循环
}

app.exec() 内部是一个永不退出的循环(直到 quit() 被调用):

┌─────────────────────────────────────────────────────┐
│  事件循环 (Event Loop) 每次迭代                        │
│                                                       │
│  ① 检查事件队列 → 有事件?→ 取出事件                  │
│       ↓ 无事件                                        │
│  ② 进入休眠等待(epoll_wait / select)                │
│       ↓ OS 通知有事件                                 │
│  ③ 取出事件 → dispatch(QEvent*) → 目标对象.event()   │
│       ↓                                              │
│  ④ 处理后置任务(deferred delete、排队的信号槽调用)  │
│       ↓                                              │
│  ⑤ 返回 ①(循环)                                    │
└─────────────────────────────────────────────────────┘

# 3.2 事件循环的伪代码实现

// Qt 事件循环的简化伪代码
bool QEventLoop::processEvents() {
    while (!exitRequested) {
        // Step 1: 处理高优先级事件(重绘、定时器)
        while (hasHighPriorityEvents()) {
            QEvent *e = dequeueHighPriorityEvent();
            dispatchEvent(e);          // 分发到目标 QObject::event()
        }

        // Step 2: 处理普通事件
        if (hasEvents()) {
            QEvent *e = dequeueEvent();
            dispatchEvent(e);
            continue;                  // 返回检查高优先级事件
        }

        // Step 3: 处理延迟删除
        processDeferredDeletion();

        // Step 4: 处理空闲任务
        if (hasIdleTasks()) {
            processIdleTasks();
        }

        // Step 5: 进入高效等待
        // 使用 epoll/kqueue/select 等 OS 系统调用
        // 无事件时 CPU 使用率接近 0%
        if (noEventsPending) {
            waitForEvents(timeout);
        }
    }
    return true;
}

# 3.3 多级优先级队列

Qt 内部并非单一队列——而是按优先级分了多个队列:

┌─────────────────────────────────┐
│  高优先级队列                    │  ← 定时器事件、重绘事件(优先保证 UI 流畅)
├─────────────────────────────────┤
│  普通事件队列                    │  ← 用户输入(鼠标/键盘/触摸)
├─────────────────────────────────┤
│  低优先级队列                    │  ← 空闲任务(QTimer::singleShot(0, ...))
├─────────────────────────────────┤
│  延迟删除队列                    │  ← deleteLater() 的 QObject
└─────────────────────────────────┘

每次循环先处理高优先级队列,确保重绘和定时器不被普通事件阻塞。

# 3.4 平台对接:Qt 如何利用 epoll

在 Linux 上,Qt 的事件分发器底层使用 epoll:

// Qt 内部(简化)—— QEventDispatcherUNIX
int epollFd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event ev;
ev.events = EPOLLIN;  // 可读事件
ev.data.fd = socketFd;  // 监听 socket 描述符
epoll_ctl(epollFd, EPOLL_CTL_ADD, socketFd, &ev);

// 等待事件(阻塞,直到有事件或超时)
int nfds = epoll_wait(epollFd, events, MAX_EVENTS, timeout);
for (int i = 0; i < nfds; i++) {
    // socket 有数据可读 → 创建 QEvent → 投递到事件循环
    postEvent(targetObject, new QSocketNotifierEvent(...));
}

关键概念——QSocketNotifier:允许 Qt 监视文件描述符(socket/串口/inotify),当 fd 可读/可写/异常时,Qt 自动投递事件。


# 4. 事件处理的三层结构

# 4.1 层一:事件处理函数(最精细)

class MyWidget : public QWidget {
protected:
    // 重写具体的事件处理函数(最常见的方式)
    void mousePressEvent(QMouseEvent *event) override {
        qDebug() << "鼠标点击位置:" << event->pos();
        qDebug() << "哪个按钮:" << event->button();
        // 调用父类实现(确保默认行为)
        QWidget::mousePressEvent(event);
    }

    void keyPressEvent(QKeyEvent *event) override {
        if (event->key() == Qt::Key_Escape) {
            close();     // ESC 键关闭窗口
        }
        QWidget::keyPressEvent(event);
    }
};

# 4.2 层二:event() 函数(通用入口)

class MyWidget : public QWidget {
protected:
    // event() 是所有事件的"总入口"
    // 在这里可以拦截任何类型的事件
    bool event(QEvent *event) override {
        // 拦截所有鼠标按下事件,记录日志
        if (event->type() == QEvent::MouseButtonPress) {
            qDebug() << "检测到鼠标按下事件";
            // 可以在这里"吃掉"事件(return true),阻止后续处理
        }
        // 其他事件交给基类的 event(),它会调用具体的事件处理函数
        // 如基类的 event() 内部:if (type == MouseButtonPress) mousePressEvent(...)
        return QWidget::event(event);
    }
};

# 4.3 层三:事件过滤器(全局拦截)

事件过滤器可以在不修改目标类源码的情况下拦截事件:

class KeyPressFilter : public QObject {
protected:
    bool eventFilter(QObject *watched, QEvent *event) override {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
            qDebug() << "过滤到按键:" << keyEvent->text()
                     << "目标对象:" << watched->objectName();
            // return true → 事件被"吃掉",不再传递给 watched 对象
            // return false → 继续正常传播
        }
        return QObject::eventFilter(watched, event);
    }
};

// 使用
KeyPressFilter *filter = new KeyPressFilter(this);
lineEdit->installEventFilter(filter);  // 监视 lineEdit 的所有事件
button->installEventFilter(filter);    // 也可以监视其他对象

# 4.4 事件传播路径总结

操作系统产生事件(如鼠标点击)
    ↓
QAbstractEventDispatcher 接收(epoll → socket notifier → QEvent)
    ↓
QEventLoop → dispatch(QEvent*)
    ↓
目标 QObject → eventFilter() 链(如果安装了事件过滤器)
    ├── 某过滤器返回 true → 事件被拦截,停止传播
    └── 所有过滤器返回 false → 继续
         ↓
目标 QObject::event(QEvent*)  ← 通用入口
    ↓
具体的事件处理函数(如 mousePressEvent)
    ↓
如未处理 → 事件向父对象传播(QWidget 特有)

# 5. sendEvent vs postEvent

# 5.1 两种投递方式的根本区别

// 方式一:sendEvent——同步投递(立即处理)
QMouseEvent event(QEvent::MouseButtonPress, pos, Qt::LeftButton, ...);
QApplication::sendEvent(targetWidget, &event);
// 这行代码执行完后,targetWidget 已经处理完了该事件
// sendEvent 是"函数调用"——和直接调用 targetWidget->mousePressEvent() 类似

// 方式二:postEvent——异步投递(放入队列,稍后处理)
QApplication::postEvent(targetWidget, new QMouseEvent(...));
// 事件被放入事件队列,函数立即返回
// 事件将在事件循环的下一次迭代中被处理

# 5.2 对比表

特性 sendEvent postEvent
执行方式 同步(立即) 异步(排入队列)
事件所有权 调用者管理 Qt 接管(处理完后自动 delete)
返回值 被处理的状态 队列是否满了
是否阻塞 阻塞直到处理完 不阻塞,立即返回
适合场景 需要立即知道结果 延迟处理/跨线程安全
性能 少一次队列操作 多一次入队+出队

# 5.3 实战中的选型

// 场景 1:模拟用户点击(同步)
// 需要立即知道点击后的结果
QTest::mouseClick(button, Qt::LeftButton);
// 等价于 sendEvent + 等待处理完成

// 场景 2:传感器数据更新(异步——推荐)
void SensorReader::onNewData(const QByteArray &data) {
    // 不在回调中直接更新 UI(可能在不同线程)
    QApplication::postEvent(mainWindow, new SensorDataEvent(data));
    // mainWindow 将在它自己的事件循环中安全处理
}

// 场景 3:自定义事件需要传递复杂数据(异步)
class DataReadyEvent : public QEvent {
public:
    static const QEvent::Type EventType = 
        static_cast<QEvent::Type>(QEvent::User + 1);
    
    DataReadyEvent(const QVariantMap &data)
        : QEvent(EventType), m_data(data) {}
    
    QVariantMap data() const { return m_data; }
private:
    QVariantMap m_data;
};

# 6. 自定义事件

# 6.1 定义自定义事件类型

// 自定义事件 ID——从 QEvent::User 开始(避免与 Qt 内置事件冲突)
enum MyEventType {
    DataReadyEvent  = QEvent::User + 1,
    DeviceEvent     = QEvent::User + 2,
    LogEvent        = QEvent::User + 3
};

class DataReadyEvent : public QEvent {
public:
    explicit DataReadyEvent(const QByteArray &data)
        : QEvent(static_cast<Type>(DataReadyEvent))
        , m_data(data) {}

    QByteArray data() const { return m_data; }

private:
    QByteArray m_data;
};

# 6.2 发送与接收自定义事件

// 发送方 ———— 硬件通信模块
class HardwareModule : public QObject {
    Q_OBJECT
public:
    void onRawData(const QByteArray &data) {
        // 异步发送自定义事件到 UI 线程
        QApplication::postEvent(uiReceiver, new DataReadyEvent(data));
    }
};

// 接收方 ———— UI 窗口
class MainWindow : public QMainWindow {
protected:
    void customEvent(QEvent *event) override {
        if (event->type() == DataReadyEvent) {
            auto *de = static_cast<DataReadyEvent *>(event);
            updateSensorDisplay(de->data());
        }
        QMainWindow::customEvent(event);
    }

private:
    void updateSensorDisplay(const QByteArray &data) {
        // 更新 UI...
    }
};

# 6.3 实战案例:CAN 总线数据到 UI 的事件驱动

// === 自定义 CAN 数据事件 ===
class CanDataEvent : public QEvent {
public:
    static const Type CanDataType = static_cast<Type>(QEvent::User + 100);

    CanDataEvent(uint32_t canId, const QByteArray &payload)
        : QEvent(CanDataType), m_canId(canId), m_payload(payload) {}

    uint32_t canId() const { return m_canId; }
    QByteArray payload() const { return m_payload; }

private:
    uint32_t m_canId;
    QByteArray m_payload;
};

// === CAN 读取线程 ===
class CanReader : public QThread {
    Q_OBJECT
public:
    void setReceiver(QObject *receiver) { m_receiver = receiver; }

protected:
    void run() override {
        while (!isInterruptionRequested()) {
            CanFrame frame = readCanBus();  // 阻塞读取 CAN
            // 异步投递事件到 UI 线程的 receiver
            QCoreApplication::postEvent(
                m_receiver, 
                new CanDataEvent(frame.id, frame.data)
            );
        }
    }
private:
    QObject *m_receiver = nullptr;
};

// === UI 窗口接收 ===
class DashboardWindow : public QMainWindow {
protected:
    void customEvent(QEvent *event) override {
        if (event->type() == CanDataEvent::CanDataType) {
            auto *ce = static_cast<CanDataEvent *>(event);
            // 根据 CAN ID 更新对应的仪表盘
            switch (ce->canId()) {
            case 0x100: updateSpeed(ce->payload()); break;
            case 0x101: updateRPM(ce->payload());   break;
            case 0x102: updateFuel(ce->payload());  break;
            }
        }
        QMainWindow::customEvent(event);
    }
};

# 7. QEvent 类型速查

# 7.1 常见事件类一览

事件类型 QEvent::Type 常量 触发时机 对应处理函数
鼠标按下 MouseButtonPress 鼠标按钮被按下 mousePressEvent()
鼠标释放 MouseButtonRelease 鼠标按钮被释放 mouseReleaseEvent()
鼠标移动 MouseMove 鼠标在 Widget 上移动 mouseMoveEvent()
键盘按下 KeyPress 键盘按键被按下 keyPressEvent()
键盘释放 KeyRelease 键盘按键被释放 keyReleaseEvent()
窗口重绘 Paint 需要重绘 Widget paintEvent()
窗口大小改变 Resize Widget 大小变化 resizeEvent()
窗口关闭 Close 窗口被请求关闭 closeEvent()
焦点获得/失去 FocusIn/FocusOut Widget 获得或失去焦点 focusInEvent()/focusOutEvent()
定时器 Timer QTimer 超时 timerEvent()
拖拽进入 DragEnter 拖拽数据进入 Widget dragEnterEvent()
拖拽释放 Drop 拖拽数据被放下 dropEvent()
鼠标滚轮 Wheel 鼠标滚轮滚动 wheelEvent()
自定义 User + N 用户自定义 customEvent()

# 8. 事件循环的常见陷阱与调试

# 8.1 阻塞事件循环的常见方式

// ❌ 陷阱 1:在事件处理函数中执行耗时操作
void MyWidget::onButtonClicked() {
    // 这会阻塞事件循环——整个 UI 冻结
    QThread::sleep(5);  // 绝对不要在主线程 sleep!
}

// ❌ 陷阱 2:waitForXxx 系列阻塞函数
void MyWidget::readSerialData() {
    serial.waitForReadyRead(5000);  // 阻塞事件循环
    // UI 会冻结 5 秒
}

// ✅ 解决:使用异步信号(readyRead)代替 waitForXxx
connect(&serial, &QSerialPort::readyRead, this, &MyWidget::onDataReady);

// ✅ 解决:将耗时操作移到 QThread 或 QtConcurrent
QtConcurrent::run([this]() {
    auto result = heavyComputation();
    QMetaObject::invokeMethod(this, [this, result]() {
        displayResult(result);  // 安全回到主线程
    }, Qt::QueuedConnection);
});

# 8.2 嵌套事件循环

void MyDialog::showAndWaitForResult() {
    QEventLoop loop;
    connect(yesButton, &QPushButton::clicked, &loop, [&loop]() {
        m_result = true;
        loop.quit();
    });
    connect(noButton, &QPushButton::clicked, &loop, [&loop]() {
        m_result = false;
        loop.quit();
    });

    loop.exec();  // ← 局部事件循环——会处理新的事件
    // 外部事件循环被"暂停"在这里,直到 loop.quit()
}

// ⚠️ 注意:嵌套事件循环允许重入!
// 如果用户在嵌套循环期间点击了其他按钮,可能导致意外行为

# 8.3 调试技巧

// 1. 安装 Qt 内置的事件过滤器(调试用)
#include <QAbstractEventDispatcher>

// 在 main 函数中安装全局事件过滤器
class DebugEventFilter : public QObject {
protected:
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event->type() == QEvent::MouseButtonPress) {
            qDebug() << "Mouse Press on:" << obj << obj->objectName();
        }
        return false;  // 不拦截,只记录
    }
};

// 2. 使用 GammaRay(Qt 官方调试工具)
// 可以实时查看事件流、对象树、信号槽连接

// 3. event() 中打印日志(仅在调试时)
bool MyWidget::event(QEvent *e) override {
    // 只打印你关心的事件类型
    if (e->type() == QEvent::Paint || e->type() == QEvent::Timer) {
        // 这些事件太频繁,不要打印
    } else {
        qDebug() << "Event:" << e->type() << "on" << objectName();
    }
    return QWidget::event(e);
}

# 9. 综合案例:嵌入式设备的事件驱动架构

下面是一个典型的嵌入式 Qt 应用的完整事件驱动设计:

┌──────────────────────────────────────────────────────┐
│                   嵌入式设备事件总线                   │
│                                                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐           │
│  │ CAN Reader│  │ 串口模块  │  │ 触摸屏   │           │
│  │ (线程)    │  │ (线程)    │  │ (驱动)   │           │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘           │
│       │ postEvent   │ postEvent   │ OS事件            │
│       ▼             ▼             ▼                   │
│  ┌────────────────────────────────────────┐          │
│  │           主线程事件循环                  │          │
│  │  QApplication::exec()                   │          │
│  └────────────────┬───────────────────────┘          │
│                   │ dispatch                          │
│       ┌───────────┼───────────┐                       │
│       ▼           ▼           ▼                       │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                 │
│  │仪表盘窗口│ │报警模块  │ │日志模块  │                 │
│  └─────────┘ └─────────┘ └─────────┘                 │
└──────────────────────────────────────────────────────┘
// 主窗口——事件驱动的嵌入式 HMI
class EmbeddedHMI : public QMainWindow {
    Q_OBJECT
public:
    EmbeddedHMI() {
        // 1. 连接信号槽(高级事件)
        connect(&canReader, &CanReader::dataReceived, this, &EmbeddedHMI::onData);
        connect(&serialWorker, &SerialWorker::dataReady, this, &EmbeddedHMI::onData);

        // 2. 安装事件过滤器(低级拦截)
        installEventFilter(this);
    }

protected:
    // 处理自定义事件(硬件数据到达)
    void customEvent(QEvent *event) override {
        if (event->type() == SensorEvent::SensorType) {
            auto *se = static_cast<SensorEvent *>(event);
            updateGauge(se->sensorId(), se->value());
        }
    }

    // 全局事件过滤
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event->type() == QEvent::KeyPress) {
            auto *ke = static_cast<QKeyEvent *>(event);
            if (ke->key() == Qt::Key_Escape) {
                // 退出应用前安全检查
                if (isInSafeMode()) {
                    QApplication::quit();
                    return true;
                }
            }
        }
        return QMainWindow::eventFilter(obj, event);
    }

private:
    CanReader canReader;
    SerialWorker serialWorker;
};

# 10. 速查表

概念 一句话解释
事件循环 app.exec() 中的永不停止的循环,等待-分发-处理事件
QEvent 所有事件的基类,封装事件类型和事件数据
sendEvent 同步投递,立即处理(不经过队列)
postEvent 异步投递,放入队列,下一轮循环处理
event() QObject 的总入口虚函数,所有事件必经之路
eventFilter() 全局拦截事件的机制,不修改源码即可过滤
customEvent() 处理自定义事件(QEvent::User + N)的虚函数
dispatch 事件循环将事件分发给目标对象的过程
epoll/kqueue Linux/macOS 的多路 I/O 复用,Qt 事件循环的底层
嵌套事件循环 QEventLoop 内嵌另一 QEventLoop(QDialog::exec 即此原理)

事件循环铁律:

  1. 不在主线程做任何阻塞操作(sleep/waitForXxx/while(1))
  2. 耗时操作移入工作线程,结果通过 postEvent 或信号槽返回
  3. 多线程投递 UI 事件必须用 postEvent(线程安全)
  4. sendEvent 跨线程使用可能产生未定义行为

下一篇:07.信号与槽机制 (opens new window)

上次更新: 2026/06/28, 17:55:19
网络与序列化
信号与槽机制

← 网络与序列化 信号与槽机制→

最近更新
01
科学方法实践论法
06-28
02
辩证思维矛盾论法
06-28
03
毛选中的调查观念
06-28
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式