
🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

文章目录
- 前言:
- [一. Qt 事件核心概念](#一. Qt 事件核心概念)
- [二. 常用事件实战(含窗口移动 / 大小事件)](#二. 常用事件实战(含窗口移动 / 大小事件))
-
- [2.1 鼠标事件:最常用的交互事件](#2.1 鼠标事件:最常用的交互事件)
- [2.2 键盘事件:捕获按键输入](#2.2 键盘事件:捕获按键输入)
- [2.3 定时器事件:周期性任务](#2.3 定时器事件:周期性任务)
- [2.4 窗口事件:移动与大小改变(补充重点)](#2.4 窗口事件:移动与大小改变(补充重点))
- [三. 事件分发器:事件处理的中间层](#三. 事件分发器:事件处理的中间层)
- [四. 事件过滤器:全局事件拦截](#四. 事件过滤器:全局事件拦截)
- [五. 事件系统避坑指南](#五. 事件系统避坑指南)
- 结尾:
前言:
Qt 的事件系统是 GUI 交互的核心,负责处理用户操作(鼠标、键盘)、系统通知(窗口大小改变、定时器触发)等所有动态行为。从鼠标点击、键盘输入,到窗口拖拽、定时任务,所有交互逻辑都依赖事件机制实现。本文系统拆解 Qt 事件的核心概念、常用事件(鼠标、键盘、定时器,窗口)、事件分发器、事件过滤器六大模块,通过可直接运行的代码示例,帮你彻底掌握 Qt 事件处理的底层逻辑与实战技巧。
一. Qt 事件核心概念
事件本质与分类 :
事件是 Qt 中对 "动作或状态变化" 的封装,所有事件均继承自抽象类 QEvent。常见事件分为两类:
- 用户交互事件:鼠标事件(点击、移动、滚轮)、键盘事件(按键按下 / 释放);
- 系统 / 状态事件:窗口事件(移动、大小改变、显示 / 隐藏)、定时器事件、进入 / 离开事件、绘屏事件。

事件处理核心方式 :
Qt 处理事件的核心是重写事件虚函数 ------QWidget 及其子类提供了大量预定义的事件虚函数(如 mousePressEvent、keyPressEvent),只需在自定义类中重写这些函数,即可捕获并处理对应事件。

关键前提:自定义控件提升
处理控件事件时,需先自定义类继承目标控件(如 QLabel、QWidget),再通过 Qt Designer 的 "提升为" 功能关联 UI 控件,步骤如下:
新建类(如 MyLabel),继承目标控件(如 QLabel);在自定义类中重写事件虚函数;在 UI 设计器中右键控件 → 提升为 → 输入自定义类名和头文件;编译运行,事件触发时将执行自定义类中的重写函数。
二. 常用事件实战(含窗口移动 / 大小事件)
2.1 鼠标事件:最常用的交互事件
鼠标事件涵盖点击、释放、双击、移动、滚轮等操作,核心通过 QMouseEvent 类获取事件详情(如点击位置、按键类型)。
核心事件函数:
mousePressEvent(QMouseEvent *event):鼠标按下时触发;mouseReleaseEvent(QMouseEvent *event):鼠标释放时触发;mouseDoubleClickEvent(QMouseEvent *event):鼠标双击时触发;mouseMoveEvent(QMouseEvent *event):鼠标移动时触发(需开启setMouseTracking(true));wheelEvent(QWheelEvent *event):鼠标滚轮滚动时触发。
实战示例1:鼠标进入和鼠标离开
cpp
// 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:鼠标按下,释放和双击事件
cpp
// 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:鼠标移动事件
cpp
// 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:鼠标滚轮事件
cpp
// 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 wheelEvent(QWheelEvent *event);
private:
Ui::Widget *ui;
int total;
};
#endif // WIDGET_H
// widget.cpp
#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;
}

2.2 键盘事件:捕获按键输入
键盘事件用于处理按键按下 / 释放,核心通过 QKeyEvent 类获取按键类型、组合键状态。
核心事件函数:
keyPressEvent(QKeyEvent *event):按键按下时触发;keyReleaseEvent(QKeyEvent *event):按键释放时触发。
实战示例:单个按键 + 组合键捕获
cpp
// 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 键";
}
}

2.3 定时器事件:周期性任务
Qt 提供两种定时器实现:QTimerEvent(轻量,基于事件循环)和 QTimer(高级,支持信号槽),适用于定时刷新、动画、周期性任务。
实战示例1:QTimerEvent 实战(基础定时,事件)
cpp
// 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 实战(信号槽机制)
cpp
// 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(); });
}
2.4 窗口事件:移动与大小改变(补充重点)
窗口移动、大小改变是常用系统事件,需重写 moveEvent 和 resizeEvent 函数,适用于窗口状态监控、自适应布局等场景。
核心事件函数:
moveEvent(QMoveEvent *event):窗口移动时触发(获取新位置);resizeEvent(QResizeEvent *event):窗口大小改变时触发(获取新尺寸)。
实战示例:窗口移动 + 大小监控
cpp
// 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,则继续分发。

实战示例:拦截鼠标按下事件
cpp
// 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表示继续分发。

实战示例:拦截标签的鼠标事件
cpp
// 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);
}
五. 事件系统避坑指南
- 鼠标移动事件 :默认需按下鼠标才能触发,需调用
setMouseTracking(true)开启实时跟踪; - 事件过滤器安装:需确保目标对象未被销毁,且过滤器对象生命周期长于目标对象;
- 事件拦截顺序:事件过滤器 → 事件分发器 → 事件处理函数,拦截优先级依次降低;
- 组合键判断 :需用
event->modifiers()获取组合键状态,不可直接判断多个 key(); - 定时器停止
:QTimerEvent需调用killTimer(timerId)停止,QTimer调用stop()即可; - 窗口事件坐标 :
moveEvent的event->pos()是相对父窗口的坐标,globalPos()是相对屏幕的坐标。
结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:本文完整覆盖了 Qt 事件系统的核心知识点,从基础的鼠标 / 键盘 / 定时器事件,到进阶的事件分发器、事件过滤器,再补充窗口移动 / 大小改变两大实用事件,所有代码均可直接复制运行。Qt 事件系统的核心是 "重写虚函数" 和 "事件流控制"------ 简单事件直接重写对应函数,复杂拦截用事件分发器,全局监控用事件过滤器。掌握这些技巧后,可轻松实现任何复杂交互逻辑(如自定义拖拽、快捷键、实时监控)。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
