【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.10 总结QT中的事件传递流程

本节对应的视频讲解:B_站_链_接

【QT开发笔记-基础篇】 第4章 事件 4.10 总结事件传递流程(1) 事件处理函数接受还是忽略


本章要实现的整体效果如下:

在这里插入图片描述



事件传递总流程图,如下:
传递总流程-明王绘制

这张图是不是还不太明白??
别担心,本节课彻底搞懂事件的传递流程!!!


程序的入口 main() 函数的最后都会调用 QApplication 类的 exec() 函数。它会使 Qt 应用程序进入事件循环。这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt 便会构建一个相应的QEvent 子类的对象来表示它,然后将它传递给相应的 QObject 对象或其子对象。下面通过例子来看一下Qt中的事件传递过程。

Qt 中的事件由特定的 QEvent 子类来表示,比如鼠标事件对应的子类为 QMouseEvent ,键盘事件对应的子类为 QKeyEvent 等。

通常,一个事件包含多个事件类型,比如鼠标事件包含鼠标按下、鼠标移动和鼠标释放三个事件类型。键盘事件包含键盘按下和键盘释放两个事件类型。这些事件类型都由 QEvent 类的枚举型 QEvent::Type 来表示,其中包含了 一百多种事件类型,可以在 QEvent 类的帮助文档中查看。

虽然 QEvent 的子类用来表示一个事件,那么应该怎样来处理一个事件呢?在 QCoreApplication 类的 notify() 函数的帮助文档处给出了 5 种处理事件的方法:

  • 方法一:重新实现部件的 paintEvent()mousePressEvent() 等事件处理函数。这是最常用的一种方法,不过它只能用来处理特定部件的特定事件。
  • 方法二:重新实现 notify() 函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。
  • 方法三:向 QApplication 对象上安装事件过滤器。因为一个程序只有一个 QApplication 对象,所以这样实现的功能与使用 notify() 函数是相同的,优点是可以同时处理多个事件。
  • 方法四:重新实现 event() 函数。QObject 类的 event() 函数可以在事件到达默认的事件处理函数之前获得该事件。
  • 方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。

**在实际编程中,最常用的是方法一,其次是方法五。**因为方法二需要继承自 QApplication 类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。

接下来通过一个案例,来演示事件的传递流程。

1. 事件处理函数

自定义一个标签控件 PropagateLabel,让它继承自 QLabel,然后重写父类的 mousePressEvent()

1.1 添加自定义控件类 PropagateLabel

首先,在左侧项目文件名上右键,然后选择 “添加新文件”,选择 “C++ Class”,如下:

添加类

新建类文件信息如下:

类信息

然后,把父类修改为 QLabel

来到 propagatelabel.h 将父类由 QWidget 修改为 QLabel,如下:

#include <QLabel>

class PropagateLabel : public QLabel
{
    // ...
};

来到 propagatelabel.cpp 将父类由 QWidget 修改为 QLabel,如下:

#include "propagatelabel.h"

PropagateLabel::PropagateLabel(QWidget* parent) : QLabel{parent}
{
}

1.2 重写 mousePressEvent()

首先,来到 propagatelabel.h ,声明它:

class PropagateLabel : public QLabel
{
private:
    void mousePressEvent(QMouseEvent* event);
};

然后,来到 propagatelabel.cpp 实现它:

#include <QDebug>

void PropagateLabel::mousePressEvent(QMouseEvent* event)
{
    qDebug() << "PropagateLabel::mousePressEvent";
}

1.3 将 PropagateLabel 显示到界面

首先,在 propagate_widget.h 中,声明 PropagateLabel 类型成员变量:

#include "propagatelabel.h"

class PropagateWidget : public QWidget
{
private:
    PropagateLabel* lbl;
};

然后,来到 propagate_widget.cpp ,在构造函数中添加 PropagateLabel 控件,如下:

PropagateWidget::PropagateWidget(QWidget* parent) : QWidget{parent}
{
    QVBoxLayout* verticalLayout = new QVBoxLayout(this);
    verticalLayout->setSpacing(0);
    verticalLayout->setContentsMargins(0, 0, 0, 0);

    // 1. 添加一个自定义的标签 LabelX
    lbl = new PropagateLabel(this);
    lbl->setText("");
    lbl->setFrameShape(QFrame::Box);
    lbl->setFixedHeight(50);
    lbl->setAlignment(Qt::AlignCenter);
    lbl->setStyleSheet("background-color: red;color: white;font-size: 25px");
    verticalLayout->addWidget(lbl);
}

