🔥个人主页: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) |
|---|---|---|
层级 | 高层通信机制(基于元对象系统) | 底层交互与系统消息传递机制 |
触发源 | 通常由对象主动发出(如按钮被点击发出 | 由操作系统或 Qt 框架分发(如操作系统捕获到硬件输入) |
处理方式 | 通过 | 通过重写虚函数(如 |
关系 | 许多信号本质上是在事件处理函数中被触发的(例如:在 | 事件是信号的底层支撑 |
二. 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。
实战示例1:QTimerEvent 实战(基础定时,事件)
// 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); }实战示例2:QTimer 实战(信号槽机制)
// 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()可以优雅地在外部对控件的事件进行拦截与定制。