【Qt开发流程】之事件系统1:事件系统描述及事件发生流程

Qt的事件系统

在Qt中,事件是对象,派生自抽象的QEvent类,它表示应用程序内部发生的事情或作为应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件特别相关。以下描述了在典型应用程序中如何传递和处理事件。
需要注意:
事件与信号并不相同,比如单击某个按钮,就会产生鼠标事件(QMouseEvent),这个事件不是按钮产生的,但因为按钮按下了,所以会发射clicked()单击信号(这个是按钮产生的)。如果只关心信号呢,就不用考虑鼠标事件;但如果要设计一个按钮,或者单击产生别的效果,就要关心鼠标事件了。这两者是两个层面的东西,发出者不同,作用也不同。
此外,在Qt中,任何QObject子类实例都可以接收和处理事件。

QEvent类关系图如下:
在这里插入图片描述
以下链接是拖放事件介绍和使用示例:
【Qt开发流程】之拖放操作1:介绍链接: https://blog.csdn.net/MrHHHHHH/article/details/134626484
【Qt开发流程】之拖放操作2:使用链接: https://blog.csdn.net/MrHHHHHH/article/details/134632006

事件类型

大多数事件类型都有特殊的类,特别是QResizeEvent, QPaintEvent, QMouseEvent, QKeyEventQCloseEvent。每个类都是QEvent的子类,并添加特定于事件的函数。例如,QResizeEvent添加了size()和oldSize(),使小部件能够发现它们的尺寸是如何被改变的。
有些类支持不止一种实际事件类型。QMouseEvent支持鼠标按键、双击、移动和其他相关操作。
每个事件都有一个关联的类型,在QEvent:: type中定义,这可以用作运行时类型信息的方便来源,以快速确定给定事件对象是从哪个子类构造的。
由于程序需要以各种复杂的方式作出反应,Qt的事件传递机制是灵活的。

当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示它,并通过调用其event()函数将其传递给QObject的特定实例(或其子类之一)。
此函数不处理事件本身;根据所交付的事件类型,它为该特定类型的事件调用事件处理程序,并根据该事件是被接受还是被忽略发送响应。
一些事件,如QMouseEvent和QKeyEvent,来自窗口系统;一些,如QTimerEvent,来自其他来源;有些来自应用程序本身。

事件的处理

如何处理一个事件呢?有以下5中处理事件的方法。
根据QCoreApplication::notify(QObject *receiver, QEvent *event)
发送事件给接收者:receiver->event(事件)。返回从接收者的事件处理程序返回的值。请注意,对于发送给任何线程中的任何对象的所有事件,都会调用此函数。
对于某些类型的事件(例如鼠标和键事件),如果接收者对事件不感兴趣(即,它返回false),事件将被传播到接收者的父对象,依此类推直到顶级对象。
处理事件有五种不同的方式;重新实现这个虚函数只是其中之一。下面列出了所有五种方法:

  1. 重新实现了paintEvent(), mousePressEvent()等。这是最常见、最简单、也是最不强大的方法。
  2. 重新实现这个函数notify()。这是非常强大的,提供完全的控制;但是一次只能有一个子类处于活动状态。
  3. 在QCoreApplication::instance()上安装事件过滤器。这样的事件过滤器能够处理所有小部件的所有事件,因此它与重新实现notify()一样强大;此外,可以有多个应用程序全局事件过滤器。全局事件过滤器甚至可以查看禁用小部件的鼠标事件。请注意,应用程序事件筛选器仅对位于主线程中的对象调用。
  4. 重新实现QObject::event()(与QWidget一样)。如果这样做,可以按Tab键,并且可以在任何特定于小部件的事件过滤器之前看到事件。
  5. 在对象上安装事件筛选器。这样的事件过滤器获取所有事件,包括Tab和Shift+Tab键按下事件,只要它们不改变焦点小部件。

在实际编程中,方法1最常用,其次是方法五,方法二和方法三虽然功能强大,但会减缓事件的传递,因此很少用到。

事件过滤

