【问题标题】:Is there a way to find QObject child by class name?有没有办法通过类名找到 QObject 子项?
【发布时间】:2018-08-19 07:37:54
【问题描述】:

如果我们知道它的类型和名称(如果指定),就有可能找到一个孩子,如下所示:

QPushButton *button = parentWidget->findChild<QPushButton *>("button1");

但是每个QObject 都有metaObject() 函数属性,它返回QMetaObject*。反过来QMetaObject 具有功能className()。是否可以像这样通过类名轻松找到QObject

QWidget *widget = (QWidget*)parentWidget->findByClassName("QPushButton", "button1");

或者是获取所有QWidget孩子的唯一方法

QList<QWidget *> widgets = parentWidget->findChildren<QWidget *>("widgetname");

然后通过metaObject()-&gt;className()过滤std::find_if列表?

【问题讨论】:

    标签: c++ qt qobject qmetaobject


    【解决方案1】:

    如果我们想指定一些CustomWidget 类来查找并且不希望或没有能力添加它的标题定义,我的解决方案,因此只想通过className() 进行搜索:

    QList<QWidget *> widgets = parentWidget->findChildren<QWidget *>();
            // or QApplication::allWidgets();
    
    QList<QWidget *>::iterator it = std::find_if(widgets.begin(), widgets.end(),
        [](QWidget *widget) -> bool {
            return QLatin1String(widget->metaObject()->className()) ==
                   "CustomWidget";
        });
    

    【讨论】:

    • 而不是QString::fromLatin1(,使用QLatin1String( 甚至qstrcmp(foo, bar) == 0:后两者是Qt 进行latin1 字符串比较的可移植方式,将避免转换为QString 的常量。
    【解决方案2】:

    您可以“欺骗”并利用 ABI 的属性来访问该类型,即使不包括其标头。我们真正关心的是声明一个变量,该变量将为CustomWidget::staticMetaObject 生成正确的错位名称。

    一个类和QObject::findChildren

    根据语言标准,这将是 UB,但所有主流 ABI 都支持这种 hack。

    class NoneSpecial { // A base used not to violate ODR
      NoneSpecial() = delete;
      NoneSpecial(const NoneSpecial &) = delete;
      NoneSpecial(NoneSpecial &&) = delete;
      void operator=(const NoneSpecial &) = delete;
      void operator=(NoneSpecial &&) = delete;
      ~NoneSpecial() = delete;
    };
    class CustomWidget final : NoneSpecial { // Must not inherit any other base!
    public:
      static const QMetaObject staticMetaObject;
    };
    
    template <typename T> QList<QWidget*> getWidgetChildren(QWidget *parent,
      Qt::FindChildOptions options = Qt::FindChildrenRecursively)
    {
      auto const widgets = parent->findChildren<T*>();
      return reinterpret_cast<const QList<QWidget*>&>(widgets);
    }
    
    auto widgets = getWidgetChildren<CustomWidget>(parentWidget);
    

    命名空间和findChildren的实现细节

    这也适用于所有健全的编译器,即使根据标准它是 UB。

    namespace CustomWidget {
      extern const QMetaObject staticMetaObject;
    }
    
    QList<QWidget*> getWidgetChildren(QWidget *parent, const QMetaObject & mo,
      Qt::FindChildOptions options = Qt::FindChildrenRecursively)
    {
      QList<QWidget*> widgets;
      qt_qFindChildren_helper(parent, {}, mo,
                              reinterpret_cast<QList<void*>*>(&widgets),
                              options);
      return widgets;
    }
    
    auto widgets = getWidgetChildren(parentWidget, CustomWidget::staticMetaObject);
    

    直接使用重整名称

    我们可以直接引用staticMetaObject 的重命名 - 这适用于 GCC、ICC 和 Clang。不幸的是,由于the mangled names not being valid identifiers,没有办法在 MSVC 上实现它。 Length 参数在编译时被断言为正确的,但不幸的是,没有预处理器技巧来获取它的长度并避免传入它的需要。

    #ifdef __GNUG__
    // Works on gcc, clang and icc
    #define DECLARE_STATIC_METAOBJECT(Class, Length) \
    inline const QMetaObject & Class##_staticMetaObject() { \
      static_assert(sizeof(#Class) == (Length+1)); \
      extern const QMetaObject _ZN##Length##Class##16staticMetaObjectE; \
      return _ZN##Length##Class##16staticMetaObjectE; \
    }
    
    DECLARE_STATIC_METAOBJECT(CustomWidget, 16)
    #endif
    
    auto widgets = getWidgetChildren(parentWidget, CustomWidget_staticMetaObject());
    

    导出已知名称的符号

    此解决方案是上述命名空间方法的一种变体,但不涉及任何 UB。它确实需要添加

    // metaexport.h
    #define DEFINE_META_EXPORT(Class) \
      const QMetaObject & Class#_staticMetaObject() { return Class::staticMetaObject; }
    #define DECLARE_META_EXPORT(Class) const QMetaObject & Class#_staticMetaObject();
    
    // customwidget.cpp
    #include "customwidget.h"
    #include "metaexport.h"
    DEFINE_META_EXPORT(CustomWidget)
    ...
    
    // myclass.cpp
    // doesn't include "customwidget.h"
    #include "metaexport.h"
    DECLARE_META_EXPORT(CustomWidget)
    

    然后使用qt_qFindChildren_helper,就像上面的第二个(命名空间)解决方案一样。

    【讨论】:

    • 应该是跨平台的。
    • @AlekseyKontsevich 是的,除非你的平台真的很晦涩。第一个解决方案适用于所有开源 C++ 编译器、ICC 和 MSVC。第二个适用于开源 C++ 编译器和 ICC。我并不是要说服您使用它,只是指出它确实是跨平台的。由于假设某类 UB 的某种行为,它有点脆弱,但很难想出一个可以破坏它的编译策略。虽然这个空间可能是第一个听到它的地方:很多人都有动力分享他们的见解。
    【解决方案3】:

    findChild() 已经允许您指定要搜索的类型。

    第二个参数其实是objectName字符串属性。

    如果您询问是否可以将类类型指定为字符串,似乎没有这样的选项。

    您可以轻松实现这样的功能,只需迭代对象树并查询每个对象的元对象以获取类名并与您的字符串进行比较。

    QObject * findByClassName(const QObject * const o, const char *name) {
      QObject * res = nullptr;
      foreach (QObject * c, o->children()) {
        if (res) break;
        if (QLatin1String(c->metaObject()->className()) == name) res = c;
        else res = findByClassName(c, name);
      }
      return res;
    }
    

    然后简单地findByClassName(parentWidget, "QPushButton"),显然,如果你想将指针作为具体类型,你可以扩展它以包含objectName并做一些qobject_casting......如果你这样做了,你无论如何都应该使用现有的findChild() 函数...仅当您提前不知道类型时,将类型指定为字符串才有意义,并且据说...在确定期间运行时。

    【讨论】:

    • 如果我想将任何小部件转换为简单的 QWidget,将类型指定为字符串是有意义的。 findChild() 不允许我按名称搜索。在另一个答案中查看我的解决方案。我想可能有一些更简单的。你的也够用了。
    • @AlekseyKontsevich 这是错误的,findChild() 可以使用具体类类型或其任何基类类型。因此,如果这是您想要的,您可以从按钮中获取小部件。这是完全有效的。
    • 你是对的。但是如果我想指定一些CustomWidget 类来查找并且不想或没有能力添加它的标题定义,那么我只想通过className() 进行搜索。
    • 好吧,在这种情况下,上面的解决方案就是要走的路。
    • 是的,您的解决方案可能会运行得更快,因为它只搜索/循环一次 - 接受它。由于children() 仅返回直接子级:文档对此一无所知。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    • 1970-01-01
    • 2016-05-13
    • 2019-05-20
    • 2020-07-26
    相关资源
    最近更新 更多