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

news2024/9/21 11:11:59

本节对应的视频讲解: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.coloradmin.cn/o/1135197.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《CS2》隆重登场,艾尔莎EA H610M-I和你征战新版本

作为电竞游戏的常青树&#xff0c;《反恐精英》的热门程度可以说是经久不衰。虽然《反恐精英》已经迭代了许多个版本&#xff0c;但是上一次大版本更新已经是2012年的《CS:GO》了。最近&#xff0c;V社正式上线了全新的《CS2》&#xff0c;它是由Source2引擎所开发&#xff0c;…

Nat. Methods | 哈佛李恒组开发三代HiFi宏基因组组装软件--hifiasm-meta

使用hifiasm-meta进行Hifi长读宏基因组序列组装 Metagenome assembly of high-fidelity long reads with hifiasm-meta Article&#xff0c;2022-05-09 Nature methods, [IF 48] DOI&#xff1a;https://doi.org/10.1038/s41592-022-01478-3 原文链接&#xff1a;https://www.n…

全连接=可编程!玻色量子成功研制光量子测控一体机——“量枢”

近日&#xff0c;北京玻色量子科技有限公司成功完成了光量子测控一体机——“量枢”的研制&#xff0c;作为一款光量子信号测量反馈控制设备&#xff0c;它是专门为相干光量子计算机定制的一套集光量子测量反馈、系统状态检测、计算流程控制等功能于一身的智能系统。 玻色量子自…

腾讯联手警方重拳出击,平阳警方斩断特大外挂黑色产业链

近期&#xff0c;在闲游盒腾讯守护者计划安全团队的协助下&#xff0c;平阳警方成功破获一起特大《绝地求生》外挂案件。捣毁了一个集外挂作者、卡盟平台、一级代理销售商、二级代理销售商等多个环节为一体的网络黑色产业链&#xff0c;共抓捕34名犯罪嫌疑人。 2018年5月初&…

什么是React中的context(上下文)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

electron27+react18集成搭建跨平台应用|electron窗口多开

基于Electron27集成React18创建一个桌面端exe程序。 electron27-vite4-react18基于electron27结合vite4构建工具快速创建react18跨端应用实践。 版本列表 "vite": "^4.4.5" "react": "^18.2.0" "electron": "^27.0.1&…

51单片机的PWM控制呼吸灯

文章目录 前言一、PWM引脚以及寄存器的配置二、呼吸灯逻辑总结 前言 hello 大家好这里是夏目学长的51单片机课堂&#xff0c;本篇博客是夏目学长观看B站up主学电超人的视频所写的一篇51单片机入门博客之51单片机PWM配置呼吸灯 &#xff0c;我自己在学习这节课程的时候觉得这节…

【MySQL--->内置函数】

文章目录 [TOC](文章目录) 一、日期函数二、字符串函数三、数学函数四、其他函数 一、日期函数 current_date();当前日期 current_time();当前时间 current_timestamp();当前时间戳 now();当前时间 date(‘date’);日期 date_sub(date,interval number second/minute/hour/d…

18.2 使用NPCAP库抓取数据包

NPCAP 库是一种用于在Windows平台上进行网络数据包捕获和分析的库。它是WinPcap库的一个分支&#xff0c;由Nmap开发团队开发&#xff0c;并在Nmap软件中使用。与WinPcap一样&#xff0c;NPCAP库提供了一些API&#xff0c;使开发人员可以轻松地在其应用程序中捕获和处理网络数据…

基于PyTorch的MNIST手写体分类实战

第2章对MNIST数据做了介绍&#xff0c;描述了其构成方式及其数据的特征和标签的含义等。了解这些有助于编写合适的程序来对MNIST数据集进行分析和识别。本节将使用同样的数据集完成对其进行分类的任务。 3.1.1 数据图像的获取与标签的说明 MNIST数据集的详细介绍在第2章中已…

基于springboot实现就业信息管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现就业信息管理系统演示 摘要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;就业信息管理系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人…

最新Unity DOTS教程之BlobAsset核心机制分析

最近DOTS发布了正式的版本, 我们来分享一下DOTS里面BlobAsset机制&#xff0c;方便大家上手学习掌握Unity DOTS开发。 BlobAsset 概叙 DOTS提供了BlobAsset机制来把数据生成高效的二进制数据。BlobAsset的数据是不可变的。BlobAsset只支持非托管类型数据。支持Burst编译器编译…

计时的vue写法

<el-input type"text" id"timetext" value"00时00分00秒" readonly></el-input> <el-button type"button" click"start()">开始</el-button> <el-button type"button" click"st…

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

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

软考系列(系统架构师)- 2015年系统架构师软考案例分析考点

试题一 软件架构&#xff08;质量属性效用树、架构风险、依够点、权衡点&#xff09; 【问题1】&#xff08;12分&#xff09; 在架构评估过程中&#xff0c;质量属性效用树&#xff08;utility tree&#xff09;是对系统质量属性进行识别和优先级排序的重要工具。请给出合适的…

第20章 Netty

20.1 说说IO的交互流程 难度:★ 重点:★ 白话解析 这道题主要是用来帮助理解后面题目的,IO交互主要分为两种:本地IO和网络IO。 1、本地IO:数据在磁盘上,通过系统调用read()方法读取数据到内核空间的缓冲区,然后再读取到 用户空间的缓冲区,这就是IO的交互过程。 2、网…

华为---DHCP中继代理简介及示例配置

DHCP中继代理简介 IP动态获取过程中&#xff0c;客户端&#xff08;DHCP Client&#xff09;总是以广播&#xff08;广播帧及广播IP报文&#xff09;方式来发送DHCPDISCOVER和DHCPREQUEST消息的。如果服务器&#xff08;DHCP Server&#xff09;和 客户端不在同一个二层网络(二…

【100天精通Python】Day71:Python可视化_一文掌握Seaborn库的使用《一》_数据分布可视化,数据关系可视化,示例+代码

目录 1. 数据分布的可视化 1.1 直方图&#xff08;Histograms&#xff09; 1.2 核密度估计图&#xff08;Kernel Density Estimation Plot&#xff09; 1.3 箱线图&#xff08;Box Plot&#xff09; 1.4 小提琴图&#xff08;Violin Plot&#xff09; ​编辑1.5 散点图&am…

蓝桥杯双周赛算法心得——铺地板(质因数)

大家好&#xff0c;我是晴天学长&#xff0c;这是第二周的蓝桥杯的双周赛&#xff0c;题可出的又好又灵活啊&#xff01;真不错&#xff01; 1) .铺地板 2) .算法思路 1.导入java.util包中的Scanner类&#xff0c;以从用户那里读取输入。 2.main方法是程序的入口点。 3.创建一…

单片机仿真设计打包项目

小伙伴们在仿真设计时会遇到各种各样的问题&#xff0c;网上的资料可能不全或者很贵。 这篇也不单纯为了打广告&#xff0c;主要是希望实实在在帮到学单片机的同学&#xff0c;大家不要一有问题就各种找dai zuo&#xff0c;做的好不好是一回事儿&#xff0c;关键是它费&#x…