Qt 事件机制全解析:从事件处理到事件过滤器

📑 目录

  • [🎯 1. Qt 事件](#🎯 1. Qt 事件)
    • [💡 1.1 事件介绍](#💡 1.1 事件介绍)
    • [🛠️ 1.2 事件的处理](#🛠️ 1.2 事件的处理)
    • [⌨️ 1.3 按键事件](#⌨️ 1.3 按键事件)
      • [1.3.1 单个按键](#1.3.1 单个按键)
      • [1.3.2 组合按键](#1.3.2 组合按键)
    • [🖱️ 1.4 鼠标事件](#🖱️ 1.4 鼠标事件)
      • [1.4.1 鼠标单击事件](#1.4.1 鼠标单击事件)
      • [1.4.2 鼠标释放事件](#1.4.2 鼠标释放事件)
      • [1.4.3 鼠标双击事件](#1.4.3 鼠标双击事件)
      • [1.4.4 鼠标移动事件](#1.4.4 鼠标移动事件)
      • [1.4.5 滚轮事件](#1.4.5 滚轮事件)
    • [⏱️ 1.5 定时器](#⏱️ 1.5 定时器)
      • [1.5.1 QTimerEvent 类](#1.5.1 QTimerEvent 类)
      • [1.5.2 QTimer 类](#1.5.2 QTimer 类)
      • [1.5.3 获取系统日期及时间](#1.5.3 获取系统日期及时间)
    • [📬 1.6 事件分发器](#📬 1.6 事件分发器)
      • [1.6.1 概述](#1.6.1 概述)
      • [1.6.2 事件分发器工作原理](#1.6.2 事件分发器工作原理)
    • [🛡️ 1.7 事件过滤器](#🛡️ 1.7 事件过滤器)
  • [📝 总结](#📝 总结)
  • [🎯 经典面试题](#🎯 经典面试题)

🎯 1. Qt 事件

💡 1.1 事件介绍

事件 是应用程序内部或者外部产生的事情或者动作的统称。在 Qt 中使用一个对象来表示一个事件,所有的 Qt 事件均继承于抽象类 QEvent。事件是由系统或者 Qt 平台本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。

一些事件是在用户操作时发出,如键盘事件、鼠标事件等;另一些事件则是由系统本身自动发出,如定时器事件。

🐼信号 VS 事件

信号槽:用户进行的各种操作,都可能会产生信号,可以给某个信号指定槽函数,当信号触发时,就能够自动的执行到对应的槽函数。槽函数类似于回调函数 。

事件非常类似:用户进行的各种操作,也会产生事件,程序员同样可以给事件关联对应的处理函数(处理的逻辑),当事件被触发时,就能够执行到对应的代码。

事件本身是操作系统提供的机制,Qt也同样把操作系统机制进行了封装,拿

到了Qt中,但是事件对应的代码编写起来不方便,于是Qt对于事件又进行了进一步的封装,就得到了信号槽

信号槽机制就是对于事件的进一步封装,事件是信号槽的底层机制。
📈在实际开发过程中,绝大多数和用户进行的交互都是通过"信号槽"来完成的,有些特殊情况下,信号槽不一定能搞定,此时就需要重写事件。

常见事件描述:

事件名称 描述
🖱️ 鼠标事件 鼠标左键、鼠标右键、鼠标滚轮,鼠标的移动,鼠标按键的按下和松开
⌨️ 键盘事件 按键类型、按键按下、按键松开
⏰ 定时器事件 定时时间到达
🚪 进入离开事件 鼠标的进入和离开
🎡 滚轮事件 鼠标滚轮滚动
🖥️ 绘屏事件 重绘屏幕的某些部分
👁️ 显示隐藏事件 窗口的显示和隐藏
📍 移动事件 窗口位置的变化
🪟 窗口事件 是否为当前窗口
📐 大小改变事件 窗口大小改变
🔍 焦点事件 键盘焦点移动
📦 拖拽事件 用鼠标进行拖拽

🛠️ 1.2 事件的处理

事件处理一般常用的方法为:重写相关的 Event 函数。

在 Qt 中,几乎所有的 Event 函数都是虚函数,所以可以重新实现。如:在实现鼠标的进入和离开事件时,直接重新实现 enterEvent() 和 leaveEvent() 即可。

示例1:鼠标进入/离开事件

  1. 新建 Qt 项目,基类选择 QWidget,同时勾选 UI 界面文件。

  2. 设计 UI 文件,放置一个 Label 控件。

  3. 在项目中新添加一个类:MyLabel;

    先选中项⽬名称 QEvent,点击⿏标右键,选择 add new ... ,弹出如下对话框:

  4. 选择:Choose ... ,弹出如下界⾯:

  5. 在 mylabel.h 中重写 enterEvent() 方法。

cpp 复制代码
#ifndef MYLABEL_H
#define MYLABEL_H

#include <QWidget>

class mylabel : public QWidget
{
    Q_OBJECT
public:
    explicit mylabel(QWidget *parent = nullptr);

    //鼠标进入事件
    void enterEvent(QEvent* event);

signals:

};

#endif // MYLABEL_H
  1. 在 UI 文件中选中 Label,右键 ---> 提升为 MyLabel。
  2. 当点击 "提升为... " 之后,弹出如下对话框:
  3. 修改基类:
cpp 复制代码
//myLabel.h
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QWidget>
#include<QLabel>
class mylabel : public QLabel
{
    Q_OBJECT
public:
    explicit mylabel(QWidget *parent = nullptr);
    //鼠标进入事件
    void enterEvent(QEvent* event);
signals:
};
#endif // MYLABEL_H

//myLabel.cpp
#include "mylabel.h"
#include<QDebug>
mylabel::mylabel(QWidget *parent) : QLabel(parent)
{

}

void mylabel::enterEvent(QEvent *event)
{
    qDebug()<<"鼠标进入了标签框内";
}
  1. 执行效果:当鼠标进入标签时,在应用程序输出栏中打印"鼠标进入"。

    示例2:鼠标点击事件

  2. 在 mylabel.h 中声明 mousePressEvent() 方法。

cpp 复制代码
#ifndef MYLABEL_H
#define MYLABEL_H

#include<QLabel>
class myLabel : public QLabel
{
    Q_OBJECT
public:
    explicit myLabel(QWidget *parent = nullptr);
    //鼠标进入事件
    void enterEvent(QEvent* event);
    //鼠标移动事件
    void mousePressEvent(QMouseEvent* ev);
signals:
};

#endif // MYLABEL_H
  1. 在 mylabel.cpp 中重写 mousePressEvent() 方法。
cpp 复制代码
#include "mylabel.h"
#include<QDebug>
#include<QMouseEvent>
myLabel::myLabel(QWidget *parent) : QLabel(parent)
{

}

void myLabel::enterEvent(QEvent *event)
{
    qDebug()<<"鼠标进入标签框内";
}

void myLabel::mousePressEvent(QMouseEvent *ev)
{
    //Qt中的格式化字符串
    QString str = QString("鼠标按下了x = %1,y = %2").arg(ev->x()).arg(ev->y());
    qDebug()<<str;
}
  1. 实现效果:鼠标左键点击时,打印对应的坐标值;鼠标右键点击时,打印基于屏幕的坐标。
cpp 复制代码
void myLabel::mousePressEvent(QMouseEvent *ev)
{
    //鼠标左键
    if(ev->button() == Qt::LeftButton)
    {
        //x,y为基于窗口的坐标
        QString str1 = QString("鼠标左键:x = %1,y = %2").arg(ev->x()).arg(ev->y());
        qDebug()<<str1.toUtf8().data();
    }
    //鼠标右键
    if(ev->button() == Qt::RightButton)
    {
        //globalX和globalY是仅用于屏幕的坐标
        QString str2 = QString("鼠标右键:x = %1,y = %2").arg(ev->globalX()).arg(ev->globalY());
        qDebug()<<str2.toUtf8().data();
    }
}

⌨️ 1.3 按键事件

Qt 中的按键事件是通过 QKeyEvent 类来实现的。当键盘上的按键被按下或者被释放时,键盘事件便会触发。

1.3.1 单个按键

示例: 当某个按键被按下时,输出"某个按键被按下了"。

  1. 在头文件 widget.h 中声明虚函数 keyPressEvent()。
cpp 复制代码
#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
  1. 在 widget.cpp 文件中重写 keyPressEvent() 虚函数。
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QKeyEvent>
#include<QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::KeyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_A)
    {
        qDebug()<<"A按键被按下了!";
    }
}
1.3.2 组合按键

Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。在 Qt 中,键盘事件可以与修改键⼀起使⽤,以实现⼀些复杂的交互操作。KeyboardModifier 中修改键的具体描述如下:

枚举值 描述
Qt::NoModifier 无修改键
Qt::ShiftModifier Shift 键
Qt::ControlModifier Ctrl 键
Qt::AltModifier Alt 键
Qt::MetaModifier Meta 键(Windows 上指 Windows 键,macOS 上指 Command 键)
Qt::KeypadModifier 使用键盘上的数字键盘进行输入时,Num Lock 键处于打开状态
Qt::GroupSwitchModifier 用于在输入法组之间切换

⽰例:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QKeyEvent>
#include<QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::KeyPressEvent(QKeyEvent *event)
{
    if(event->modifiers() == Qt::ControlModifier)//判断ctrl键是否被按下了
    {
        if(event->key() == Qt::Key_A)//判断A是否被按下了
        {
            qDebug()<<"ctrl + A 被按下了";
        }
    }
}

🖱️ 1.4 鼠标事件

在 Qt 中,鼠标事件是用 QMouseEvent 类来实现的。当在窗口中按下鼠标或者移动鼠标时,都会产生鼠标事件。利用 QMouseEvent 类可以获取鼠标的哪个键被按下了以及鼠标的当前位置等信息。

1.4.1 鼠标单击事件

鼠标按下是通过虚函数 mousePressEvent() 来捕获的。

cpp 复制代码
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event)

鼠标左右键及滚轮的表示:

枚举值 描述
Qt::LeftButton 鼠标左键
Qt::RightButton 鼠标右键
Qt::MidButton 鼠标滚轮

⽰例1:⿏标左键或者右键或者滚轮

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键被按下了";
    }else if(event->button() == Qt::RightButton)
    {
        qDebug()<<"鼠标右键被按下了";
    }else if(event->button() == Qt::MidButton)
    {
        qDebug()<<"鼠标滚轮被按下了";
    }
}
1.4.2 鼠标释放事件

鼠标释放事件是通过虚函数 mouseReleaseEvent() 来捕获的。mouseReleaseEvent() 函数原型如下:

cpp 复制代码
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event)

示例:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键被释放了";
    }
    else if(event->button() == Qt::RightButton)
    {
        qDebug()<<"鼠标右键被释放了";
    }
}

效果如下:

1.4.3 鼠标双击事件

鼠标双击事件是通过虚函数 mouseDoubleClickEvent() 来实现的。mouseDoubleClickEvent()函数原型如下:

cpp 复制代码
[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event)

示例:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}
Widget::~Widget()
{
    delete ui;
}
void Widget::mouseDoubleClickEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键被双击";
    }else if(event->button() == Qt::RightButton)
    {
        qDebug()<<"鼠标右键被双击";
    }
}

执行结果如下:

1.4.4 鼠标移动事件

鼠标移动事件是通过虚函数 mouseMoveEvent() 来实现的。同时为了实时捕获鼠标位置信息,需要通过函数 setMouseTracking() 来追踪鼠标的位置。

cpp 复制代码
//mouseMoveEvent()函数原型
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event)
//setMouseTracking()函数原型
void setMouseTracking(bool enable)

说明: setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获鼠标位置信息。否则只有当鼠标按下时才能捕获其位置信息。

示例:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QMouseEvent>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置鼠标最终状态
    setMouseTracking(true);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug()<<"[" <<event->x() << "," <<event->y() << "]";
}

执行结果如下:

1.4.5 滚轮事件

在 Qt 中,鼠标滚轮事件是通过 QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获取。

cpp 复制代码
//delta() 函数原型
int QGraphicsSceneWheelEvent::delta() const

其中返回值代表滚轮滑动的距离 。正数 表示滚轮相对于用户向前 滑动,负数 表示滚轮相对于用户向后 滑动。

示例:

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);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::wheelEvent(QWheelEvent *event)
{
    static int x = 0;
    x += event->delta();//±120

    if(event->delta() > 0){
        qDebug()<<"滚轮向前"<<x;
    }
    else
    {
        qDebug()<<"滚轮往后"<<x;
    }
}

执行结果如下:

⏱️ 1.5 定时器

Qt 中在进行窗口程序的处理过程中,经常要周期性的执行某些操作,或者制作一些动画效果,使用定时器就可以实现。所谓定时器就是在间隔一定时间后,去执行某一个任务。

Qt 中的定时器分为 QTimerEvent 和 QTimer 这 2 个类。

  • QTimerEvent 类 :用来描述一个定时器事件。在使用时需要通过 startTimer() 函数来开启一个定时器,这个函数需要输入一个以毫秒为单位的整数作为参数来表明设定的时间,它返回的整型值代表这个定时器。当定时器溢出时(即定时时间到达)就可以在 timerEvent() 函数中获取该定时器的编号来进行相关操作。
  • QTimer 类:用来实现一个定时器,它提供了更高层次的编程接口,如:可以使用信号和槽,还可以设置只运行一次的定时器。

💡 笔记: 在 QTimer 背后是 QTimerEvent 定时器事件进行支撑的。QObject 提供了一个 timerEvent 这个函数。startTimer 启动定时器,killTimer 关闭定时器。

1.5.1 QTimerEvent 类

示例: 在 UI 界面上放置两个 Label 控件,一个让其 1 秒数字累加一次,一个让其 2 秒数字累加一次。

  1. 新建项目,在 UI 界面文件放置两个 Label 控件。

  2. 在 widget.h 头文件中声明 timerEvent() 函数,并定义两个整型变量。

cpp 复制代码
#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) override;

private:
    Ui::Widget *ui;

    int timer_1 = 0;
    int timer_2 = 0;
};
#endif // WIDGET_H
  1. 在 widget.cpp 文件中重写 timerEvent() 函数。
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QTimerEvent>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建窗口时就启动定时器
    timer_1 = startTimer(1000);//单位为毫秒
    timer_2 = startTimer(2000);

}

Widget::~Widget()
{
    delete ui;
}

void Widget::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == timer_1)
    {
        static int num1 = 1;
        //label每隔1秒加一次
        ui->label->setText(QString::number(num1++));
    }

    if(event->timerId() == timer_2)
    {
        static int num2 = 1;
        //label_2每隔2秒加一次
        ui->label_2->setText(QString::number(num2++));
    }
}

执行结果如下:

1.5.2 QTimer 类

示例: 在 UI 界面放置一个 Label 标签,两个按钮("开始"和"停止")。点击"开始"按钮时,开始每隔 1 秒计数一次;点击"停止"按钮时,暂停计数。

  1. 设计 UI 界⾯如下:
  2. 在 "widget.cpp" ⽂件中实现对应功能;
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);

    connect(ui->start,&QPushButton::clicked,[=]()
    {
       timer->start(1000);
    });

    connect(timer,&QTimer::timeout,[=]()
    {
        static int num = 1;
        ui->label->setText(QString::number(num++));
    });

    connect(ui->stop,&QPushButton::clicked,[=]()
    {
        timer->stop();
    });
}

Widget::~Widget()
{
    delete ui;
}

执行结果如下:

1.5.3 获取系统日期及时间

在 Qt 中,获取系统的日期及实时时间可以通过 QTimer 类和 QDateTime 类。QDateTime 类提供了字符串格式的时间,字符串形式的时间输出格式由 toString() 方法中的 format 参数列表决定。

⽰例:获取系统⽇期及实时时间;

  1. 设计UI界⾯⽂件;放置⼀个 Label控件,⽤来显⽰⽇期及时间,放置两个按钮:"开始" 和 "停⽌" ;
  2. 在 "widget.h" 头⽂件中声明更新时间的槽函数;
cpp 复制代码
#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();
public slots:
    void TimeUpDate();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

3、在 "widget.cpp" ⽂件中实现对应功能;

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
#include<QDateTime>
#include<QLabel>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建一个定时器
    QTimer* time = new QTimer(this);

    connect(ui->start,&QPushButton::clicked,[=](){
        time->start();
    });

    connect(ui->stop,&QPushButton::clicked,[=](){
        time->stop();
    });

    connect(time,&QTimer::timeout,this,&Widget::TimeUpDate);
}
Widget::~Widget()
{
    delete ui;
}
void Widget::TimeUpDate()
{
    QString str = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    ui->label->setText(str);
}

📬 1.6 事件分发器

1.6.1 概述

在 Qt 中,事件分发器(Event Dispatcher)是一个核心概念,用于处理 GUI 应用程序中的事件。事件分发器负责将事件从一个对象传递到另一个对象,直到事件被处理或被取消。每个继承自 QObject 类的对象都可以在本类中重写 bool event(QEvent *e) 函数,来实现相关事件的捕获和拦截。

1.6.2 事件分发器工作原理

在 Qt 中,我们发送的事件都是传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函数。所有的事件都会进入到这个函数里面,那么我们处理事件就要重写这个 event() 函数。event() 函数本身不会去处理事件,而是根据**事件类型(type 值)**调用不同的事件处理函数。

💡 笔记: 事件分发器/事件过滤属于 Qt 事件机制背后的逻辑。Qt 也把这部分内容提供了一些 API 让程序员有更多的操作空间。事件分发可以直接重写 event 函数,细节获取到所有的事件,但杀伤力比较大,不当使用可能会对现有逻辑(现有的事件体系)造成一些负面影响。

如上图,事件分发器⽤于分发事件。在此过程中,事件分发器也可以做拦截操作。

事件分发器主要是通过 bool event(QEvent *e) 函数来实现。其返回值为布尔类型,若为 true,代表拦截,不向下分发。

⽰例:

  1. 在 "widget.h" 头⽂件中声明 ⿏标点击事件 和 事件分发器;
cpp 复制代码
#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 mousePressEvent(QMouseEvent *event) override;

    //通过 事件分发器 拦截 鼠标按下事件
    bool event(QEvent* event) override;

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

2、在 "widget.cpp" ⽂件中实现 ⿏标点击事件 和 拦截事件;

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QMouseEvent>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}
Widget::~Widget()
{
    delete ui;
}
//具体的事件处理函数 - 由于鼠标左键点击事件的处理函数被事件分发器拦截了
//所以当我们在窗口中点击鼠标左键时,event函数会被执行,而不会执行
//mousePressEvent()函数!
void Widget::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键被按下了";
    }
}
bool Widget::event(QEvent *event)
{
    if(event->type() == QEvent::MouseButtonPress)
    {
        qDebug()<<"Event 中鼠标被按下了!";
        return true;//返回true,代表拦截不向下分发
    }
    //其他事件交给父类处理(默认处理动作)
    return QWidget::event(event);
}