此时运行程序,效果如下:

添加标签

此时,点击标签,就会执行自定义的 PropagateLabel 控件的 mousePressEvent() 函数

到目前为止,都是前面讲解过的内容。


1.4 接受/忽略事件

PropagateLabel 控件的 mousePressEvent() 函数,处理事件后,可以决定是 “接受”( accept ) 还是 “忽略”( ignore) 这个事件。

Qt 框架传递过来的这个 event, 它有一个标志位 m_accept

  • 把该事件的 m_accept 标志设置为 true

    该事件的传递,到此为止。则该事件不会传递给其父控件,也就是 PropagateWidget

  • 把该事件的 m_accept 标志设置为 false

    该事件会再传递给其父控件,也就是 PropagateWidget


那么,如何设置 m_accept 标志位呢?

  • 忽略事件(事件继续传递给父控件)
方法一:
event->ignore();
跳转到 ignore() 的实现,可见它就是直接设置 m_accept 为 false

方法二:
调用父类的实现
// QLineEdit 是直接继承自父类 QWidget 中的 keyReleaseEvent
QLineEdit::keyReleaseEvent(event); 

而父类 QWidget 中的 keyReleaseEvent 实现,就是直接调用 event->ignore() 如下:
void QWidget::keyReleaseEvent(QKeyEvent *event)
{
	event->ignore();
}
    
可见,不管是方法一,还是方法二,最终都是设置 event 中的 m_accept 标志位。

  • 接受事件(事件到此为止,不再传递给其父控件)
event->accept();
跳转到 accept() 的实现,可见它就是直接设置 m_accept 为 true

然而,由于该标志位刚传递过来的值为 true(可以加打印查看),因此不需要显示地调用 accept() 

接下来,看PropagateLabel 控件的鼠标按下事件能否传递到其父控件 PropagateWidget

首先,来到 propagate_widget.h中,声明 mousePressEvent() 函数,如下:

class PropagateWidget : public QWidget
{
private:
    void mousePressEvent(QMouseEvent* event);
};

然后,来到 propagate_widget.cpp中,实现 mousePressEvent() 函数,如下:

void PropagateWidget::mousePressEvent(QMouseEvent* event)
{
    qDebug() << "PropagateWidget::mousePressEvent";
}

此时可以修改 propagatelabel.cpp 中的 mousePressEvent() 函数,来决定事件是否继续向上传递:

void PropagateLabel::mousePressEvent(QMouseEvent* event)
{
    qDebug() << "PropagateLabel::mousePressEvent";

    // 接受事件(事件到此为止,不再传递给其父控件)
    // 方法一:
    //    event->accept();

    // 方法二:什么都不用写。因为传递过来的 event 其 accept 标志位默认为 true,可打印验证
    //    qDebug() << event->isAccepted();

    // 忽略事件(事件继续传递给父控件)
    event->ignore();
}

以上忽略事件之后,事件会传递到propagate_widget.cppmousePressEvent() 函数,打印如下:

忽略事件


2. 事件分发函数 event()

通过查看Qt源码 qwidget.cpp,可以得知:

event() 中,通过区分不同的事件类型来调用特定的事件处理函数

也就是说,可以在事件到达默认的事件处理函数之前,在 event() 中截获

bool QWidget::event(QEvent *event)
{
    switch (event->type()) {
  
        case QEvent::MouseMove:
            mouseMoveEvent((QMouseEvent*)event);
            break;
        case QEvent::MouseButtonPress:
            mousePressEvent((QMouseEvent*)event);
            break;
        case QEvent::MouseButtonRelease:
            mouseReleaseEvent((QMouseEvent*)event);
            break;
        case QEvent::MouseButtonDblClick:
            mouseDoubleClickEvent((QMouseEvent*)event);
            break;
        case QEvent::Drop:
            dropEvent((QDropEvent*) event);
            break;
        case QEvent::DragEnter:
            dragEnterEvent((QDragEnterEvent*) event);
            break;
        case QEvent::DragMove:
            dragMoveEvent((QDragMoveEvent*) event);
            break;
        case QEvent::DragLeave:
            dragLeaveEvent((QDragLeaveEvent*) event);
            break;
        default:
            return QObject::event(event);
    }
    return true;
}

2.1 重写 event() 函数