有时,对象需要查看(可能还需要拦截)传递给另一个对象的事件。例如,对话框通常需要过滤某些小部件的按键;例如,修改返回键处理。
QObject::installEventFilter()函数通过设置一个事件过滤器来实现这一点,使指定的过滤器对象在其QObject::eventFilter()函数中接收目标对象的事件。事件过滤器在目标对象处理事件之前处理事件,允许它根据需要检查和丢弃事件。现有的事件过滤器可以使用QObject::removeEventFilter()函数删除。
当调用过滤器对象的eventFilter()实现时,它可以接受或拒绝事件,并允许或拒绝事件的进一步处理。如果所有事件过滤器都允许对事件进行进一步处理(通过每个过滤器返回false),则将事件发送到目标对象本身。如果其中一个停止处理(通过返回true),则目标和任何后续事件过滤器根本无法看到该事件。

  bool FilterObject::eventFilter(QObject *object, QEvent *event){if (object == target && event->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);if (keyEvent->key() == Qt::Key_Tab) {// Special tab handlingreturn true;} elsereturn false;}return false;}

上面的代码显示了拦截发送到特定目标小部件的Tab键按下事件的另一种方法。在这种情况下,过滤器处理相关事件并返回true以阻止它们被进一步处理。所有其他事件都被忽略,过滤器返回false以允许它们通过安装在目标小部件上的任何其他事件过滤器发送到目标小部件。
通过在QApplication或QCoreApplication对象上安装事件过滤器,也可以过滤整个应用程序的所有事件。在特定于对象的筛选器之前调用此类全局事件筛选器。这是非常强大的,但它也减慢了整个应用程序中每个事件的事件交付;通常应该使用讨论的其他技术。

事件的传递

每个程序main()函数最后都会调用QApplication()exec()方法,它会使Qt应用程序进入事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt便会构建一个相应的QEvent子类对象表示,之后它会传递相应的QObject对象或子对象。

传递事件的正常方式是调用虚函数。例如,QPaintEvent是通过调用QWidget::paintEvent()传递的。这个虚拟函数负责做出适当的反应,通常是通过重新绘制小部件。如果在虚函数的实现中没有执行所有必要的工作,则可能需要调用基类的实现。
例如,下面的代码处理自定义复选框小部件上的鼠标左键点击,同时将所有其他按钮点击传递给基类QCheckBox:

  void MyCheckBox::mousePressEvent(QMouseEvent *event){if (event->button() == Qt::LeftButton) {// handle left mouse button here} else {// pass on other buttons to base classQCheckBox::mousePressEvent(event);}}

如果想替换基类的函数,必须自己实现所有的东西。但是,如果只想扩展基类的功能,那么可以实现想要的内容,并调用基类来获取不想处理的任何情况的默认行为。
有时,没有这样一个特定于事件的函数,或者特定于事件的函数是不够的。最常见的例子包括按Tab键。通常,QWidget会拦截这些键来移动键盘焦点,但是一些小部件本身需要Tab键。
这些对象可以重新实现QObject::event()(通用事件处理程序),并在通常的处理之前或之后处理它们的事件,或者它们可以完全替换函数。一个非常不寻常的小部件,既解释Tab,又具有特定于应用程序的自定义事件,可能包含以下event()函数:

  bool MyWidget::event(QEvent *event){if (event->type() == QEvent::KeyPress) {QKeyEvent *ke = static_cast<QKeyEvent *>(event);if (ke->key() == Qt::Key_Tab) {// special tab handling herereturn true;}} else if (event->type() == MyCustomEventType) {MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);// custom event handling herereturn true;}return QWidget::event(event);}

注意,对于所有未处理的情况,仍然调用QWidget::event(),并且返回值指示是否处理了事件;true值防止将事件发送给其他对象。
事件传递流程图如下:
在这里插入图片描述

发送事件

许多应用程序都希望创建和发送它们自己的事件。通过构造合适的事件对象并使用QCoreApplication::sendEvent()QCoreApplication::postEvent()发送事件,可以以与Qt自己的事件循环完全相同的方式发送事件。
sendEvent()立即处理事件。当它返回时,事件过滤器和/或对象本身已经处理了事件。对于许多事件类,都有一个名为isAccepted()的函数,它告诉您事件是被最后调用的处理程序接受还是拒绝。
postEvent()将事件发送到队列中以供以后调度。下次Qt的主事件循环运行时,它会调度所有发布的事件,并进行一些优化。例如,如果有多个调整大小事件,它们将被压缩为一个事件。这同样适用于绘制事件:QWidget::update()调用postEvent(),这消除了闪烁并通过避免多次重绘提高了速度。
postEvent()也在对象初始化期间使用,因为提交的事件通常会在对象初始化完成后很快被分派。在实现小部件时,重要的是要认识到,事件可以在其生命周期的早期交付,因此,在其构造函数中,一定要在早期初始化成员变量,以免它有可能接收到事件。
要创建自定义类型的事件,需要定义一个事件号,该事件号必须大于QEvent::User,并且可能需要创建QEvent的子类,以便传递关于自定义事件的特定信息。

事件发生流程示例

以下是一个示例,根据打印结果,看下其传递的整个流程:
新建一个自定义QLineEdit子类
customlineedit.h

#ifndef CUSTOMLINEEDIT_H
#define CUSTOMLINEEDIT_H#include <QLineEdit>class CustomLineEdit : public QLineEdit
{Q_OBJECT
public:explicit CustomLineEdit(QWidget *parent = nullptr);signals:public slots:protected:void keyPressEvent(QKeyEvent *e);bool event(QEvent *event);
};#endif // CUSTOMLINEEDIT_H

customlineedit.cpp

#include "customlineedit.h"#include <QKeyEvent>
#include <QDebug>CustomLineEdit::CustomLineEdit(QWidget *parent) : QLineEdit(parent)
{}void CustomLineEdit::keyPressEvent(QKeyEvent *e)
{qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "自定义单行编辑框 键盘按下事件";QLineEdit::keyPressEvent(e);e->ignore();
}bool CustomLineEdit::event(QEvent *event)
{if(event->type() == QEvent::KeyPress){qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "自定义单行编辑框  event()函数";}return  QLineEdit::event(event);
}

主窗口:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();protected:void keyPressEvent(QKeyEvent *event);bool eventFilter(QObject *watched, QEvent *event);private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QKeyEvent>
#include <QDebug>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);ui->lineEdit->installEventFilter(this);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::keyPressEvent(QKeyEvent *event)
{qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "Mainwindow 键盘按下事件";
}bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{if(watched == ui->lineEdit){if(event->type() == QEvent::KeyPress){qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "Mainwindow 事件过滤器";}}return QMainWindow::eventFilter(watched, event);
}

运行,输出如下:
在这里插入图片描述
得出结论:

  • 先进入父对象事件过滤器,判断对象是不是,是,判断类型,最后返回父类的过滤器事件;
  • 进入目标对象的event()事件
  • 进入目标对象的具体处理事件
  • 进入父对象的事件

流程图如下:
在这里插入图片描述

结论

天没降大任于我,照样苦我心智,劳我筋骨

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【南昌大学主办、往届均已实现EI、Scopus双检索】第三届电子信息工程与数据处理国际学术会议(EIEDP 2024)

第三届电子信息工程与数据处理国际学术会议&#xff08;EIEDP 2024&#xff09; 2024 3rd International Conference on Electronic Information Engineering and Data Processing 第三届电子信息工程与数据处理国际学术会议&#xff08;EIEDP 2024&#xff09;将于2024年3月1…

如何入驻抖音本地生活服务商,门槛太高怎么办?

随着抖音本地生活服务市场的逐渐成熟&#xff0c;越来越多平台开始涉及本地生活服务领域&#xff0c;而本地生活服务商成了一个香窝窝&#xff0c;为了保护用户权益和平台生态&#xff0c;对入驻入驻抖音本地生活服务商的条件及审核也越来越严格&#xff0c;这让很多想成为抖音…

为什么Nginx被称为反向代理

下图显示了 &#x1d41f;&#x1d428;&#x1d42b;&#x1d430;&#x1d41a;&#x1d42b;&#x1d41d; &#x1d429;&#x1d42b;&#x1d428;&#x1d431;&#x1d432; 和 &#x1d42b;&#x1d41e;&#x1d42f;&#x1d41e;&#x1d42b;&#x1d42c;&#…

JFrog----SBOM清单包含哪些:软件透明度的关键

文章目录 SBOM清单包含哪些&#xff1a;软件透明度的关键引言SBOM清单的重要性SBOM清单包含的核心内容SBOM的创建和管理结论 软件物料清单&#xff08;SBOM&#xff09;是一个在软件供应链安全中越来越重要的组成部分。它基本上是一份清单&#xff0c;详细列出了在特定软件产品…

51综合程序03-DS1302时钟

文章目录 DS1302时钟芯片一、DS1302时钟芯片的工作原理1. 芯片特点2. 引脚说明3. 寄存器地址4. 读数据的时序图5. 写数据的时序图 二、综合实例LCD1602显示 DS1302时钟芯片 一、DS1302时钟芯片的工作原理 1. 芯片特点 实时计算年、月、日、时、分、秒、星期&#xff0c;直到2…

java项目日常运维需要的文档资料

一、前言 java项目开发完成&#xff0c;部署上线&#xff0c;进入项目运维阶段&#xff0c;日常工作需要准备哪些资料和文档?当项目上线后&#xff0c;运行一段时间&#xff0c;或多或少会遇到一些运维上的问题&#xff0c;比如服务器磁盘饱满&#xff0c;服务器CPU&#xff0…

vue3 中使用 sse 最佳实践,封装工具

工具 // 接受参数 export interface SSEChatParams {url: string,// sse 连接onmessage: (event: MessageEvent) > void,// 处理消息的函数onopen: () > void,// 建立连接触发的事件finallyHandler: () > void,// 相当于 try_finally 中的 finally 部分&#xff0c;不…

ESP32-Web-Server编程-通过 Web 下载文本

ESP32-Web-Server编程-通过 Web 下载文本 概述 当你希望通过网页导出设备的数据时&#xff0c;可以在 ESP32 上部署一个简单的文件 Web 服务器。 需求及功能解析 本节演示如何在 ESP32 上部署一个最简单的 Web 服务器&#xff0c;来接收浏览器或者 wget 指令请求文件数据。…

layui+ssm实现数据批量删除

layuissm实现数据的批量删除 //数据表格table.render({id: adminList,elem: #adminList,url: ctx "/admin/getAdminList", //数据接口cellMinWidth: 80,even: true,toolbar: #toolbarDemo,//头部工具栏limit: 10,//每页条数limits: [10, 20, 30, 40],defaultToolba…

JVM之jps虚拟机进程状态工具

jps虚拟机进程状态工具 1、jps jps&#xff1a;(JVM Process Status Tool)&#xff0c;虚拟机进程状态工具&#xff0c;可以列出正在运行的虚拟机进程&#xff0c;并显示虚拟机执 行主类&#xff08;Main Class&#xff0c;main()函数所在的类&#xff09;的名称&#xff0c…

应急响应-挖矿病毒处理

应急响应-挖矿病毒处理 使用top​命令实时监控占用CPU资源的是哪个进程&#xff0c;结果可以看到是2725这个进程。 ​​ 再使用netstat -anltp命令查看网络连接状态&#xff0c;定位到对应的PID号后&#xff0c;就拿到了远程地址 ​​ 拿到远程IP&#xff0c;结果是VPN入口…

链表【3】

文章目录 &#x1f433;23. 合并 K 个升序链表&#x1f41f;题目&#x1f42c;算法原理&#x1f420;代码实现 &#x1f437;25. K 个一组翻转链表&#x1f416;题目&#x1f43d;算法原理&#x1f367;代码实现 &#x1f433;23. 合并 K 个升序链表 &#x1f41f;题目 题目链…