每当将 QWidget 添加到 QApplication 的小部件树时,如何接收事件?

Posted

技术标签:

【中文标题】每当将 QWidget 添加到 QApplication 的小部件树时,如何接收事件?【英文标题】:How to receive event whenever a QWidget is added to QApplication's widget tree? 【发布时间】:2016-08-25 22:31:57 【问题描述】:

我想在应用程序范围内检查一些东西。具体来说,我想检查添加到应用程序中的每个小部件。

类似的事情可以在 javascript/html 中完成,您可以在其中添加 DOM 突变侦听器,该侦听器会在 DOM 更改时触发。

我可以在QApplication 上收听添加的小部件吗?具体来说,捕获添加到QApplication 对象的小部件作为子级或添加到任何***小部件或它们的子级、子级等的小部件...

如果不可能,最好的解决方法是什么?

【问题讨论】:

您真的需要检查应用程序中的每一个小部件吗? @bnaecker 是的。我需要能够“等待小部件” - 例如。当出现符合某些抽象描述的小部件时触发事件。只有另一种方法是在一个间隔上循环整个 qapplication 并找到小部件。这很讨厌。我还缓存了一些小部件选择器,我想在添加/删除小部件时更新缓存。 什么是“抽象描述”?这些小部件是特定类的吗?例如,您需要检查所有 QLabel 对象,还是您自己的任何自定义类?还是整个应用程序中的任何小部件? @bnaecker 抽象描述是一个接受QWidget* 并返回真或假的谓词。所以是的,正如我在我的问题和对您的评论的最后回复中所说,我想检查在 qapplication 中添加或删除的 any 小部件。但这个问题特别是关于添加。 我不是在问“抽象描述”的定义,而是你的实际谓词函数是什么。这可能有助于减少问题的规模,并可能提出更好的解决方案。 【参考方案1】:

最稳定的解决方案可能是每隔一段时间遍历对象层次结构,从QApplication 作为根开始,并使用谓词检查每个对象。这将是低效的,这就是为什么我要求提供有关您实际要查询的对象的更多信息。另一方面,对于一个测试框架,你可能不太关心效率。

不过,也许可以做些别的事情。您可以在任何QObject 上安装事件过滤对象,这些对象定义了它们在接收到来自 Qt 事件系统的事件时如何响应。您可以在根 QApplication 对象上安装一个事件过滤器(并在创建的任何子对象上递归),以检查事件是否为 QChildEvent 以及 added() 是否为它返回 true。如果这是真的,这意味着添加了一个新的孩子,那么您也可以将此事件过滤器安装到该孩子上。所有其他事件都将原封不动地传递下去。您可以阅读有关安装事件过滤器的更多信息here. 本质上,过滤器是一个 QObject 子类,它定义了 eventFilter() 函数,对于应该过滤(停止)的事件返回 true,否则返回 false。当这返回 true 时,在新创建的对象上运行您的谓词并在其上安装相同的事件过滤器。

需要注意的一个问题是eventFilter() 函数只接收QObjects。您可以通过调用isWidgetType() 来了解对象是否为QWidget,但您不知道更多。因此,只要您的谓词只能使用为这些通用基类定义的方法和数据,就可以了。

【讨论】:

事件过滤器是个好主意,但这不会覆盖应用程序中现有的事件过滤器吗? 你会捕获你感兴趣的事件,做你的检查,在新的孩子上安装事件过滤器,然后返回 false。这意味着您拦截事件以检查其类型,但实际上并未过滤 事件。对象会照常处理它。 我实现了,完美!绝对比轮询更快、更流畅。 我刚刚发现了一个缺陷。根据文档在 QEvent::ChildAdded 案例中,孩子尚未完全构造。您知道如何等待构建小部件吗? 我认为这意味着QObjectQWidget 基类构造函数在发送此事件时已被调用,但派生类的完整构造函数尚未运行。因此,您似乎应该将其视为这些基类之一(并调用方法或检查数据),但不能将其视为任何派生类。【参考方案2】:

根据@bnaecker 的回答,这里有一些代码:

AddChildEventFilter.h

#include <QObject>
class QEvent;
class AddChildEventFilter: public QObject 
        Q_OBJECT
    public:
        AddChildEventFilter(QObject* parent=0);
    protected:
        bool eventFilter(QObject *obj, QEvent *event) override;
;

AddChildEventFilter.cpp

#include "AddChildEventFilter.h"
#include <QEvent>
#include <QDebug>
#include <QWidget>
#include <QChildEvent>
AddChildEventFilter::AddChildEventFilter(QObject* parent):QObject(parent) 

bool AddChildEventFilter::eventFilter(QObject* obj, QEvent* event) 
    if(QWidget* widget = dynamic_cast<QWidget*>(obj)) 
        if(QChildEvent* chevent = dynamic_cast<QChildEvent*>(event)) 
            if(chevent->added()) 
                QObject* child = chevent->child();
                qDebug()<<"Child added: "<<child->metaObject()->className()<<"to"<<widget->metaObject()->className();
                child->installEventFilter(new AddChildEventFilter(child));
            
        
    
    return false;

用法:

#include "AddChildEventFilter.h"
#include <QWidget>
#include <QApplication>
void PrintAllEvents()

    for(QWidget* widget: QApplication::allWidgets()) 
        widget->installEventFilter(new AddChildEventFilter(widget));
    

【讨论】:

这很好,但请注意您不需要在AddChildEventFilter.cpp 中执行dynamic_casts。每个QEvent 都对其类型进行编码,可通过type() 方法访问。所以你可以和if (chevent-&gt;type() == QEvent::ChildEvent) ... 比较。我也会使用if (obj-&gt;isWidgetType()) 而不是第一个dynamic_cast @bnaecker 我知道这些,但我需要实际访问子类'(例如child())方法,所以我需要转换它。 按照 bnaecker 的建议检查案例,然后使用 'static_cast' 而不是 'dynamic_cast' 会不会更有效? @VaidotasStrazdas 是的,我有点懒得浏览文档并首先根据用于 Qt 事件的枚举器对事件类型进行 IF 检查。由于我注意到这并没有影响性能,因此我没有对其进行优化。

以上是关于每当将 QWidget 添加到 QApplication 的小部件树时,如何接收事件?的主要内容,如果未能解决你的问题,请参考以下文章

将没有布局的 QWidget 添加到 QStackedWidget 镶嵌间隙

如何将 QWidget 添加到 QMenuBar?

将 QWidget 添加到 QGraphicsScene

将 QWidget 添加到 QListWidget

如何将 QWidget 添加到 QGLWidget

如何将菜单添加到从 Qwidget 派生的小部件