QT-事件

Qt事件

除了信号和槽通信机制外,Qt中还提供了事件处理机制实现与用户的交互和对象间的通信。Qt捕获底层操作系统消息,进行封装之后转换为Qt事件,事件处理后才发出信号。

一、事件概述

Qt中事件是程序内部或外部发生的动作。比如程序外部:用户移动鼠标、单击鼠标、鼠标拖拽、按下按键等操作都会产生对应的事件。程序内部:窗口第一次显示、放大、缩小等。

📌 两类事件

  1. 外部事件 (用户操作导致的)

    就是人跟程序互动时触发的,比如:

    • 移动鼠标 → 程序收到一个"鼠标移动事件"。
    • 单击鼠标 → 程序收到一个"鼠标点击事件"。
    • 按下键盘 → 程序收到一个"键盘按下事件"。
    • 拖动窗口 → 程序收到"鼠标拖拽事件"。

    👉 就好比你在敲门、按电梯按钮,程序必须做出反应。


  1. 内部事件 (程序自己触发的)

    就算你啥都不点,程序自己也会产生一些动作,比如:

    • 窗口 第一次显示 → 产生"窗口显示事件"。
    • 窗口被 放大/缩小 → 产生"窗口大小改变事件"。
    • 程序定时器到点了 → 产生"定时器事件"。

    👉 就像手机自己亮屏、锁屏,这是系统自己触发的。

1、事件来源

事件有两个来源:程序外部和程序内部

  • 在与用户交互时产生,比如用户点击鼠标、按下按键等,此时操作系统会感知到用户的行为,并产生消息,然后将消息投递到应用程序的消息队列当中;应用程序从消息队列中提取消息,并将其转化为Qt事件,生产事件对象。
  • 由Qt应用程序自身产生。例如当窗口第一次显示时,会产生一个绘制事件,以通知窗口需要重新绘制自身,从而使窗口可见。这是由程序内部产生的事件。
2、Qt事件处理机制

(1)当操作系统发生一个事件时,事件首先会被操作系统内核中的设备驱动程序所感知,然后发送给操作系统的事件管理系统,事件管理系统将其放入到事件队列中。

(2)Qt应用程序作为一个客户端,通过调用QApplication的exec()函数启动事件循环,这个循环会不断地从事件队列取出事件,Qt捕获之后,会将该事件转换为相应的Qt事件对象。

(3)事件自己不能处理自己,循环中依次取出事件首先交给notify()函数,通过notify()函数派发给处理事件的对象。由QObject类以及其派生类对象进行事件的处理,通过重写event()函数,或重写对应的事件处理函数完成事件的处理。

完整流程总结

  1. 程序运行后,QApplication::exec() 启动事件循环,持续监听事件;
  2. 事件产生后,先到 QApplication::notify() 做全局处理;
  3. 再传递到目标 QObjectevent() 方法,识别事件类型;
  4. 最终调用具体的 xxxEvent() 函数,执行实际逻辑。
3、事件与信号

Qt的事件与信号很容易混淆。

事件是底层操作系统所产生的消息,由Qt捕获之后,封装为对应的事件对象,比如鼠标单击对应的事件类型是 QEvent::MouseButtonPress.我们在程序中可以通过重写对应的事件处理函数或event()函数进行事件的处理。

Qt为了方便事件的处理,引入了信号(Signal)的概念,封装了一些事件操作的标准预处理,比如对QPushButton预定义了clicked()信号。使用户不必去处理底层事件,只需要处理信号即可。当一个事件触发后,对象通过发射一个信号(Signal)进行通知,而其他对象的槽函数(Slot)可以连接到这个信号,从而实现对事件的处理。

信号是对事件的封装

二、事件类型

常见的事件类型包括:

  • 键盘事件(如QKeyEvent,当用户按下或释放键盘按键时产生)
  • 鼠标事件(如QMouseEvent,包含鼠标点击、移动、滚轮滚动等操作)
  • 窗口事件(如resize、paint、move等,与窗口大小、位置、重绘相关的事件)

Qt捕获之后,会将该事件转换为相应的Qt事件对象,所有事件都是QEvent类或其派生类的实例,常见的事件类如下:

类名 作用
QMouseEvent 鼠标事件
QWheelEvent 滚轮事件
QKeyEvent 键盘事件
QPaintEvent 绘画事件
QTimerEvent 定时器事件
QResizeEvent 窗口大小改变事件
QCloseEvent 关闭事件
QShowEvent 显示事件
QHideEvent 隐藏事件

三、鼠标事件

用户点击鼠标 → 硬件信号 → 操作系统事件 → Qt 事件循环接收 → 转换为 QMouseEvent → 传递给按钮 → 按钮判断有效后发出 clicked() 信号 → 连接的槽函数执行