🛡️ 1.7 事件过滤器

在 Qt 中,一个对象可能经常要查看或拦截另外一个对象的事件,如对话框想要拦截按键事件,不让别的组件接收到,或者修改按键的默认值等。

通过上面的学习,我们已经知道,Qt 创建了 QEvent 事件对象之后,会调用 QObject 的 event() 函数处理事件的分发。显然,我们可以在 event() 函数中实现拦截的操作。由于 event() 函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个 event() 函数。这当然相当麻烦,更不用说重写 event() 函数还得小心一堆问题。

好在 Qt 提供了另外一种机制来达到这一目的:事件过滤器 。事件过滤器是在应用程序分发到 event 事件分发器之前,再做一次更高级的拦截。

事件过滤器的一般使用步骤:

  • 安装事件过滤器;
  • 重写事件过滤器函数:eventFilter()。

⽰例:

  1. 新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件;
  2. 设计 UI ⽂件,添加一个label标签,然后给label加上一个边框,便于显示。
  3. 在项⽬新添加⼀个类:MyLabel;先选中项⽬名称 QEvent,点击⿏标右键,选择 add new ... ,弹出如下对话框,然后选择选择:Choose ... ,此时项⽬中会新添加以下两个⽂件。
  4. 在 UI ⽂件中选中 Label,右键 ------> 提升为...
  5. 当点击 "提升为... " 之后,弹出如下对话框:

