Есть ли способ найти дочерний объект QObject по имени класса?

Можно найти ребенка, если мы знаем его тип и имя (если указано) следующим образом:

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");

а затем отфильтруйте список с помощью std::find_if от metaObject()->className()?

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_castЕсли вы хотите получить указатель как конкретный тип... который, если вы сделали, вы просто должны использовать существующий findChild() функция в любом случае... Указание типа в виде строки имеет смысл только тогда, когда вы не знаете тип заранее, а это, скажем,... определяется во время выполнения.

Мое решение, если мы хотим указать некоторые 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 QString::fromLatin1(widget->metaObject()->className()) ==
               "CustomWidget";
    });

Вы можете "обманывать" и использовать свойства ABI для доступа к типу, даже не включая его заголовок. Все, что нам действительно нужно, это объявить переменную, которая сгенерирует правильное искаженное имя для CustomWidget::staticMetaObject,

Класс и QObject::findChildren

В соответствии со стандартом языка это будет UB, но все основные ABI поддерживают этот взлом.

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. К сожалению, нет способа реализовать это в 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 как в 2-м (пространство имен) решение выше.

Другие вопросы по тегам