4

It is possible to find a child if we know its type and name (if specified) like this:

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

However each QObject has metaObject() function property which returns QMetaObject*. In its turn QMetaObject has function className(). Is it possible to easily find QObject by class name like this:

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

Or the only way to get all QWidget children by

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

and then filter the list with std::find_if by metaObject()->className()?

Aleksey Kontsevich
  • 4,671
  • 4
  • 46
  • 101

3 Answers3

7

findChild() already allows you to specify the type of what you are searching for.

The second parameter is actually the objectName string property.

If you are asking whether you can specify the class type as string, there doesn't appear to be such an option.

You could easily make such function, simply iterate the object tree and query the each object's meta object for the class name and compare against your string.

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;
}

And then simply findByClassName(parentWidget, "QPushButton"), obviously, you can expand on this to include the objectName and do some qobject_casting if you want to get the pointer as a concrete type... which if you did, you simply should have used the existing findChild() function anyway... Specifying the type as a string only makes sense when you don't know the type in advance, and it is say... determined during the runtime.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • Specifying the type as a string makes sense if I want to cast any widget to simple QWidget. findChild() does not allows me to search by name. See my solution in another answer. I thought there could be some simpler one. Your one is good enough too. – Aleksey Kontsevich Mar 11 '18 at 12:58
  • 1
    @AlekseyKontsevich this is wrong, `findChild()` can work either with the concrete class type or any of its base class types. So you can get a widget out of a button if that's what you want. It is perfectly valid. – dtech Mar 11 '18 at 13:03
  • You are right. But if I want to specify some `CustomWidget` class to find and do not want or have no ability to add it's header definition, then I'd like to search only by `className()`. – Aleksey Kontsevich Mar 11 '18 at 13:12
  • Well, in that case the above solution is the way to go. – dtech Mar 11 '18 at 13:15
  • Yes, probably Your solution will work faster cause it searches/cycles only once - accept it. As `children()` returns only direct children only: docs says nothing about. – Aleksey Kontsevich Mar 11 '18 at 13:26
  • It returns direct children only, but the function is recursive, so it checks the entire tree until it finds a match. It will still be faster than your solution, because that's a lot of extra work. You may also want to modify it to search all direct children first before you go deeper down the tree. Depending on what structure you expect to have during runtime. It can be modified to find multiple items too, just replace the return value with an object list reference and add every match to it. – dtech Mar 11 '18 at 13:31
3

My solution if we want to specify some CustomWidget class to find and do not want or have no ability to add it's header definition, and therefore would like to search only by 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";
    });
Aleksey Kontsevich
  • 4,671
  • 4
  • 46
  • 101
  • 1
    Instead of `QString::fromLatin1(`, use `QLatin1String(` or even `qstrcmp(foo, bar) == 0`: the latter two are Qt's portable way to do latin1 string comparisons and will avoid the const of a conversion to `QString`. – Kuba hasn't forgotten Monica Mar 16 '18 at 21:24
0

You can "cheat" and leverage the properties of the ABI to access the type even without including its header. All we really care for is to declare a variable that will generate the correct mangled name for the CustomWidget::staticMetaObject.

A class and QObject::findChildren

Per the language standard, this would be UB, but all mainstream ABIs support this 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);

A namespace and findChildren's implementation detail

This also works fine on all sane compilers, even if per the standard it's 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);

Direct use of mangled name

We can refer directly to the staticMetaObject's mangled name - this works on GCC, ICC and Clang. Unfortunately, there's no way to implement it on MSVC, due to the mangled names not being valid identifiers. The Length argument is asserted to be correct at compile time, but unfortunately there is no preprocessor trick to get its length and avoid the need for passing it in.

#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());

Export of of symbols with known names

This solution is a variation of the namespace approach above, but it doesn't involve any UB. It does require additions

// 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)

Then use qt_qFindChildren_helper as in the 2nd (namespace) solution above.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • should be cross-platform. – Aleksey Kontsevich Mar 17 '18 at 22:26
  • @AlekseyKontsevich It is, unless your platform is truly obscure. The first solution works on all open-source C++ compilers, ICC and MSVC. The second works on open-source C++ compilers and ICC. I'm not trying to convince you to use it, just pointing out that it is indeed quite cross-platform. It is a bit brittle due to assuming a certain behavior from a certain class of UBs, but it'd be very hard to come up with a compilation strategy that would break it. Although this space is probably where one would be the first to hear about it: lots of people motivated to share their insights. – Kuba hasn't forgotten Monica Mar 19 '18 at 18:46