6、在 "mylabel.h" 中声明 ⿏标点击事件 和 事件分发器;

cpp 复制代码
#ifndef MYLABEL_H
#define MYLABEL_H

#include <QObject>
#include <QWidget>
#include<QLabel>
class mylabel : public QLabel
{
    Q_OBJECT
public:
    explicit mylabel(QWidget *parent = nullptr);

    //鼠标点击事件
    void mousePressEvent(QMouseEvent *ev) override;

    //事件分发器
    bool event(QEvent* e) override;

signals:

};

#endif // MYLABEL_H
  1. 在 "mylabel.cpp" ⽂件中实现⿏标点击事件和事件分发器;
cpp 复制代码
#include "mylabel.h"
#include<QMouseEvent>
#include<QDebug>
#include<>
mylabel::mylabel(QWidget *parent) : QLabel(parent)
{

}

void mylabel::mousePressEvent(QMouseEvent *ev)
{
    QString str = QString("鼠标按下:x = %1,y = %2").arg(ev->x()).arg(ev->y());
    qDebug()<<str.toUtf8().data();
}

bool mylabel::event(QEvent *e)
{
    //如果时鼠标按下,在event事件分发时做拦截操作
    if(e->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent* event = static_cast<QMouseEvent*>(e);
        QString str = QString("Event函数中鼠标按下:x = %1,y = %2").arg(event->x()).arg(event->y());
        qDebug()<<str.toUtf8().data();
        return true;//返回true,代表用户自己处理,不向下分发
    }
    //其他的事件交给父类处理
    return QLabel::event(e);
}
  1. 在 "widget.h" 头⽂件中声明事件过滤器函数;
