有没有办法通过 class 名称找到 QObject child?
Is there a way to find QObject child by class name?
如果我们知道它的类型和名称(如果指定),就可以找到一个 child,如下所示:
QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
但是每个 QObject
都有 metaObject()
函数 属性,其中 returns QMetaObject*
。反过来 QMetaObject
具有函数 className()
。是否可以像这样通过 class 名称轻松找到 QObject
:
QWidget *widget = (QWidget*)parentWidget->findByClassName("QPushButton", "button1");
或者唯一的方法是 QWidget
children
QList<QWidget *> widgets = parentWidget->findChildren<QWidget *>("widgetname");
然后通过 metaObject()->className()
?
筛选具有 std::find_if
的列表
findChild()
已经允许您指定要搜索的内容的类型。
第二个参数其实就是objectName
字符串属性.
如果您询问是否可以将 class 类型指定为字符串,似乎没有这样的选项。
您可以轻松实现这样的功能,只需迭代对象树并查询每个对象的元对象以获取 class 名称并与您的字符串进行比较。
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_cast
ing。 .. 如果你这样做了,你应该简单地使用现有的 findChild()
函数......只有当你 不知道 时,将类型指定为字符串才有意义提前输入,据说...在运行时确定。
我的解决方案如果我们想指定一些CustomWidget
class来查找并且不想或没有能力添加它的header定义,因此只想搜索通过 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";
});
您可以 "cheat" 并利用 ABI 的属性来访问该类型,即使不包括其 header。我们真正关心的是声明一个变量,该变量将为 CustomWidget::staticMetaObject
.
生成正确的错位名称
A class 和 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
。
如果我们知道它的类型和名称(如果指定),就可以找到一个 child,如下所示:
QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
但是每个 QObject
都有 metaObject()
函数 属性,其中 returns QMetaObject*
。反过来 QMetaObject
具有函数 className()
。是否可以像这样通过 class 名称轻松找到 QObject
:
QWidget *widget = (QWidget*)parentWidget->findByClassName("QPushButton", "button1");
或者唯一的方法是 QWidget
children
QList<QWidget *> widgets = parentWidget->findChildren<QWidget *>("widgetname");
然后通过 metaObject()->className()
?
std::find_if
的列表
findChild()
已经允许您指定要搜索的内容的类型。
第二个参数其实就是objectName
字符串属性.
如果您询问是否可以将 class 类型指定为字符串,似乎没有这样的选项。
您可以轻松实现这样的功能,只需迭代对象树并查询每个对象的元对象以获取 class 名称并与您的字符串进行比较。
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_cast
ing。 .. 如果你这样做了,你应该简单地使用现有的 findChild()
函数......只有当你 不知道 时,将类型指定为字符串才有意义提前输入,据说...在运行时确定。
我的解决方案如果我们想指定一些CustomWidget
class来查找并且不想或没有能力添加它的header定义,因此只想搜索通过 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";
});
您可以 "cheat" 并利用 ABI 的属性来访问该类型,即使不包括其 header。我们真正关心的是声明一个变量,该变量将为 CustomWidget::staticMetaObject
.
A class 和 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
。