鼠标事件涉及鼠标左键或右键按下,释放、双击、移动等操作。QMouseEvent类用于处理鼠标事件。

事件处理函数都是虚函数,全部声明在QWidget类中,如果要在子类中处理事件,需要重写对应的事件处理函数。

与鼠标事件相关的处理函数如下:

事件处理函数 参数类型 描述
mousePressEvent() QMouseEvent 鼠标按键按下
mouseDoubleClickEvent() QMouseEvent 鼠标双击
mouseReleaseEvent() QMouseEvent 鼠标释放
mouseMoveEvent() QMouseEvent 鼠标移动
wheelEvent() QWheelEvent 鼠标滚轮滚动
1、重写鼠标事件函数

创建QWidget类的子类,重写事件处理函数

widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>       // QWidget 是所有界面部件的基类
#include <QPoint>        // QPoint 用于存储点的坐标

namespace Ui {
class Widget;            // 前向声明 Qt Designer 生成的 Ui::Widget 类
}

class Widget : public QWidget
{
    Q_OBJECT             // Qt 的宏,启用信号与槽机制

public:
    explicit Widget(QWidget *parent = nullptr); // 构造函数
    ~Widget();                                  // 析构函数

protected:
    // 重写鼠标按下事件函数,用于获取鼠标点击位置
    void mousePressEvent(QMouseEvent *e) override;
    // 重写鼠标滚轮事件函数,用于实现图标放大/缩小
    void wheelEvent(QWheelEvent *e) override;
    // 重写鼠标移动事件函数,用于实现窗口拖动
    void mouseMoveEvent(QMouseEvent *event) override;

private:
    Ui::Widget *ui;       // UI 界面指针
    QPoint offset;        // 鼠标相对于窗口左上角的偏移量(用于拖动窗口)
};

#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent>   // 鼠标事件类
#include <QDebug>        // 调试输出

// 构造函数
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);                                 // 初始化 UI
    ui->label->setPixmap(QPixmap(":/images/iot.png")); // 在 label 上显示图片
    ui->label->setScaledContents(true);                // 图片随 label 大小自动缩放
    setWindowFlags(Qt::FramelessWindowHint | windowFlags()); // 去掉窗口边框(自绘窗口)
}

// 析构函数
Widget::~Widget()
{
    delete ui; // 释放 UI 内存
}

// 鼠标按下事件
void Widget::mousePressEvent(QMouseEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)   // 判断是否是鼠标按下事件
    {
        if (event->button() == Qt::LeftButton)       // 如果是鼠标左键
        {
            qDebug() << "鼠标左键被按下,位于用户区坐标:" << event->pos()
                     << ",屏幕坐标:" << event->globalPos();
            offset = event->pos(); // 记录鼠标按下时,鼠标相对于窗口的位置(用于拖动窗口)
        }
        else                                         // 如果是鼠标右键
        {
            qDebug() << "鼠标右键被按下,位于用户区坐标:" << event->pos();
        }
    }
}

// 鼠标滚轮事件
void Widget::wheelEvent(QWheelEvent *e)
{
    // angleDelta().y() > 0 表示向上滚动,< 0 表示向下滚动
    if (e->angleDelta().y() > 0) // 滚轮向上,放大图片
    {
        ui->label->resize(ui->label->width()+5, ui->label->height()+5);
    }
    else // 滚轮向下,缩小图片
    {
        ui->label->resize(ui->label->width()-5, ui->label->height()-5);
    }
}

// 鼠标移动事件
void Widget::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) // 当鼠标左键按下并移动时
    {
        // 移动窗口位置
        // globalPos() 是鼠标在屏幕上的坐标
        // offset 是鼠标相对窗口的偏移量
        // 两者相减得到窗口新的左上角位置
        this->move(event->globalPos() - offset);
    }
}
  1. mousePressEvent(QMouseEvent *e)
    👉 处理鼠标按下事件。
  • 左键:记录点击位置(用于窗口拖动)。
  • 右键:只是输出调试信息。
  1. wheelEvent(QWheelEvent *e)
    👉 处理鼠标滚轮事件。
  • 向上滚:放大图片。
  • 向下滚:缩小图片。
  1. mouseMoveEvent(QMouseEvent *event)
    👉 处理鼠标移动事件(拖动窗口)。
  • 鼠标按住左键拖动时,计算窗口新位置,并移动窗口。
2、QMouseEvent对象

QMouseEvent封装鼠标事件的类,常用的成员函数:

成员函数 描述
Qt::MouseButton button() const; 返回产生事件的按钮
QPoint globalPos() const; 返回鼠标的位置,使用屏幕坐标
QPoint pos() const; 返回鼠标的位置,使用用户区坐标
QEvent::Type type() const 返回事件类别,如按下、释放或双击等
3、QWheelEvent对象