cpp 复制代码
#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();

    //声明eventfilter事件拦截器
    bool eventFilter(QObject* obj,QEvent* event) override;

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
  1. 在 "widget.cpp" ⽂件中实现事件过滤器的两个步骤;
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QMouseEvent>
#include<QDebug>
#include<QEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //步骤1:给label安装事件过滤器,this:当前窗口安装事件过滤器
    ui->label->installEventFilter(this);
}

Widget::~Widget()
{
    delete ui;
}

//步骤2:重写eventFilter事件
bool Widget::eventFilter(QObject *obj, QEvent *event)
{
    if(obj == ui->label)//判断控件是否是被过滤事件
    {
        if(event->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent* ev = static_cast<QMouseEvent*>(event);
            QString str = QString("事件过滤器中鼠标按下:x = %1,y = %2").arg(ev->x()).arg(ev->y());
            qDebug()<<str.toUtf8().data();
            return true;
        }
    }
    //其他的交给父类处理
    return QWidget::eventFilter(obj,event);
}

执⾏结果如下所⽰:


📝 总结

事件是应用程序内部或者外部产生的事情或者动作的统称,所有 Qt 事件均继承于抽象类 QEvent。事件处理一般常用的方法是重写相关的 Event 函数,因为 Qt 中几乎所有的 Event 函数都是虚函数,可以重新实现。

  • 事件介绍 :事件分为用户操作触发(如键盘、鼠标事件)和系统自动触发(如定时器事件)。信号槽机制是对于事件的进一步封装,事件是信号槽的底层机制。
  • 事件的处理 :通过重写 enterEvent()、leaveEvent()、mousePressEvent() 等虚函数来实现。
  • 按键事件 :通过 QKeyEvent 类实现,支持单个按键(如 Qt::Key_A)和组合按键(配合 Qt::KeyboardModifier 修改键,如 Ctrl + A)。
  • 鼠标事件 :通过 QMouseEvent 类实现,包括单击(mousePressEvent())、释放(mouseReleaseEvent())、双击(mouseDoubleClickEvent())、移动(mouseMoveEvent(),需配合 setMouseTracking(true))以及滚轮事件(QWheelEvent,通过 delta() 获取滑动距离)。
  • 定时器 :分为 QTimerEvent(底层,通过 startTimer() 开启,在 timerEvent() 中处理)和 QTimer(高层,支持信号槽,可设置只运行一次)。在 QTimer 背后是 QTimerEvent 定时器事件进行支撑的。
  • 事件分发器 :所有事件都会传给 QObject 对象的 event() 函数,event() 函数根据**事件类型(type 值)**调用不同的事件处理函数。通过重写 bool event(QEvent *e) 可以实现拦截,返回 true 代表拦截,不向下分发。
  • 事件过滤器 :在事件分发到 event() 函数之前做更高级的拦截。使用步骤为:先安装事件过滤器(installEventFilter()),再重写 eventFilter() 函数。它可以避免重写多个 event() 函数。

