事件传播机制 与 责任链模式

1、基本概念
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,将请求沿着处理链传递,直到有一个对象能够处理为止。

2、实现的模块有:

Handler(处理者):定义一个处理请求的接口。
ConcreteHandler(具体处理者):实现了处理者接口,判断自己是否能够处理请求,如果不能将请求传递给下一个处理者。
Request(请求):封装了请求的信息,通常作为处理者方法的参数传递。
3、使用场景
当需要将请求的发送者和接收者进行解耦时。
当有多个对象可以处理同一请求,但不确定哪个对象应该处理时。
当需要动态指定处理请求的顺序时。

4、责任链模式的C++实现

#include <iostream>  
#include <memory>  
#include <string>  // 抽象处理器类  
class Handler {  
public:  virtual ~Handler() {}  // 处理请求的方法  virtual void HandleRequest(const std::string& request) {  if (successor_) {  successor_->HandleRequest(request);  }  }  // 设置后继处理器  void setSuccessor(std::shared_ptr<Handler> successor) {  successor_ = successor;  }  protected:  std::shared_ptr<Handler> successor_;  
};  // 具体处理器类A  
class ConcreteHandlerA : public Handler {  
public:  void HandleRequest(const std::string& request) override {  if (request == "A") {  std::cout << "ConcreteHandlerA handles the request: " << request << std::endl;  } else {  Handler::HandleRequest(request);  }  }  
};  // 具体处理器类B  
class ConcreteHandlerB : public Handler {  
public:  void HandleRequest(const std::string& request) override {  if (request == "B") {  std::cout << "ConcreteHandlerB handles the request: " << request << std::endl;  } else {  Handler::HandleRequest(request);  }  }  
};  // 具体处理器类C  
class ConcreteHandlerC : public Handler {  
public:  void HandleRequest(const std::string& request) override {  if (request == "C") {  std::cout << "ConcreteHandlerC handles the request: " << request << std::endl;  } else {  Handler::HandleRequest(request);  }  }  
};  int main() {  std::shared_ptr<Handler> handlerA = std::make_shared<ConcreteHandlerA>();  std::shared_ptr<Handler> handlerB = std::make_shared<ConcreteHandlerB>();  std::shared_ptr<Handler> handlerC = std::make_shared<ConcreteHandlerC>();  // 设置责任链  handlerA->setSuccessor(handlerB);  handlerB->setSuccessor(handlerC);  // 发送请求  handlerA->HandleRequest("A");  handlerA->HandleRequest("B");  handlerA->HandleRequest("C");  handlerA->HandleRequest("D");  return 0;  
}

在上述示例中,Handler是抽象处理器类,定义了处理请求的方法。ConcreteHandlerA、ConcreteHandlerB和ConcreteHandlerC是具体处理器类,分别处理请求A、B和C。在创建这些处理器对象时,按照责任链的顺序将它们连接起来。在main函数中,程序创建了一个责任链,将请求依次发送给处理器A、B、C,如果没有任何处理器能够处理请求,则请求不会被处理。

总之,责任链模式是一种非常有用的设计模式,它可以将请求和处理请求的对象解耦,从而提高系统的灵活性和可扩展性。在实际应用中,它可以帮助我们解决很多复杂的问题,提高系统的处理能力和吞吐量,同时也提高了代码的可维护性和可读性。

5、Qt事件传播机制
QT源码:事件系统
QT的事件处理系统同样用到了事件处理系统,其中事件通过事件队列发送到对应的对象,每个对象都可以处理该事件,如果该对象无法处理,将会发给下一个对象。

以下是 QAppliaction 发送鼠标事件给 QWidget 的部分源码:

//接收鼠标事件的对象w
QWidget* w = static_cast<QWidget *>(receiver);
//鼠标事件e
QMouseEvent* mouse = static_cast<QMouseEvent*>(e);......while (w) {//创建一个新的鼠标事件对象,用于在对象树中传播鼠标事件QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());......//如果鼠标事件被接受,打破循环eventAccepted = (w == receiver ? mouse : &me)->isAccepted();if (res && eventAccepted)break;......//如果鼠标事件未被接受,将w设置为w的父组件,继续循环w = w->parentWidget();
}

可以看出, QApplication 将鼠标事件沿着对象树传递,直到有一个对象能够处理为止,符合责任链模式的思想,其中:

QObject:Handler(处理者),定义一个处理鼠标事件的接口。
QWidgt:ConcreteHandler(具体处理者),实现了 QObject 的接口,判断自己是否能够处理鼠标事件,如果不能将请求传递给父类 QWidget。
QMouseEvent:Request(请求),封装了鼠标事件的信息。
QAppliaction:客户端,是请求的发起者。

以上即可展现责任链模式在QT中应用。如果在意细节,可以看下方第6条,否则,后面不用看!

6、Qt事件传播机制 具体细节

事件传播机制和对象树机制共同构成了Qt中对象的一种管理方式和事件的一种传播方式。通过在对象之间建立处理请求的责任链,可以使请求的处理与请求的发起者解耦,提高代码的可扩展性和可维护性。另外,Qt还提供了QObject::installEventFilter方法,即可以安装一个事件过滤器来处理事件。事件过滤器是一个单独的对象,它可以拦截并处理一个或多个对象的事件。因此,事件过滤器也可以看作责任链模式中的一环。

下面给出Qt源码中如何实现事件传播机制,并且体现出责任链模式的一个示例。注意,这个示例省略了比较多的细节,但作为示例去描述Qt源码中如何体现责任链模式,应该是勉强足够的。

#include <iostream>enum EventType { UnknownEvent, MouseButtonPressEvent };class QEvent {
public:explicit QEvent(EventType type) : _type(type), _accepted(false) {}virtual ~QEvent() {}EventType type() const { return _type; }void accept() { _accepted = true; }void ignore() { _accepted = false; }bool isAccepted() const { return _accepted; }private:EventType _type;bool _accepted;
};class QObject {
public:virtual ~QObject() {}virtual bool event(QEvent *event) {// 默认实现,不处理任何事件return false;}virtual bool eventFilter(QObject *, QEvent *) {// 默认实现,不处理任何事件return false;}
};class QWidget : public QObject {
public:virtual ~QWidget() {}bool event(QEvent *event) override {switch (event->type()) {case MouseButtonPressEvent:mousePressEvent(event);return true;default:return QObject::event(event);}}virtual void mousePressEvent(QEvent *event) {std::cout << "QWidget: Mouse button press event\n";event->accept();}
};

通过上面的示例,可以看到,QObjectQWidgetQEvent三个类的设计和实现,体现了责任链模式。

QWidget,继承自QObject,重写了event()方法,专门处理用户界面事件。例如,当按键事件发生,我们可以获取按下的键码,然后调用事件的accept()方法表示处理完成,从而阻止事件继续向上传递。如果QWidget无法处理事件,它会将事件传递给父对象。

QEvent是所有事件的基类,每个事件有独特的类型标识符。QEvent类还提供了isAccepted()accept()ignore()方法,标记事件是否被接受和处理。一旦事件被处理,我们可以通过调用accept()方法停止事件的传递,避免重复处理。

作为所有Qt对象的基类,QObject通过提供event()eventFilter()两个虚函数,构建了事件处理的基础。其中event()负责处理自身接收到的事件,eventFilter()则处理需要过滤的事件。如果事件未被处理,它会被传递给父对象,形成责任链。那么传递给父对象的逻辑,在哪里呢?往QWidget::event()方法里打断点调试就会发现,调用栈会经过QApplication::notify(QObject *receiver, QEvent *e),也就是说,事件的分发回经过里面QApplication::notify。切换到这个方法的调用点查看源代码,可以看到这么一段逻辑:

bool QApplication::notify(QObject* receiver, QEvent* e)
{//其他逻辑//...bool res = false;switch (e->type()) {//处理其他事件类型//...case QEvent::MouseButtonPress:case QEvent::MouseButtonRelease:case QEvent::MouseButtonDblClick:case QEvent::MouseMove:{QWidget* w = static_cast<QWidget*>(receiver);QMouseEvent* mouse = static_cast<QMouseEvent*>(e);QPoint relpos = mouse->pos();if (e->spontaneous()) {if (e->type() != QEvent::MouseMove)QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos);// ### Qt 5 These dynamic tool tips should be an OPT-IN feature. Some platforms// like OS X (probably others too), can optimize their views by not// dispatching mouse move events. We have attributes to control hover,// and mouse tracking, but as long as we are deciding to implement this// feature without choice of opting-in or out, you ALWAYS have to have// tracking enabled. Therefore, the other properties give a false sense of// performance enhancement.if (e->type() == QEvent::MouseMove && mouse->buttons() == 0&& w->rect().contains(relpos)) { // Outside due to mouse grab?d->toolTipWidget = w;d->toolTipPos = relpos;d->toolTipGlobalPos = mouse->globalPos();QStyle* s = d->toolTipWidget->style();int wakeDelay = s->styleHint(QStyle::SH_ToolTip_WakeUpDelay, 0, d->toolTipWidget, 0);d->toolTipWakeUp.start(d->toolTipFallAsleep.isActive() ? 20 : wakeDelay, this);}}bool eventAccepted = mouse->isAccepted();QPointer<QWidget> pw = w;while (w) {QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());me.spont = mouse->spontaneous();me.setTimestamp(mouse->timestamp());QGuiApplicationPrivate::setMouseEventFlags(&me, mouse->flags());// throw away any mouse-tracking-only mouse eventsif (!w->hasMouseTracking()&& mouse->type() == QEvent::MouseMove && mouse->buttons() == 0) {// but still send them through all application event filters (normally done by notify_helper)d->sendThroughApplicationEventFilters(w, w == receiver ? mouse : &me);res = true;}else {w->setAttribute(Qt::WA_NoMouseReplay, false);res = d->notify_helper(w, w == receiver ? mouse : &me);e->spont = false;}eventAccepted = (w == receiver ? mouse : &me)->isAccepted();if (res && eventAccepted)break;if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))break;relpos += w->pos();w = w->parentWidget();}mouse->setAccepted(eventAccepted);if (e->type() == QEvent::MouseMove) {if (!pw)break;w = static_cast<QWidget*>(receiver);relpos = mouse->pos();QPoint diff = relpos - w->mapFromGlobal(d->hoverGlobalPos);while (w) {if (w->testAttribute(Qt::WA_Hover) &&(!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) {QHoverEvent he(QEvent::HoverMove, relpos, relpos - diff, mouse->modifiers());d->notify_helper(w, &he);}if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))break;relpos += w->pos();w = w->parentWidget();}}d->hoverGlobalPos = mouse->globalPos();}break;//处理其他事件类型//...}return res;
}

我们在这里只关心鼠标事件(QEvent::MouseButtonPress,QEvent::MouseButtonRelease,QEvent::MouseButtonDblClick,QEvent::MouseMove:)的处理。下面分析一下关键的程序段:

1. 在处理鼠标事件时,首先获取当前QWidget对象w

QWidget* w = static_cast<QWidget *>(receiver);

2. 把事件循环遍历至最高级父窗口或直到一个处理了事件的窗口。在此过程中,对于没有成功处理鼠标事件的窗口,事件会沿着窗口的父链继续传递。

while (w) {// 将事件发送给QWidget对象w,尝试让其处理// ...// 当事件已经被接收并得到处理,跳出循环if (res && eventAccepted)break;// 当到达顶层窗口或当前窗口标记为Qt::WA_NoMousePropagation (无鼠标事件传递)时,跳出循环if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))break;// 获取父窗口,在while循环下一轮尝试让父窗口去处理w = w->parentWidget();
}

在这个while循环中,就会不断地去获取父对象,尝试让其处理。事件得到处理,或者当到达顶层窗口,或者当前窗口标记为Qt::WA_NoMousePropagation (无鼠标事件传递)时,跳出循环。这里就体现出了事件处理的传递链。

可以继续解析上面的程序示例中,"将事件发送给QWidget对象w,尝试让其处理"部分没有展示出的代码:

1. 根据收到的原始鼠标事件(mouse)创建一个新的鼠标事件(me),并使用正确的相对位置(relpos)更新它。同时设置一些其他属性,如是否自发、时间戳和鼠标事件标志。

QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());
me.spont = mouse->spontaneous();
me.setTimestamp(mouse->timestamp());
QGuiApplicationPrivate::setMouseEventFlags(&me, mouse->flags());