QWheelEvent类封装了鼠标滚轮事件,如滚轮滚动的方向、幅度、位置等。

常用的成员函数:

成员函数 描述
QPoint pos() const; 返回事件发生时鼠标的位置(用户区坐标)
QPoint globalPos() const; 返回事件发生时鼠标的位置(屏幕坐标)
QPoint angleDelta() const; 返回滚轮的滚动量

在 Qt 里,事件函数(event handler)QWidget 或其他控件类自带的虚函数,比如:

  • mousePressEvent(QMouseEvent *e) ------ 鼠标按下
  • wheelEvent(QWheelEvent *e) ------ 鼠标滚轮
  • keyPressEvent(QKeyEvent *e) ------ 键盘按下
  • paintEvent(QPaintEvent *e) ------ 绘制事件

🔹 为什么要 重写(override) 这些函数?

  1. Qt 已经帮我们定义了这些事件函数,但默认实现是"什么都不做"
    • 比如 mousePressEvent() 默认不会帮你输出坐标,也不会帮你移动窗口。
    • 如果你需要自定义行为,就要 重写 它们,写上自己的逻辑。
  2. 让控件有"特殊功能"
    • 例子:重写 mousePressEvent,实现点击后窗口能拖动。
    • 如果不重写,窗口只能被系统默认拖动(有标题栏时),去掉边框后根本没法拖动。
  3. 事件驱动编程的核心
    • Qt 是 事件驱动 的:鼠标、键盘、窗口变化都会产生事件。
    • 程序如何响应这些事件,就要靠重写事件函数。

🔹 举个通俗例子:

事件函数 想象成「门铃」。

  • Qt 框架:默认帮你装好一个门铃(函数)。
  • 系统按门铃(鼠标点击 / 滚轮滚动 / 按键),就会触发事件。
  • 如果你没写(不重写),按门铃没人理(默认什么都不做)。
  • 如果你写了(重写),就能定义:
    • 门铃响时开灯
    • 门铃响时开门
    • 门铃响时发个消息

✅ 所以:
重写事件函数 = 告诉程序当某个事件发生时,你要如何反应。

四、键盘事件

1、键盘事件处理函数
事件处理函数 对应事件类型 参数类型 描述
void keyPressEvent(QKeyEvent *event); QEvent::KeyPress QKeyEvent 按键按下
void keyReleaseEvent(QKeyEvent *event); QEvent::KeyRelease QKeyEvent 按键释放

下面是处理键盘事件的示例:

通过按键移动图片,重写键盘按下事件的处理函数。

cpp 复制代码
#ifndef KEYEVENTWIDGET_H   // 头文件防卫,防止重复包含
#define KEYEVENTWIDGET_H

#include <QWidget>         // QWidget 是所有可视化窗口控件的基类

namespace Ui {
class KeyEventWidget;      // 声明一个 UI 命名空间里的 KeyEventWidget 类(Qt Designer 生成)
}

class KeyEventWidget : public QWidget   // 自定义类,继承 QWidget
{
    Q_OBJECT               // Qt 元对象系统宏,支持信号槽机制等

public:
    explicit KeyEventWidget(QWidget *parent = nullptr); // 构造函数,支持父子对象机制
    ~KeyEvevtWidget();  // 析构函数,释放资源(这里注意拼写错了,应该是 KeyEventWidget)

protected:
    // 处理按键事件
    void keyPressEvent(QKeyEvent *e);   // 重写 QWidget 的键盘按下事件函数

private:
    Ui::KeyEventWidget *ui;  // 指向 UI 界面类的指针,Qt Designer 自动生成的 UI 操作接口
};

#endif // KEYEVENTWIDGET_H  // 文件结尾的防卫符号

继承 QWidget

  • KeyEventWidget 是一个自定义窗口类,继承自 QWidget,可以显示在界面上。

重写 keyPressEvent(QKeyEvent *e)

  • 这是 Qt 里专门处理键盘按下的事件函数。
  • 默认实现是"不处理",所以我们重写后,可以实现:
    • 按下某个键时打印信息
    • 按下 Esc 键关闭窗口
    • 按下方向键移动控件

KeyEvevtWidget.cpp

cpp 复制代码
#include "KeyEventWidget.h"              // 包含自定义的 KeyEventWidget 类头文件
#include "ui_keyeventwidget.h"           // 包含 Qt Designer 生成的 UI 类定义
#include <QWheelEvent>                   // 包含鼠标滚轮事件类(虽然这里没用到)
#include <QKeyEvent>                     // 包含键盘事件类

