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

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 events
            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;
            }
            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

相关推荐
handsomethefirst2 天前
【设计模式】【行为型模式】【责任链模式】
设计模式·责任链模式
L_qingting5 天前
C++ 设计模式之责任链模式
c++·设计模式·责任链模式
小白要code5 天前
设计模式-责任链模式
java·设计模式·责任链模式
Fireworkitte6 天前
责任链模式(设计模式)
java·设计模式·责任链模式
黄俊懿9 天前
【图解IO与Netty系列】Netty源码解析——ChannelPipeline中的责任链模式
java·后端·源码·netty·责任链模式·nio
wei于人海12 天前
OkHttp框架源码深度剖析【Android热门框架分析第一弹】
android·okhttp·开源·github·建造者模式·责任链模式·源码分析
AlieNeny14 天前
【01】区块链技术概述
区块链·责任链模式·分布式账本
沉夢志昂丶19 天前
C++中的责任链模式
开发语言·c++·责任链模式
让你三行代码QAQ21 天前
设计模式- 责任链模式(行为型)
设计模式·责任链模式