2. 对于没有鼠标跟踪并且是无按钮按下的鼠标移动事件(仅用于鼠标跟踪的事件),忽略这个事件,并将其传递给所有应用程序事件过滤器。设置结果变量res = true表示事件已被处理。

if (!w->hasMouseTracking()&& mouse->type() == QEvent::MouseMove && mouse->buttons() == 0) {// but still send them through all application event filters (normally done by notify_helper)d->sendThroughApplicationEventFilters(w, w == receiver ? mouse : &me);res = true;
}

3. 否则,尝试将mouseme事件传递给QWidget对象(视情况而定)。更新事件的自发属性为false

else {w->setAttribute(Qt::WA_NoMouseReplay, false);res = d->notify_helper(w, w == receiver ? mouse : &me);e->spont = false;
}

4. 检查事件是否已被接受。这个步骤会影响后续窗口的事件传递。

eventAccepted = (w == receiver ? mouse : &me)->isAccepted();

综上,QObject作为事件处理接口,QWidget实现具体的处理逻辑,而QEvent则扮演了请求的角色,QApplication参与了事件的分发和向上传递。这几个类的协作为Qt提供了一种高效、灵活且可扩展的事件处理机制,帮助开发者轻松处理各种事件和场景。

总结

至此,我们已经解析了Qt的对象树机制,构建在对象树机制之上的事件传递机制,以及它们背后的设计思想。在后续的文章中,我们可能还会继续解析Qt源码中事件机制相关的设计模式。事件机制确实是Qt中的核心机制之一,我们能够深挖的东西,恐怕还有很多。

                        
原文链接:https://blog.csdn.net/buhuiCyvyan/article/details/138793057