接下来演示 event() 函数如何使用

首先,在 propagate_label.h 中,声明 event() 函数:

class PropagateLabel : public QLabel
{
private:
    bool event(QEvent* e);
};

然后,在 propagate_label.cpp 中,实现 event() 函数:

bool PropagateLabel::event(QEvent* e)
{
    if ( e->type() == QEvent::MouseButtonPress ) {
        qDebug() << "PropagateLabel::event";
    }

    return QLabel::event(e); // 调用父类的 event() 函数
}

这里,仅仅是根据事件类型,做了一个打印。

最后一般调用父类的 event() 函数,这样可以接着把事件分发到特定的事件处理函数。如果直接返回 true false,就无法将事件传递给特定的事件处理函数!

此时运行,控制台打印如下:

事件分发-传递给事件处理函数

事件传递流程:

  • 事件首先到达 PropagateLabel 中的 event() 函数
  • 由于上一步的 event() 函数,调用了父类的 event() 函数,因此事件被分发到 PropagateLabel 中的 mouseMoveEvent() 函数
  • 由于 PropagateLabel 中的 mouseMoveEvent() 函数,忽略了该事件,因此事件又被传递到 PropagateWidget 中的 mouseMoveevent() 函数

2.2 event() 返回 true

event() 函数返回一个 bool 值

通常直接 return QLabel::event(e);,这样可以接着把事件分发到特定的事件处理函数

如果不调用父类的 event() 函数,而是直接返回 true 或者 false,会有什么效果呢?

bool PropagateLabel::event(QEvent* e)
{
    if ( e->type() == QEvent::MouseButtonPress ) {
        qDebug() << "PropagateLabel::event";
        return true;
    }

    return QLabel::event(e);
}

以上对于 QEvent::MouseButtonPress 事件,直接返回 true,表示事件被识别,传递到此为止,不会接着传递。

此时运行,控制台打印如下:

事件分发-到此为止


2.3 event() 返回 false

event() 函数返回一个 bool 值

通常直接 return QLabel::event(e);,这样可以接着把事件分发到特定的事件处理函数

如果不调用父类的 event() 函数,而是直接返回 true 或者 false,会有什么效果呢?

bool PropagateLabel::event(QEvent* e)
{
    if ( e->type() == QEvent::MouseButtonPress ) {
        qDebug() << "PropagateLabel::event";
        return false;
    }

    return QLabel::event(e);
}

以上对于 QEvent::MouseButtonPress 事件,直接返回 false,表示事件没有被识别,会接着传递。由于没有调用调用父类的 event() 函数,因此不会将事件分发到 PropagateLabelmousePressEvent(),而是直接传递给父控件的 mousePressEvent()

此时运行,控制台打印如下:

事件分发-传递给父控件


3. 事件过滤函数

前面讲解的事件处理函数比如 mouseMoveEvent() 函数,以及事件分发函数 event(),都是事件已近到达了控件,那么在事件到达控件之前,能不能提前拦截,或者说过滤呢?

答:可以,给控件安装事件过滤器。


首先,来到 propagate_widget.cpp 构造,为 PropagateLabel 安装事件过滤器:

PropagateWidget::PropagateWidget(QWidget* parent) : QWidget{parent}
{
    // ...
    
    lbl->installEventFilter(this);
}

然后,在 propagate_widget.h 中声明 eventFilter() 函数:

class PropagateWidget : public QWidget
{
private:
    bool eventFilter(QObject *watched, QEvent *event);
};

并在 propagate_widget.cpp 中实现:

bool PropagateWidget::eventFilter(QObject* watched, QEvent* event)
{
    if ( watched == lbl && event->type() == QEvent::MouseButtonPress ) {
        qDebug() << "PropagateWidget::eventFilter";
    }

    return QWidget::eventFilter(watched, event);
}

这里,仅仅是做了一个打印。

eventFilter() 返回一个 bool 值:

  • 返回 true:则事件到此为止,不再向下传递,也就是不再传递到对应的控件
  • 返回 false:则事件继续传递,也就是接着会传递到对应的控件

父类中 eventFilter() 函数,在 QObject 类中实现,源码对应 qobject.cpp(在 qwidget.cpp中没有实现):

bool QObject::eventFilter(QObject * /* watched */, QEvent * /* event */)
{
    return false;
}

可见,只是简单地返回了一个 false,表示让事件接着传递。

因此,上面的

