ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Qt 事件机制详解:从 QEvent 派生类到事件过滤器的全景指南

Qt 事件机制详解:从 QEvent 派生类到事件过滤器的全景指南 个人主页Cx330❄️个人专栏《C语言》《LeetCode刷题集》《数据结构-初阶》《C知识分享》《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔《Git深度解析》:版本管理实战全解 《Qt 极境架构》MySQL 核心技术与实战心向往之行必能Cx330的简介目录前言一. 信号槽 vs 事件机制它们有什么区别二. Qt 事件体系结构 (QEvent 继承树)三. 常用事件处理与实战案例3.1 鼠标事件与鼠标追踪 (Mouse Events)3.2 键盘事件 (Keyboard Events)3.3 定时器事件 (Timer Events)3.4 窗口事件移动与大小改变补充重点四. 事件分发器事件处理的中间层五. 事件过滤器全局事件拦截总结前言在 Qt 开发中很多初学者常常混淆信号槽Signals Slots和事件Events。实际上事件是 Qt 框架更底层的通信与响应机制。无论是鼠标点击、键盘输入、窗口重绘还是定时器触发其本质都是 Qt 事件循环分发的QEvent对象。本文将梳理 Qt 事件处理的核心概念、常见事件类型及其实战应用并深入剖析事件传递与过滤器的全流程。一. 信号槽 vs 事件机制它们有什么区别维度信号与槽 (Signals Slots)事件机制 (Event System)层级高层通信机制基于元对象系统底层交互与系统消息传递机制触发源通常由对象主动发出如按钮被点击发出clicked()由操作系统或 Qt 框架分发如操作系统捕获到硬件输入处理方式通过connect()将信号绑定到槽函数通过重写虚函数如mousePressEvent或事件过滤器处理关系许多信号本质上是在事件处理函数中被触发的例如在mouseReleaseEvent中发出clicked信号事件是信号的底层支撑二. Qt 事件体系结构 (QEvent 继承树)Qt 中所有的事件类都继承自QEvent抽象基类。当事件发生时Qt 会创建一个具体的QEvent派生类对象并分发给对应的QObject实例。┌─────────────┐ │ QEvent │ └──────┬──────┘ │ ┌──────────────┬───────┴───────┬──────────────┬──────────────┐ │ │ │ │ │ ┌───┴──────────┐ ┌─┴────────────┐ ┌┴────────────┐ ┌┴────────────┐ ┌┴────────────┐ │ QInputEvent │ │ QTimerEvent │ │ QResizeEvent│ │ QPaintEvent │ │ QCloseEvent │ └───┬──────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ ┌──┴───────────────┬────────────────┐ │ │ │ ┌┴─────────────┐ ┌──┴───────────┐ ┌──┴────────────┐ │ QMouseEvent │ │ QKeyEvent │ │ QWheelEvent │ └──────────────┘ └──────────────┘ └──────────────┘常见的QEvent派生类及其用途QMouseEvent鼠标按键、移动、双击等事件。QKeyEvent键盘按按下与释放事件。QWheelEvent鼠标滚轮滚动事件。QTimerEvent定时器超时事件。QResizeEvent控件/窗口尺寸改变事件。QPaintEvent控件需要重绘的事件。三. 常用事件处理与实战案例3.1 鼠标事件与鼠标追踪 (Mouse Events)鼠标事件涵盖点击、释放、双击、移动、滚轮等操作核心通过QMouseEvent类获取事件详情如点击位置、按键类型。核心事件函数mousePressEvent(QMouseEvent *event)鼠标按下时触发mouseReleaseEvent(QMouseEvent *event)鼠标释放时触发mouseDoubleClickEvent(QMouseEvent *event)鼠标双击时触发mouseMoveEvent(QMouseEvent *event)鼠标移动时触发需开启setMouseTracking(true)wheelEvent(QWheelEvent *event)鼠标滚轮滚动时触发。实战示例1鼠标进入和鼠标离开// label.h #ifndef LABEL_H #define LABEL_H #include QWidget #include QLabel class Label : public QLabel { Q_OBJECT public: Label(QWidget* parent); void enterEvent(QEvent* event); void leaveEvent(QEvent* event); }; #endif // LABEL_H // label.cpp #include label.h #include QDebug Label::Label(QWidget* parent) : QLabel(parent) { } void Label::enterEvent(QEvent *event) { (void) event; qDebug() enterEvent; } void Label::leaveEvent(QEvent *event) { (void) event; qDebug() leaveEvent; }实战示例2鼠标按下释放和双击事件// label.h #ifndef LABEL_H #define LABEL_H #include QWidget #include QLabel class Label : public QLabel { Q_OBJECT public: Label(QWidget* parent); void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); void mouseDoubleClickEvent(QMouseEvent *ev); }; #endif // LABEL_H // label.cpp #include label.h #include QDebug #include QMouseEvent Label::Label(QWidget* parent) :QLabel(parent) { } void Label::mousePressEvent(QMouseEvent *ev) { if(ev-button() Qt::LeftButton){ qDebug() 按下左键; }else if(ev-button() Qt::RightButton){ qDebug() 按下右键; } // 当前 ev 对象就包含了鼠标点击位置的坐标 qDebug() ev-x() , ev-y(); // 相对于Label qDebug() ev-globalX() , ev-globalY(); // 相对于屏幕左上角 } void Label::mouseReleaseEvent(QMouseEvent *ev) { if(ev-button() Qt::LeftButton){ qDebug() 释放左键; }else if(ev-button() Qt::RightButton){ qDebug() 释放右键; } } void Label::mouseDoubleClickEvent(QMouseEvent *ev) { if(ev-button() Qt::LeftButton){ qDebug() 双击左键; }else if(ev-button() Qt::RightButton){ qDebug() 双击右键; } }实战示例3鼠标移动事件// mianwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow #include QMouseEvent QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); void mouseMoveEvent(QMouseEvent* ev); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H // mianwindow.cpp #include mainwindow.h #include ui_mainwindow.h #include QDebug MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui-setupUi(this); this-setMouseTracking(true); // 设置为 true 才能追踪鼠标移动 } MainWindow::~MainWindow() { delete ui; } void MainWindow::mouseMoveEvent(QMouseEvent *ev) { qDebug() ev-x() , ev-y(); }实战示例4鼠标滚轮事件#include widget.h #include ui_widget.h #include QDebug #include QWheelEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); total 0; } Widget::~Widget() { delete ui; } void Widget::wheelEvent(QWheelEvent *event) { total event-delta(); qDebug()total; }3.2 键盘事件 (Keyboard Events)通过重写keyPressEvent可以捕获按键按下动作支持结合键Ctrl, Shift, Alt的判断。核心事件函数keyPressEvent(QKeyEvent *event)按键按下时触发keyReleaseEvent(QKeyEvent *event)按键释放时触发。实战示例单个按键 组合键捕获// widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); void keyPressEvent(QKeyEvent *event); private: Ui::Widget *ui; }; #endif // WIDGET_H // widget.cpp #include widget.h #include ui_widget.h #include QDebug #include QKeyEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); } Widget::~Widget() { delete ui; } void Widget::keyPressEvent(QKeyEvent *event) { // qDebug() event-key(); if(event-key() Qt::Key_A event-modifiers() Qt::ControlModifier) { qDebug() 按下了 ctrl A 键; } }3.3 定时器事件 (Timer Events)在 Qt 中处理定时任务有两种常见方式使用QTimer类基于信号槽或直接重写timerEvent。实战示例1QTimerEvent 实战基础定时事件// widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); void timerEvent(QTimerEvent* event); private: Ui::Widget *ui; int timeId; }; #endif // WIDGET_H // widget.cpp #include widget.h #include ui_widget.h Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); // 开启定时器事件 // 此处的 timeId 是一个定时器的身份标识 timeId this-startTimer(1000); } Widget::~Widget() { delete ui; } void Widget::timerEvent(QTimerEvent *event) { // 如果一个程序中存在多个定时器 (startTimer 创建的定时器), 此时每个定时器都会触发 timeEvent 函数 // 先判断一下这次触发的是否是想要的定时器触发的 if(event-timerId() ! this-timeId) { // 如果不是我们的定时器触发的,就直接忽略 return; } // 是我们自己搞的定时器 int value ui-lcdNumber-intValue(); if(value 0) { // 停止定时器 this-killTimer(this-timeId); return; } value - 1; ui-lcdNumber-display(value); }实战示例2QTimer 实战信号槽机制// widget.cpp #include widget.h #include ui_widget.h #include QTimer Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); QTimer *timer new QTimer(this); timer-setInterval(1000); // 1秒间隔 // 连接定时器触发信号 connect(timer, QTimer::timeout, this, []() { static int num 0; qDebug() QTimer 触发 num; }); // 开始定时器 ui-pushButton_start-clicked([]() { timer-start(); }); // 停止定时器 ui-pushButton_stop-clicked([]() { timer-stop(); }); }3.4 窗口事件移动与大小改变补充重点窗口移动、大小改变是常用系统事件需重写moveEvent和resizeEvent函数适用于窗口状态监控、自适应布局等场景。核心事件函数moveEvent(QMoveEvent *event)窗口移动时触发获取新位置resizeEvent(QResizeEvent *event)窗口大小改变时触发获取新尺寸。实战示例窗口移动 大小监控// widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); void moveEvent(QMoveEvent* event); void resizeEvent(QResizeEvent* event); private: Ui::Widget *ui; }; #endif // WIDGET_H // widget.cpp #include widget.h #include ui_widget.h #include QDebug #include QMoveEvent #include QResizeEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); } Widget::~Widget() { delete ui; } void Widget::moveEvent(QMoveEvent *event) { qDebug() event-pos(); } void Widget::resizeEvent(QResizeEvent *event) { qDebug() event-size(); }四. 事件分发器事件处理的中间层事件分发器event(QEvent *event)是 Qt 事件处理的核心枢纽所有事件都会先经过该函数再分发到对应的事件处理函数如mousePressEvent。通过重写event函数可实现事件拦截、自定义分发逻辑。核心原理事件触发后Qt 会创建QEvent对象调用控件的event函数event函数根据事件类型event-type()调用对应的事件处理函数若event函数返回true表示事件被拦截不再向下分发返回false则继续分发。实战示例拦截鼠标按下事件// widget.h bool event(QEvent *event) override; // widget.cpp bool Widget::event(QEvent *event) { // 拦截鼠标按下事件 if (event-type() QEvent::MouseButtonPress) { qDebug() 事件分发器拦截鼠标按下事件; return true; // 拦截不调用 mousePressEvent } // 其他事件交给父类处理默认分发 return QWidget::event(event); }五. 事件过滤器全局事件拦截事件过滤器是比事件分发器更灵活的事件拦截机制允许一个对象监控另一个对象的所有事件无需继承目标对象适用于全局事件监控如拦截多个控件的事件。使用步骤给目标对象安装事件过滤器target-installEventFilter(filterObj)在过滤器对象中重写eventFilter(QObject *obj, QEvent *event)函数过滤器函数返回true表示拦截事件false表示继续分发。实战示例拦截标签的鼠标事件// widget.h bool eventFilter(QObject *obj, QEvent *event) override; // widget.cpp Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui-setupUi(this); // 给 label 安装事件过滤器this 为过滤器对象 ui-label-installEventFilter(this); } bool Widget::eventFilter(QObject *obj, QEvent *event) { // 仅拦截 label 的事件 if (obj ui-label) { if (event-type() QEvent::MouseButtonPress) { qDebug() 事件过滤器拦截 label 鼠标按下; return true; // 拦截事件 } else if (event-type() QEvent::MouseMove) { qDebug() 事件过滤器监控 label 鼠标移动; // 不拦截继续分发 return false; } } // 其他对象的事件交给父类处理 return QWidget::eventFilter(obj, event); }总结信号槽与事件事件是底层的消息分发系统/GUI驱动信号槽常建构于事件处理之上。事件处理核心根据需要重写QWidget对应的xxxEvent虚函数。鼠标追踪记住setMouseTracking(true)是捕获无需按下的移动事件的关键。灵活拦截通过installEventFilter()可以优雅地在外部对控件的事件进行拦截与定制。
返回列表