KeyEventWidget::KeyEventWidget(QWidget *parent)
    : QWidget(parent)                    // 调用 QWidget 构造函数,初始化父类
    , ui(new Ui::KeyEventWidget)           // 创建 UI 类的对象
{
    ui->setupUi(this);                   // 设置 UI 界面,关联 .ui 文件中的控件
    ui->label->setStyleSheet("background-color: #ccc; height: 200px; width:100px"); 
    // 设置 label 的样式:灰色背景,固定大小(200 高,100 宽)
}

KeyEventWidget::~KeyEventWidget()        // 析构函数
{
    delete ui;                           // 删除 UI 对象,释放内存
}

void KeyEventWidget::keyPressEvent(QKeyEvent *e) // 重写按键事件处理函数
{
    // 按上方向键 → 标签向上移动 5 像素
    if (e->key() == Qt::Key_Up)
    {
        ui->label->move(ui->label->x(), ui->label->y()-5);
    }
    // 按下方向键 → 标签向下移动 5 像素
    else if (e->key() == Qt::Key_Down)
    {
        ui->label->move(ui->label->x(), ui->label->y()+5);
    }
    // 按左方向键 → 标签向左移动 5 像素
    else if (e->key() == Qt::Key_Left)
    {
        ui->label->move(ui->label->x()-5, ui->label->y());
    }
    // 按右方向键 → 标签向右移动 5 像素
    else if (e->key() == Qt::Key_Right)
    {
        ui->label->move(ui->label->x()+5, ui->label->y());
    }
    // 按下 Ctrl + M 组合键
    else if (e->key() == Qt::Key_M && e->modifiers() == Qt::ControlModifier)
    {
        // 如果窗口是最大化的 → 还原窗口
        if (windowState() & Qt::WindowMaximized) {
            setWindowState(windowState() & ~Qt::WindowMaximized);
        } 
        // 否则 → 最大化窗口
        else {
            setWindowState(Qt::WindowMaximized);
        }
    }
    // 其他按键 → 使用父类默认处理
    else
    {
        QWidget::keyPressEvent(e);
    }
}
  • e->key() == Qt::Key_M

    判断用户按下的是否是 M 键

  • e->modifiers() == Qt::ControlModifier

    判断是否同时按住了 Ctrl 键

    • modifiers() 表示组合键(比如 Ctrl、Shift、Alt)。
    • 这里检查是否等于 Qt::ControlModifier,也就是 Ctrl 键。

👉 所以整体条件:按下 Ctrl+M


  • windowState()
    返回窗口的当前状态,比如:
    • Qt::WindowNoState → 普通状态
    • Qt::WindowMaximized → 最大化
    • Qt::WindowMinimized → 最小化
    • Qt::WindowFullScreen → 全屏

  • if (windowState() & Qt::WindowMaximized)
    • & 是按位与运算,检查当前窗口状态里是否包含 最大化
    • 如果是最大化 → 进入还原操作。

  • setWindowState(windowState() & ~Qt::WindowMaximized);
    • ~Qt::WindowMaximized 表示"去掉最大化这个标志"。
    • windowState() & ~Qt::WindowMaximized → 当前状态里,去掉"最大化"。
    • 也就是说:把窗口从最大化还原回普通大小

  • else { setWindowState(Qt::WindowMaximized); }
    • 如果当前不是最大化 → 就设置成最大化。

Qt 里的 windowState() 返回的值,其实就像 MCU(单片机)里寄存器的值:

  • 每一位(或者说每个二进制标志位)代表一个"开关"状态。
  • 如果该位是 1,说明这个状态 启用
  • 如果该位是 0,说明这个状态 未启用

例如:

Qt 定义的窗口状态(简化):

状态宏 二进制位 说明
Qt::WindowNoState 0000 默认普通状态
Qt::WindowMinimized 0001 最小化
Qt::WindowMaximized 0010 最大化
Qt::WindowFullScreen 0100 全屏

如果 windowState() 返回:

  • 0010 → 表示窗口是 最大化
  • 0001 → 表示窗口是 最小化
  • 0011 → 表示窗口同时有 最小化 + 最大化(可能组合状态)

那么:

复制代码
if (windowState() & Qt::WindowMaximized)

就是在问:

👉 "这个二进制值的 第2位(最大化标志)是不是 1?"

如果是 1,就说明当前窗口已经最大化。


📌 所以它和寄存器检查位完全一样。

在嵌入式里,我们常写:

复制代码
if (reg & (1<<3)) { ... } // 检查寄存器的第3位

在 Qt 里就是:

复制代码
if (windowState() & Qt::WindowMaximized) { ... }
2、QKeyEvent对象

用于描述键盘事件,常用成员函数:

成员函数 描述
int key() const; 获取按下的键
int modifiers() const; 判断修饰键(Ctrl、Shift、Alt)是否在按下状态

五、重写event()事件函数