return QWidget::eventFilter(watched, event);

等价于

return false;

此时运行,控制台打印如下:

整体传递流程


事件传递流程:

  • 事件首先到达 PropagateWidget 中的 eventFilter() 函数
  • 由于上一步的 eventFilter() 函数,不管是调用父类的 eventFilter() 函数,还是直接返回 false,都表示要继续事件的传递。因此事件被分发到 PropagateLabel 中的 event() 函数
  • 由于 PropagateLabel 中的 event() 函数,直接调用父类的 event() 函数,因此事件被分发到 PropagateLabel 中的 mouseMoveevent() 函数
  • 由于 PropagateLabel 中的 mouseMoveevent() 函数,忽略了该事件,因此事件又被传递到 PropagateWidget 中的 mouseMoveevent() 函数

4. 事件传递-画图演示

从Qt程序的入口开始:

#include "mywindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    // 1. QApplication 是 Qt 框架提供的应用程序类
    // 作用:负责 Qt 中事件的处理,比如鼠标的单击事件,键盘的输入事件等
    QApplication a(argc, argv);
    
    // 2. 创建自己的窗口对象,并调用其 show 方法,将窗口显示出来
    MyWindow w;
    w.show();
    
    // 3. 调用 QApplication 类的 exec 方法,应用程序就阻塞在这里,并不会退出,而是进入到事件循环的处理, 直到退出程序(比如点击了窗体右上角的关闭按钮)
    return a.exec();
}

可见程序启动后,就是进入一个事件的循环,事件的传递流程如下:

传递总流程-明王绘制

图示中除了 notify() 函数,其他都讲到了。

notify()QCoreApplication 类中的方法,QApplication 是其子类。main() 函数最后调用 return a.exec(),就进入了事件循环。

如果要重写 notify() 函数,需要自定义一个 QApplication 类。实际工作中,一般不会重写 notify() 函数,这里就略过!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/105693.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ基础篇 笔记

RabbitMQ 余额支付 同步调用 一步一步的来&#xff0c;支付业务写完后&#xff0c;如果之后加需求&#xff0c;还需要增加代码&#xff0c;不符合开闭原则。 性能上也有问题&#xff0c;openfeign是同步调用&#xff0c;性能太差。 同步调用耦合太多。 同步的优势是可以立…

matlab中narginchk函数用法及其举例

matlab中narginchk函数用法及其举例 narginchk在编写子函数程序时候&#xff0c;在验证输入参数数目方面具有重要作用&#xff0c;本博文讲一讲该函数的用法。 一、narginchk功能 narginchk的作用是验证输入参数数目。 二、语法 narginchk(minArgs,maxArgs)narginchk(minA…

windows服务器环境下使用php调用com组件

Office设置 安装 office2013 且通过正版激活码激活 在组件服务 计算机 我的电脑 DOM 中找到 Microsoft Word 97 - 2003 文档 服务&#xff0c;右键属性 身份验证调整为 无 在 标识中 调整为 交互式用户 php环境设置 开启com组件扩展 在php.ini中设置 extensionphp_com_dotn…

同范围中的嵌入式和单片机区别是什么?

今日话题&#xff0c;同范围中的嵌入式和单片机区别是什么&#xff1f;嵌入式系统和单片机不仅仅是软硬件的区别&#xff0c;更涉及到应用领域和功能特性的不同。嵌入式系统通常包括一个完整的计算机系统&#xff0c;其中包括处理器、内存、输入输出接口以及一个操作系统&#…

2023年中国消防报警设备市场规模现状及行业竞争趋势分析[图]

消防安全行业主要分为消防产品和消防工程两个子行业。消防产品又可细分成消防装备、消防报警、自动灭火、防火与疏散、通用与防烟排烟、消防供水等 6 大类&#xff0c;其中消防装备主要用于消防部队&#xff0c;其他 5 大类主要用于建筑物消防。 消防行业内容 资料来源&#x…

21款奔驰GLE450升级23P驾驶辅助 缓解开车疲劳

驾驶辅助和自动驾驶的区别就是需要人为去接管&#xff0c;虽然车辆会根据道路自己行驶&#xff0c;弯道上也能居中自动修正行驶&#xff0c;长时间不接管方向盘&#xff0c;系统会提示人为接管&#xff0c;这就是奔驰的23P驾驶辅助系统&#xff0c; 很多车友升级23P驾驶辅助系…