原文链接:https://zhuanlan.zhihu.com/p/631330647

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

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

相关文章

码蹄集部分题目(2024OJ赛19期;贪心集训)

1&#x1f40b;&#x1f40b;水温调节&#xff08;黄金&#xff1b;贪心&#xff09; 时间限制&#xff1a;1秒 占用内存&#xff1a;128M &#x1f41f;题目思路 贪心思路&#xff1a;先将两只水龙头的流速开到最大&#xff0c;温度高了&#xff0c;就把热水的流速降低一个…

Java:缓存行和伪共享

在Java中&#xff0c;缓存行&#xff08;Cache Line&#xff09;和伪共享&#xff08;False Sharing&#xff09;是与多线程访问共享数据相关的两个重要概念。以下是关于这两个概念的详细解释&#xff1a; 缓存行&#xff08;Cache Line&#xff09; 定义&#xff1a;缓存行是…

3.2. 马氏链-马氏链的构造及马氏性(1)

马氏链的构造及马氏性 1. 马氏链的构造本节首先构造马氏链, 即构造活动概率空间: 在序列空间中构造 P x , P μ P_x,P_\mu Px

新人学习笔记之(注释和关键字)

一、注释 1.什么是注释 (1)注释是在程序指定位置添加的说明性信息 (2)简单理解&#xff0c;就是对代码的一种解释 2.注释的分类 (1)单行注释 格式&#xff1a;// 注释信息 (2)多行注释 格式&#xff1a;/*注释信息*/ 3.注释的使用 (1)主要作用&#xff1a;增加代码的阅读性 4.注…

4_机械臂坐标系简介

一、坐标系的标准命名 为了规范起见&#xff0c;有必要给机器人和工作空间专门命名和确定专门的“标准”坐标系。 图3-27为一种典型的工况&#xff0c;机器人抓持某种工具&#xff0c;并把工具末端移动到操作者指定的位置。图3-27所示的5个坐标系就是需要命名的坐标系。这五个坐…

7z及7zip-cpp最高压缩比的免费开源压缩软件

7z介绍 7z是一种主流高效的压缩格式&#xff0c;它拥有极高的压缩比。在计算机科学中&#xff0c;7z是一种可以使用多种压缩算法进行数据压缩的档案格式。该格式最初由7-Zip实现并采用&#xff0c;但这种档案格式是公有的&#xff0c;并且7-Zip软件本身亦在GNU宽通用公共许可证…

MySQL分组聚合

where 与 having 的区别 & order by 假如我们有一张表&#xff0c;表名为 sales&#xff0c;如下所示&#xff1a; ----------------------------------------------------------------------------- | transaction_id | customer_id | product_name | price | quantit…

数据结构之B树的原理与业务场景

B树是一种自平衡的树形数据结构&#xff0c;它能够保持数据有序&#xff0c;并且可以高效地进行查找、顺序访问、插入和删除操作。B树的设计是为了优化磁盘I/O操作&#xff0c;因为它可以减少磁盘访问次数&#xff0c;这在数据库和文件系统中非常有用。 1. B树的原理 节点的出…

PCIe总线-RK3588 PCIe子系统简介(八)

1.PCIe子系统 RK3588 PCIe子系统如下图所示。总共拥有5个PCIe控制器。PCIe30X4(4L)支持RC和EP模式&#xff0c;其他4个仅支持RC模式。ITS port 1连接PCIe30X4(4L)和PCIe30X2(2L)控制器&#xff0c;PCIe30X4(4L)和PCIe30X2(2L)控制器使用PCIe3.0 PIPE PHY。ITS port 0连接PCIe3…