event()函数是QObject类所提供的,特定事件处理函数仅能处理预定义的事件类型,而event()函数可以处理所有类型的事件。

  • 对于只需要处理某种特定类型事件的情况,重写对应的特定事件处理函数会更加直接。
  • 如果需要对多种不同类型的事件进行处理,那么就重写event()函数。

下面是重写event()实现事件处理示例

  1. 新建MyWidget类
  2. mywidget.h文件如下:
cpp 复制代码
#ifndef MYWIDGET_H        // 头文件保护,防止重复包含
#define MYWIDGET_H

#include <QWidget>        // 引入 QWidget 基类,MyWidget 要继承它

namespace Ui {
class MyWidget;           // 前向声明 UI 界面类(Qt Designer 生成的类)
}

class MyWidget : public QWidget   // 定义 MyWidget 类,继承自 QWidget
{
    Q_OBJECT              // Qt 的元对象宏,支持信号槽、事件机制等

public:
    explicit MyWidget(QWidget *parent = nullptr); // 构造函数,父对象默认为空
    ~MyWidget();         // 析构函数,释放资源

    // 重写 Qt 的事件处理函数 event()
    // 所有事件(鼠标、键盘、窗口等)都会先经过 event()
    // 返回 true 表示事件已处理,不需要再传递;false 表示未处理,交给默认处理
    bool event(QEvent *event);

protected:
    // 自定义的鼠标事件处理函数(自己写的,名字不是 Qt 内置的)
    void doPressEvent(QMouseEvent *event);

private:
    Ui::MyWidget *ui;     // UI 界面指针,指向 Qt Designer 生成的界面类
};

#endif // MYWIDGET_H       // 头文件保护宏结束
  1. event(QEvent *event)
    • Qt 的 事件分发函数
    • 凡是传给这个窗口的事件(鼠标、键盘、窗口大小变化等),都会先经过它。
    • 如果你重写它,可以统一拦截和处理不同类型的事件。
  2. doPressEvent(QMouseEvent *event)
    • 这是你 自定义的函数,不是 Qt 框架强制要求的。
    • 一般用来在 event() 里检测到鼠标事件后,专门调用它来处理。
    • 相当于你自己封装的"鼠标事件处理逻辑"。
  3. 返回值 bool 的意义
    • true → 表示事件在这里已经被处理完毕,Qt 不会再把它传递下去。
    • false → 表示你没处理,Qt 会交给默认的父类逻辑继续处理。

📌 所以可以理解为:

  • event() → 总入口,像一个"事件分发器"。
  • doPressEvent() → 你自己写的业务逻辑处理函数。

mywidget.cpp

cpp 复制代码
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)                     // 调用父类 QWidget 的构造函数
    , ui(new Ui::MyWidget)                // 创建 UI 界面对象
{
    ui->setupUi(this);                    // 初始化 UI 布局和控件
}

MyWidget::~MyWidget()
{
    delete ui;                            // 释放 UI 对象,防止内存泄漏
}

// 重写 QWidget 的事件处理函数 event()
bool MyWidget::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)   // 判断是否为鼠标按下事件
    {
        // 将接收到的 QEvent 对象强制转换为 QMouseEvent 对象
        QMouseEvent *mEvent = dynamic_cast<QMouseEvent*>(event);
        // 调用自定义处理函数处理鼠标按下事件
        doPressEvent(mEvent);
        // 返回 true 表示事件已被处理,不再传递给其他处理函数
        return true;
    }
    // 对于其他类型事件,调用基类的 event() 函数进行默认处理
    return QWidget::event(event);
}

// 自定义函数,用于处理鼠标按下事件
void MyWidget::doPressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)   // 判断是否为鼠标左键
    {
        qDebug() << "左键点击";              // 打印调试信息
    }
    else                                      // 其他情况(如右键)
    {
        qDebug() << "右键点击";              // 打印调试信息
    }
}
  • 事件过滤与处理 :通过重写 event() 函数可以拦截所有事件类型,并根据类型选择性处理。

  • 类型转换 :使用 dynamic_cast 将基类 QEvent* 转换为具体事件类型 QMouseEvent*,便于访问特有属性。

  • 事件消费 :返回 true 表示事件已经处理,不会继续向父类或其他对象传递。

  • 自定义函数解耦 :将具体的鼠标按下处理逻辑放到 doPressEvent() 函数中,使代码结构更清晰。

  • 当 Qt 收到一个事件(比如鼠标、键盘、绘图请求),它会先调用 QWidget::event()

  • event() 内部会判断事件类型,然后再调用更具体的事件处理函数:

    • mousePressEvent()
    • keyPressEvent()
    • paintEvent()
    • ...

所以 如果你重写了 event(),但没有调用 QWidget::event(event),相当于"截断"了事件分发