苍穹外卖-01

苍穹外卖-01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示&#xff1a; ​ 管理端-外卖商家使用 ​ 用户端-点餐用户使用 当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为一名…

用Visual Studio(VS)开发UNIX/Linux项目

目录 FTP是免不了的 正确设置头文件 组织项目结构 创建何种项目类型 FTP自动上传 大部分具有Windows开发经验的程序员会比较喜欢使用Visual Studio&#xff0c;而大部分Unix/Linux程序员则喜欢使用UltraEdit直接在主机上写代码。 为什么直接在主机上写代码呢&#xff0c;因…

Leo赠书活动-03期 【ChatGPT 驱动软件开发:AI 在软件研发全流程中的革新与实践 】

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

【JAVA学习笔记】45 - (35 - 43)第十章作业

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter10/src/com/yinhai/homework10 1.静态属性的共享性质 判断下列输出什么 public class HomeWork01 {public static void main(String[] args) {Car c new Car();//无参构造时改变color为red…

Redis快速上手篇(四)(Spring Cache,缓存配置)(注解方式)

Spring Cache 从3.1开始&#xff0c;Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的 使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。 使用Spring Cache需要我们做两方面…

聚观早报 |2024款飞凡R7官宣;小米14新配色材质

【聚观365】10月27日消息 2024款飞凡R7官宣 小米14新配色材质 金山办公2023第三季度业绩 IBM2023第三季度业绩 新东方2024财年第一季度业绩 2024款飞凡R7官宣 飞凡汽车官宣&#xff0c;2024款飞凡R7将于11月上市&#xff0c;新车将搭载飞凡巴赫座舱&#xff0c;同时超过1…

endnote设置

问题1&#xff1a;参考文献的tab太长 首先要在endnote里面这样设置&#xff0c;file->output->edit "XXX" 保存之后&#xff0c;在word更新目录。 在word里面设置悬挂缩进 结果&#xff1a; Endnote参考编号与参考文献距离太远怎么调整 endnote 文献对齐方式…

正点原子嵌入式linux驱动开发——外置RTC芯片PCF8563

上一章学习了STM32MP1内置RTC外设&#xff0c;了解了Linux系统下RTC驱动框架。一般的应用场合使用SOC内置的RTC就可以了&#xff0c;而且成本也低&#xff0c;但是在一些对于时间精度要求比较高的场合&#xff0c;SOC内置的RTC就不适用了。这个时候需要根据自己的应用要求选择合…

解决报错:gnutls_handshake() failed: The TLS connection was non-properly terminated.

执行git clone的时候&#xff0c;出现错误&#xff1a;gnutls_handshake() failed: The TLS connection was non-properly terminated. 如图&#xff1a; 解决方式&#xff1a; 两次重置代理&#xff1a;完美解决 git config --global --unset https.https://github.com.pro…

网络搭建和运维的基础题目

服务部分&#xff08;linux&#xff09; 实操部分 1.在任意文件夹下面创建形如 A/B/C/D 格式的文件夹系列。 [rootlocalhost ~]# mkdir -p A/B/C/D 2.在创建好的文件夹下面&#xff0c;A/B/C/D &#xff0c;里面创建文本文件 mkdir.txt [rootlocalhost ~]# cd A/B/C/D [r…

7.MySQL复合查询

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 复合查询 基本查询回顾 多表查询 自连接 子查询 单行子查询 多行子查询 多列子查询 在from子句中使用子查询 合并查询 union union all 实战OJ 复合查询 前面我们讲解的mysql表的查询都是对一张表进行查询…

jenkins、ant、selenium、testng搭建自动化测试框架

如果在你的理解中自动化测试就是在eclipse里面讲webdriver的包引入&#xff0c;然后写一些测试脚本&#xff0c;这就是你所说的自动化测试&#xff0c;其实这个还不能算是真正的自动化测试&#xff0c;你见过每次需要运行的时候还需要打开eclipse然后去选择运行文件吗&#xff…

竞赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

【方法】如何给PDF文件添加“打开密码”?

PDF文件可以在线浏览&#xff0c;但如果想要给文件添加“打开密码”&#xff0c;就需要用到软件工具&#xff0c;下面小编分享两种常用的工具&#xff0c;小伙伴们可以根据需要选择。 工具一&#xff1a;PDF编辑器 PDF阅读器一般是没有设置密码的功能模块&#xff0c;PDF编辑器…