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

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

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值