⚠️ 重点:Qt 内部的设计就是 依赖 QWidget::event() 去调用其他事件函数 。 如果你直接返回 false,那些具体的事件函数根本不会被调用。

return QWidget::event(event); 是显式调用 父类版本的 event(),让它继续帮你分发事件。

六、定时器事件

Qt的定时器事件主要用于实现周期性或延迟执行任务的功能。对于实时监控、动画效果、定期检查状态变化以及其他需要按时间顺序控制的行为非常有用。

在Qt中,有两种主要的方式来使用定时器:

1、QTimer类
  • QTimer是基于事件循环的定时器,通过创建一个QTimer对象并调用其start()方法来启动定时器。当设定的时间间隔到期时,会自动触发关联的槽函数(slot)或者发送一个timeout()信号。
  • 使用QTimer可以轻松实现在特定时间间隔后执行重复的任务,例如更新用户界面、轮询硬件状态、刷新数据等。
2、定时器事件

(1)对于任意QObject子类,可以调用以下成员函数开启定时器:

cpp 复制代码
int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);

该函数会以interval毫秒为周期开启一个定时器,产生一个QTimerEvent定时器事件,并返回定时器标识。

Qt::TimerType timerType用于指定计时器的类型:

  • Qt::VeryCoarseTimer:这是最不精确的计时器类型,可能有几秒钟的误差。这种类型的计时器会消耗最少的系统资源。
  • Qt::CoarseTimer:这是一种相对不精确的计时器类型,可能有几百毫秒的误差。这种类型的计时器比VeryCoarseTimer更精确,但仍然消耗较少的系统资源。
  • Qt::PreciseTimer:这是最精确的计时器类型,误差通常小于1毫秒。这种类型的计时器提供最高的精度,但可能会消耗更多的系统资源。

(2)QObject子类,可以通过重写timerEvent()成员函数处理定时器事件

cpp 复制代码
void timerEvent(QTimerEvent *event);

(3)清除定时器,直到调用QObject类里的成员函数

cpp 复制代码
void killTimer(int id);

下面是一个定时器事件的示例:

timerwidget.h

cpp 复制代码
#ifndef TIMERWIDGET_H
#define TIMERWIDGET_H

#include <QWidget>

namespace Ui {
class TimerWidget;
}

class TimerWidget : public QWidget
{
    Q_OBJECT
public:
    explicit TimerWidget(QWidget *parent = nullptr); // 构造函数
    ~TimerWidget();                                  // 析构函数

protected:
    // 重写QObject提供的定时器事件处理函数
    void timerEvent(QTimerEvent *e);

private slots:
    void on_start_clicked();  // "开始"按钮点击槽
    void on_stop_clicked();   // "结束"按钮点击槽

private:
    Ui::TimerWidget *ui;     // UI指针
    int timerId;             // 保存定时器ID,用于开启/关闭定时器
};

#endif // TIMERWIDGET_H

重写定时器事件函数,timerwidget.cpp代码如下:

cpp 复制代码
#include "timerwidget.h"
#include "ui_timerwidget.h"
#include <QTime> // 用于显示当前时间

TimerWidget::TimerWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::TimerWidget)
    , timerId(-1) // -1表示当前没有定时器
{
    ui->setupUi(this); 

    // 创建定时器,周期1秒(1000ms)
    if (timerId == -1)
        timerId = startTimer(1000);  // startTimer 返回一个 timerId,用于killTimer

    // 美化显示标签
    ui->label->setStyleSheet(
        "font-size:20px;"
        "background-color:#fff;"
        "border:1px solid black;"
        "border-radius:5px;"
    );
    ui->label->resize(200, 30); // 设置标签大小
}

// 析构函数
TimerWidget::~TimerWidget()
{
    delete ui; // 释放UI对象
}

// 定时器事件,每当定时器时间到达时,这个函数会被自动调用
void TimerWidget::timerEvent(QTimerEvent *e)
{
    Q_UNUSED(e); // 不使用参数
    // 更新标签显示当前系统时间
    ui->label->setText(QTime::currentTime().toString());
}

// "开始"按钮点击槽函数
void TimerWidget::on_start_clicked()
{
    if (timerId == -1) // 如果当前没有定时器,则启动
        timerId = startTimer(1000); // 周期1秒
}

// "结束"按钮点击槽函数
void TimerWidget::on_stop_clicked()
{
    if (timerId != -1) // 如果定时器存在
    {
        killTimer(timerId); // 停止定时器
        timerId = -1;       // 重置ID
    }
}
  • 定时器是什么?

    定时器就像手机闹钟,每隔固定时间就"响一次",Qt会给你一个事件,让你在 timerEvent() 里处理。

  • 定时器ID
    startTimer() 会返回一个整数ID,用来区分不同的定时器。要关闭定时器必须用 killTimer(id)

  • 事件触发

    每当时间到,Qt会自动调用你重写的 timerEvent(QTimerEvent *e) 函数,你在里面写想做的事情,比如刷新UI。

  • 开始/结束按钮

    • on_start_clicked():手动开启定时器
    • on_stop_clicked():手动关闭定时器
  • QTimer vs 定时器事件

    • QTimer 是信号槽方式,更现代,更方便
    • startTimer() + timerEvent() 是事件驱动方式,更底层,更灵活