RIP路由附加度量值(华为)

#交换设备 RIP路由附加度量值 RIP&#xff08;Routing Information Protocol&#xff09;路由协议中的附加度量值是指在RIP路由原来度量值的基础上所增加的额外度量值&#xff0c;通常以跳数来表示。这个附加度量值可以是正值&#xff0c;也可以是负值&#xff0c;用于影响路…

关于STM32上用HID HOST调鼠标数据的解析

一、前言 关于这章主要是基于我前面的那篇文章 链接: 关于怎么用Cubemx生成的USBHID设备实现读取一体的鼠标键盘设备&#xff08;改进版&#xff09; https://blog.csdn.net/qq_29187987/article/details/139535648?spm1001.2014.3001.5501 引用的文章的简介 引用的这篇文…

数据库概述1

数据&#xff1a;描述事物的符号记录称为数据&#xff1b; 包括数字、图片、音频等&#xff1b; 数据库&#xff1a;长期储存在计算机内有组织、可共享的大量数据的集合&#xff1b;数据库中的数据按照一定的数据模型组织、描述和存储&#xff0c;具有较小的数据冗余、较高的数…

STM32学习笔记(一)--时钟树详解

&#xff08;1&#xff09;时钟概述&#xff1b;时钟是具有周期性的脉冲信号&#xff0c;最常用的是占空比50%的方波。&#xff08;时钟相当于单片机的脉搏&#xff1b;STM32本身非常复杂&#xff0c;外设非常的多&#xff0c;为了保持低功耗工作&#xff0c;STM32 的主控默认不…

(一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景

utf8mb4_general_ci 和 utf8mb4_unicode_ci 是 MySQL 数据库中字符集和排序规则。用于指定字符数据的排序和比较规则&#xff0c;确保在数据库中对字符串进行查询和比较时得到正确的结果。 1、utf8mb4_general_ci 是一个较为简单的排序规则。不区分大小写&#xff08;case-ins…

【Python】深入了解 AdaBoost:自适应提升算法

我们都找到天使了 说好了 心事不能偷藏着 什么都 一起做 幸福得 没话说 把坏脾气变成了好沟通 我们都找到天使了 约好了 负责对方的快乐 阳光下 的山坡 你素描 的以后 怎么抄袭我脑袋 想的 &#x1f3b5; 薛凯琪《找到天使了》 在机器学习的领域中&#x…

算法工程师 | 如何快速 了解,掌握一个算法!脚踏实地,迎着星辰,向前出发 ~

本文是一些碎碎念 希望对正在迈向 算法工程师道路的你 有所裨益 一般来说&#xff0c;代码 中会有很多 算法实现的细节&#xff0c;但论文可能并没有体现&#xff0c;所以能够尝试自己 仔细阅读论文&#xff0c;手动复现代码&#xff0c;基本上来说对 这个 算法 你有了全…

夏季城市环境卫生挑战多:TSINGSEE青犀智慧环卫方案助力城市垃圾站智能管理

一、背景分析 夏季&#xff0c;随着气温的攀升&#xff0c;城市垃圾的数量和种类也随之增加&#xff0c;这给环卫工作带来了极大的挑战。环卫垃圾站点作为城市垃圾处理的重要一环&#xff0c;其管理效率直接关系到城市环境的整洁与卫生。近年来&#xff0c;随着视频监控技术的…

【Redis】Redis常见问题——缓存更新/内存淘汰机制/缓存一致性

目录 回顾数据库的问题如何提高 mysql 能承担的并发量&#xff1f;缓存解决方案应对的场景 缓存更新问题定期生成如何定期统计定期生成的优缺点 实时生成maxmemory 设置成多少合适呢&#xff1f;项目类型上来说 新的问题 内存淘汰策略Redis淘汰策略为什么redis要内存淘汰内存淘…

ESP32 IDF ADF 加入音频

需要把mp3制作成音频bin 用ADF自带工具 果用户需要生成自己的 audio-esp.bin&#xff0c;则需要执行 mk_audio_bin.py 脚本&#xff08;位于 $ADF_PATH/tools/audio_tone/mk_audio_tone.py&#xff09;&#xff0c;并且指定相关文件的路径。 源 MP3 文件在 tone_mp3_folder …