当前位置: 首页 > news >正文

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

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

相关文章:

  • Matlab 入门学习
  • .net core使用EPPlus设置Excel的页眉和页脚
  • G7易流赋能化工物流,实现安全、环保与效率的共赢
  • Java延迟初始化Logger日志对象
  • 【C++11 之nullptr关键字 用以消除空指针和0歧义】基础知识必须了解
  • 【Python教程】压缩PDF文件大小
  • Vue3中的常见组件通信之`provide`、`inject`
  • webkit 的介绍
  • 大模型网信办备案全网最详细说明(付附件)
  • Docker部署Nginx1.21.5(保姆级图文教程)
  • Mybatis框架的缓存
  • Excel导出实例
  • rust内存分配,内存回收,内存泄露
  • 联华集团:IT团队如何实现从成本中心提升至价值中心|OceanBase 《DB大咖说》(十)
  • (三十)Flask之wtforms库【剖析源码上篇】
  • 分享的文章《人生如棋》
  • es6(二):字符串的扩展
  • Idea+maven+scala构建包并在spark on yarn 运行
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • js面向对象
  • Magento 1.x 中文订单打印乱码
  • QQ浏览器x5内核的兼容性问题
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 原生js练习题---第五课
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 阿里云API、SDK和CLI应用实践方案
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​学习笔记——动态路由——IS-IS中间系统到中间系统(报文/TLV)​
  • #includecmath
  • #QT(TCP网络编程-服务端)
  • (+4)2.2UML建模图
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (55)MOS管专题--->(10)MOS管的封装
  • (delphi11最新学习资料) Object Pascal 学习笔记---第14章泛型第2节(泛型类的类构造函数)
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (二)Linux——Linux常用指令
  • (二)springcloud实战之config配置中心
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)人的集合论——移山之道
  • .form文件_SSM框架文件上传篇
  • .Net mvc总结
  • .net/c# memcached 获取所有缓存键(keys)
  • .NetCore发布到IIS
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .vimrc 配置项
  • /使用匿名内部类来复写Handler当中的handlerMessage()方法
  • @AutoConfigurationPackage的使用
  • @Validated和@Valid校验参数区别
  • @软考考生,这份软考高分攻略你须知道
  • [ C++ ] STL---string类的模拟实现
  • [ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)
  • [ 网络通信基础 ]——网络的传输介质(双绞线,光纤,标准,线序)