timerId == -1 的含义

cpp 复制代码
int timerId; // 定时器ID
timerId = -1; // -1 表示当前没有定时器
  • timerId == -1 时,说明当前没有定时器在运行
  • timerId != -1 时,说明已经有一个定时器存在timerId 保存了这个定时器的ID。

如果不判断,直接调用:

cpp 复制代码
timerId = startTimer(1000);
  • 每次点击"开始"按钮或构造函数里执行,都会再启动一个新的定时器。
  • 结果就是同一秒内可能有多个定时器同时触发 ,导致 timerEvent() 被重复调用,UI更新混乱。

所以加判断:

cpp 复制代码
if (timerId == -1) 
    timerId = startTimer(1000);
  • 确保同一时间只存在一个定时器,避免重复触发。

Qt能不能同时存在多个定时器?

  • ,Qt允许一个对象启动多个定时器,每个定时器都有自己的ID。
  • 但是你的设计是只需要一个定时器去刷新时间,所以只保留一个,方便管理和关闭。
  • 如果你想同时运行多个定时器,需要每个定时器保存自己的ID ,在 timerEvent(QTimerEvent *e) 里通过 e->timerId() 区分不同的定时器。

七、事件过滤器

Qt中的事件过滤器机制,允许在对象接收到事件之前拦截和处理这些事件。使用事件过滤器可以在不直接修改对象代码的情况下增加额外的事件处理逻辑。

事件过滤器的用途:

  • 监视和响应特定对象的事件,而无需直接修改对象的代码
  • 拦截事件,在事件达到目标对象之前进行预处理或完全阻止事件
  • 实现跨多个对象的通用事件处理逻辑,而不需要在每个对象所属的类中定义重复相同的代码

事件过滤器的使用步骤:

  1. 安装事件过滤器,给要进行事件过滤的对象(组件)安装事件过滤器
  2. 开发事件过滤器,对事件进行拦截处理。进行事件拦截处理的对象需要重写QObject::eventFilter()函数进行事件的处理
  3. 事件处理,如果eventFilter()返回true表示事件处理完毕,如果返回false,事件将继续传递,最终到达目标对象。
1、安装事件过滤器

使用QObject::installEventFilter()函数将一个事件过滤器对象安装到需要进行事件过滤的对象上。

cpp 复制代码
obj->installEventFilter(this);

在UI可视化界面拖放一个QLabel组件,为该组件安装事件过滤器:

cpp 复制代码
// 为标签(label)控件安装事件过滤器,事件过滤器会监视所有传递给该 label 的事件。
//这里this是指向实现了事件过滤逻辑的对象。将事件的处理交给该对象。
ui->label->installEventFilter(this);
2、重写eventFilter()函数

对事件进行过滤器处理的对象必须重写QObject::eventFilter()虚函数。

它的主要作用是在事件传递给目标对象之前提供一个拦截点。在事件到达目标对象之前对其进行处理、修改甚至阻止。

  • watched : 参数是一个指向被监视对象的指针。这个对象就是调用installEventFilter函数安装过滤器的那个对象。
  • event : 参数是一个指向事件的指针,表示正在传递给watched对象的事件
cpp 复制代码
 virtual bool eventFilter(QObject *watched, QEvent *event);
  1. watched

    • 类型:QObject*

    • 含义:正在被你监视的对象

    • 就是你之前调用 installEventFilter(this) 的那个控件。

    • 例子:

      cpp 复制代码
      ui->textEdit->installEventFilter(this);

      那么 watched 就可能是 ui->textEdit

    • 作用:通过它你可以知道"这个事件是哪个控件发来的",这样你可以针对不同控件做不同处理。

  2. event

    • 类型:QEvent*

    • 含义:正在发生的事件本身

    • 事件的类型很多,比如:

      • QEvent::KeyPress → 按键事件
      • QEvent::MouseButtonPress → 鼠标点击事件
      • QEvent::FocusIn → 焦点进入事件
    • 作用:你可以通过它获取事件的详细信息,例如:

      cpp 复制代码
      if(event->type() == QEvent::KeyPress){
          QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
          if(keyEvent->key() == Qt::Key_Return){
              // 按下的是回车
          }
      }

watched → 谁发的事件

event → 事件是什么

3、案例