🎯 经典面试题

1. 什么是 Qt 事件?Qt 中如何处理事件?

答: 事件是应用程序内部或者外部产生的事情或者动作的统称。在 Qt 中,所有事件均继承于抽象类 QEvent。事件处理一般常用的方法是重写相关的 Event 函数 ,因为 Qt 中几乎所有的 Event 函数都是虚函数,可以重新实现。例如,重写 mousePressEvent() 处理鼠标点击事件,重写 keyPressEvent() 处理按键事件。

2. 简述 Qt 事件分发器的工作原理。

答: 在 Qt 中,发送的事件都会传给 QObject 对象的 event() 函数。event() 函数本身不会去处理事件,而是根据**事件类型(type 值)**调用不同的事件处理函数。事件分发器主要是通过 bool event(QEvent *e) 函数来实现,其返回值为布尔类型,若为 true,代表拦截,不向下分发。

3. 什么是事件过滤器?它和事件分发器有什么区别?

答: 事件过滤器是在应用程序分发到 event 事件分发器之前,再做一次更高级的拦截。使用步骤为:先安装事件过滤器,再重写 eventFilter() 函数。事件分发器是事件进入 event() 函数后的分发逻辑,而事件过滤器是在事件到达 event() 函数之前进行拦截,可以避免重写多个 event() 函数。

