Qt 核心事件系统全攻略:鼠标 / 键盘 / 定时器 / 窗口 + 事件分发与过滤


🔥草莓熊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 及其子类提供了大量预定义的事件虚函数(如 mousePressEventkeyPressEvent),只需在自定义类中重写这些函数,即可捕获并处理对应事件。

关键前提:自定义控件提升

处理控件事件时,需先自定义类继承目标控件(如 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 窗口事件:移动与大小改变(补充重点)

窗口移动、大小改变是常用系统事件,需重写 moveEventresizeEvent 函数,适用于窗口状态监控、自适应布局等场景。

核心事件函数

  • 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() 即可;
  • 窗口事件坐标moveEventevent->pos() 是相对父窗口的坐标,globalPos() 是相对屏幕的坐标。

结尾:

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:本文完整覆盖了 Qt 事件系统的核心知识点,从基础的鼠标 / 键盘 / 定时器事件,到进阶的事件分发器、事件过滤器,再补充窗口移动 / 大小改变两大实用事件,所有代码均可直接复制运行。Qt 事件系统的核心是 "重写虚函数" 和 "事件流控制"------ 简单事件直接重写对应函数,复杂拦截用事件分发器,全局监控用事件过滤器。掌握这些技巧后,可轻松实现任何复杂交互逻辑(如自定义拖拽、快捷键、实时监控)。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
数智联AI团队2 小时前
AI搜索流量争夺战白热化,数智联科技以GEO优化助力企业抢占春节营销先机
人工智能·科技
zxsz_com_cn2 小时前
预测性维护助力春节值守——智能传感器让工厂“安心过年”
人工智能·数据挖掘
_OP_CHEN2 小时前
【前端开发之JavaScript】(三)JS基础语法中篇:运算符 / 条件 / 循环 / 数组一网打尽
开发语言·前端·javascript·网页开发·图形化界面·语法基础·gui开发
才聚PMP2 小时前
基于易经思维的组织级项目管理测评体系
大数据·人工智能
Clarence Liu2 小时前
用大白话讲解人工智能(6) 深度学习:堆“多层神经网络“会发生什么?
人工智能·深度学习·神经网络
Web打印2 小时前
Phpask(php集成环境)之05配置tp5网站
开发语言·php
阿kun要赚马内4 小时前
C++中的Windows API双缓冲技术
c++
LaughingZhu10 小时前
Product Hunt 每日热榜 | 2026-02-14
数据库·人工智能·经验分享·神经网络·搜索引擎·chatgpt
大模型探员10 小时前
告别答非所问!深度解析文档切分如何决定RAG的搜索上限
人工智能