在一个窗口中有一个多行文本输入框QTextEdit,需要让我们屏蔽掉键盘上的回车键,也就是按回车键之后在这个文本编辑框中再也不能换行了。

其实上面的需求有三种解决方案:

  1. 自定义一个新的类让其继承QTextEdit,在这个子类中重写键盘事件keyPressEvent,在这个函数里边屏蔽掉回车键
  2. 自定义一个新的类让其继承QTextEdit,在这个子类中重写事件分发器event,在这个函数里边屏蔽掉回车键
  3. 给QTextEdit安装事件过滤器,基于QTextEdit的父窗口对这个控件的事件进行过滤

最简单的方式还是第三种,因为我们不需要再定义出一个子类就可以轻松的完成控件事件的过滤了。

准备工作:在主窗口中添加一个QTextEdit类型的控件,如下图:

主窗口头文件: widget_01.h

cpp 复制代码
class widget_01 : public QWidget
{
    Q_OBJECT

public:
    explicit widget_01(QWidget *parent = nullptr); // 构造函数
    ~widget_01();                                  // 析构函数

    // 重写事件过滤器函数
    bool eventFilter(QObject *watched, QEvent *event) override;

private:
    Ui::widget_01 *ui;                             // UI指针
};

主窗口源文件: widget_01.cpp

cpp 复制代码
widget_01::widget_01(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::widget_01)
{
    ui->setupUi(this);                              // 初始化UI界面

    // 安装事件过滤器,把textEdit的事件交给当前对象处理
    ui->textEdit->installEventFilter(this);        // 安装过滤器后,所有textEdit事件会先到eventFilter
}

widget_01::~widget_01()
{
    delete ui;                                     // 释放UI对象
}

// 事件过滤器函数
bool widget_01::eventFilter(QObject *watched, QEvent *event)
{
    // 判断事件来源是否是 textEdit,并且事件类型是否为按键事件
    if(watched == ui->textEdit && event->type() == QEvent::KeyPress){
        // 将 QEvent 转换为 QKeyEvent 以获取按键信息
        QKeyEvent *pKeyEvent = dynamic_cast<QKeyEvent*>(event);

        // 判断按键是否为回车键
        if(pKeyEvent->key() == Qt::Key_Return){
            qDebug() << "回车...";                // 打印调试信息
            return true;                           // 返回true,表示事件已被处理,不再向下传递
        }
    }  

    // 对于其他事件,调用基类的eventFilter处理
    return QWidget::eventFilter(watched, event);
}

事件过滤器作用

  • 可以拦截指定对象的事件(如 textEdit 的按键、鼠标等事件),在到达对象本身之前先处理。
  • 常用于想对某个控件做特殊行为,但不想修改控件内部逻辑的场景。

installEventFilter(this)

  • 安装过滤器后,控件的事件会先到过滤器对象的 eventFilter()
  • 可以选择处理 (返回 true)或传递 (返回 false 或调用基类)。

事件类型判断

  • event->type() 用于判断事件类型,比如 QEvent::KeyPressQEvent::MouseButtonPress 等。

事件消费

  • 返回 true:事件被拦截,不再传递给控件自身。
  • 返回 false 或调用基类:事件继续传递,控件可自行处理。

类型转换

  • 使用 dynamic_cast<QKeyEvent*>QEvent* 转换为 QKeyEvent*,可以访问按键信息(如 key())。
相关推荐
专注VB编程开发20年4 小时前
CSS 的命名方式像是 PowerShell 的动词-名词结构,缺乏面向对象的层级关系
开发语言·后端·rust
古译汉书4 小时前
嵌入式铁头山羊stm32-ADC实现定时器触发的注入序列的单通道转换-Day26
开发语言·数据结构·stm32·单片机·嵌入式硬件·算法
DreamLife☼4 小时前
Qt 中的 Q_OBJECT 宏详解 —— 从源码到底层机制的全面剖析
qt·信号·qml·q_object··rtti·运行时类型信息
计算机毕业设计木哥4 小时前
计算机毕设选题:基于Python+Django的B站数据分析系统的设计与实现【源码+文档+调试】
java·开发语言·后端·python·spark·django·课程设计
陈陈爱java5 小时前
Spring八股文
开发语言·javascript·数据库
歪歪1005 小时前
qt creator新手入门以及结合sql server数据库开发
c语言·开发语言·后端·qt·数据库开发
@大迁世界5 小时前
用 popover=“hint“ 打造友好的 HTML 提示:一招让界面更“懂人”
开发语言·前端·javascript·css·html
星哥说事5 小时前
Python自学12 — 函数和模块
开发语言·python
拾忆,想起5 小时前
Redis复制延迟全解析:从毫秒到秒级的优化实战指南
java·开发语言·数据库·redis·后端·缓存·性能优化