4. Qt 中 QTimer 和 QTimerEvent 有什么区别?

答: QTimerEvent 类用来描述一个定时器事件,使用时需要通过 startTimer() 函数开启定时器,并在 timerEvent() 函数中处理。QTimer 类提供了更高层次的编程接口,可以使用信号和槽,还可以设置只运行一次的定时器。


相关推荐
于指尖飞舞1 小时前
java面试题总结
java·开发语言
(Charon)2 小时前
【C++面试】vector底层原理:扩容、拷贝移动与迭代器失效
c++·面试
zhangzeyuaaa2 小时前
深入理解 Ruby 运算符:本质、分类、坑点与重载实战
开发语言·前端·ruby
一木 之林2 小时前
DeepSeek Agent 开发(一)
开发语言·前端·javascript
朔北之忘 Clancy2 小时前
青少年软编等考七级题解目录
开发语言·c++·青少年编程·gesp·csp·信奥赛·noi
ss2732 小时前
AI全栈实战 | 3.2-01 Python 基础:四大数据容器怎么选,推导式为什么是 Pythonic 的灵魂
开发语言·人工智能·python
Mr_Macallon3 小时前
C++回顾(基础)(01)
c语言·开发语言·c++·学习
未来之窗软件服务3 小时前
Go 利器 GoReSym:剥离符号恢复 Go 二进制元数据—东方仙盟
开发语言·golang·仙盟创梦ide·东方仙盟
考研保研资料分享3 小时前
电气工程保研经验:夏令营失利后上岸上交,科研追问与九推复习(2024)
面试