How to find widgets of a given type in a QLayout?
Asked Answered
S

1

6

I am able to find the QRadioButton this way:

for(int i = 0; i < ui->verticalLayout->count(); i++)
{
    QRadioButton* r = qobject_cast<QRadioButton*>(ui->verticalLayout->itemAt(i)->widget());
    if(r->isChecked())
        //found it!
}

But I don't like this way of iterating over elements and would like to use the foreach construct. My first attempt fails:

foreach(QRadioButton* child, ui->verticalLayout->findChildren<QRadioButton*>())
{
    if(child->isChecked())
        //found it!
}

Problem is that the ui->verticalLayout->findChildren<QRadioButton*>() returns zero elements. It also returns no elements with findChildren<QObject*>(). Can someone please explain this behaviour?

Note: the title of this question is almost identical to mine, but it is related to python Qt, and does not contain any helpful information for me.

Children vs elements?

Experimentally I figured that ui->verticalLayout->children().count() returns zero where as ui->verticalLayout->count() returns the number of elements I have in the verticalLayout. This implies that itemAt(i) and findChild<QRadioButton*>() do not access the same list. Looking at the Qt documentation on children() did not help me.

Can someone point me to a good material on Qt child parent concepts? I am assuming that this has nothing to do with accesing nested objects which is what I am trying to accomplish.

Edit

The answers to this question contains valuable information on another topic, whereas his answer makes clarification on my question about children of layout. Thus this is not a duplicate question.

Scharf answered 21/7, 2015 at 18:7 Comment(3)
possible duplicate of Qt - iterating through QRadioButtonsKovacs
I've modified my answer to filter on widget types.Ecotype
@Kovacs The other question is more specific, and has a valuable answer about using a QButtonGroup. I'd leave them separate for the moment.Ecotype
W
6

The widgets are not children of the layout in the sense of being QObject children - they are children of the parent widget. A QWidget can only be a child of another QWidget - thus you can't ever expect widgets to be layout's children. While new QWidget(new QWidget()) works, new QWidget(new QHBoxLayout()) won't compile.

You could iterate a widget's children of a given type as follows:

// C++11
for (auto button : findChildren<QRadioButton*>()) if (button->isChecked()) {
  ...
}

// C++98
Q_FOREACH (QWidget * button, findChildren<QRadioButton*>())
  if (button->isChecked()) {
    ...
  }

If you're using C++11, you should use the range-based for loop, not the now obsolete foreach or Q_FOREACH.

To iterate the child widgets managed by a layout, you need an iterator adapter for the layout. For example:

#include <QLayout>
#include <QDebug>
#include <QPointer>
#include <utility>

template<class WT> class IterableLayoutAdapter;

template<typename WT>
class LayoutIterator {
   QPointer<QLayout> m_layout;
   int m_index;
   friend class IterableLayoutAdapter<WT>;
   LayoutIterator(QLayout * layout, int dir) :
      m_layout(layout), m_index(dir>0 ? -1 : m_layout->count()) {
      if (dir > 0) ++*this;
   }
   friend QDebug operator<<(QDebug dbg, const LayoutIterator & it) {
      return dbg << it.m_layout << it.m_index;
   }
   friend void swap(LayoutIterator& a, LayoutIterator& b) {
      using std::swap;
      swap(a.m_layout, b.m_layout);
      swap(a.m_index, b.m_index);
   }
public:
   LayoutIterator() : m_index(0) {}
   LayoutIterator(const LayoutIterator & o) :
      m_layout(o.m_layout), m_index(o.m_index) {}
   LayoutIterator(LayoutIterator && o) { swap(*this, o); }
   LayoutIterator & operator=(LayoutIterator o) {
      swap(*this, o);
      return *this;
   }
   WT * operator*() const { return static_cast<WT*>(m_layout->itemAt(m_index)->widget()); }
   const LayoutIterator & operator++() {
      while (++m_index < m_layout->count() && !qobject_cast<WT*>(m_layout->itemAt(m_index)->widget()));
      return *this;
   }
   LayoutIterator operator++(int) {
      LayoutIterator temp(*this);
      ++*this;
      return temp;
   }
   const LayoutIterator & operator--() {
      while (!qobject_cast<WT*>(m_layout->itemAt(--m_index)->widget()) && m_index > 0);
      return *this;
   }
   LayoutIterator operator--(int) {
      LayoutIterator temp(*this);
      --*this;
      return temp;
   }
   bool operator==(const LayoutIterator & o) const { return m_index == o.m_index; }
   bool operator!=(const LayoutIterator & o) const { return m_index != o.m_index; }
};

template <class WT = QWidget>
class IterableLayoutAdapter {
   QPointer<QLayout> m_layout;
public:
   typedef LayoutIterator<WT> const_iterator;
   IterableLayoutAdapter(QLayout * layout) : m_layout(layout) {}
   const_iterator begin() const { return const_iterator(m_layout, 1); }
   const_iterator end() const { return const_iterator(m_layout, -1); }
   const_iterator cbegin() const { return const_iterator(m_layout, 1); }
   const_iterator cend() const { return const_iterator(m_layout, -1); }
};

template <class WT = QWidget>
class ConstIterableLayoutAdapter : public IterableLayoutAdapter<const WT> {
public:
   ConstIterableLayoutAdapter(QLayout * layout) : IterableLayoutAdapter<const WT>(layout) {}
};

It's used as follows:

#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>

int main(int argc, char ** argv) {
   QApplication app(argc, argv);
   tests();
   QWidget a, b1, b3;
   QLabel b2;
   QHBoxLayout l(&a);
   l.addWidget(&b1);
   l.addWidget(&b2);
   l.addWidget(&b3);

   // Iterate all widget types as constants
   qDebug() << "all, range-for";
   for (auto widget : ConstIterableLayoutAdapter<>(&l)) qDebug() << widget;
   qDebug() << "all, Q_FOREACH";
   Q_FOREACH (const QWidget * widget, ConstIterableLayoutAdapter<>(&l)) qDebug() << widget;

   // Iterate labels only
   qDebug() << "labels, range-for";
   for (auto label : IterableLayoutAdapter<QLabel>(&l)) qDebug() << label;
   qDebug() << "labels, Q_FOREACH";
   Q_FOREACH (QLabel * label, IterableLayoutAdapter<QLabel>(&l)) qDebug() << label;
}

Some rudimentary tests look as follows:

void tests() {
   QWidget a, b1, b3;
   QLabel b2;
   QHBoxLayout l(&a);

   IterableLayoutAdapter<> l0(&l);
   auto i0 = l0.begin();
   qDebug() << i0; Q_ASSERT(i0 == l0.begin() && i0 == l0.end());

   l.addWidget(&b1);
   l.addWidget(&b2);
   l.addWidget(&b3);

   IterableLayoutAdapter<> l1(&l);
   auto i1 = l1.begin();
         qDebug() << i1; Q_ASSERT(i1 == l1.begin() && i1 != l1.end());
   ++i1; qDebug() << i1; Q_ASSERT(i1 != l1.begin() && i1 != l1.end());
   ++i1; qDebug() << i1; Q_ASSERT(i1 != l1.begin() && i1 != l1.end());
   ++i1; qDebug() << i1; Q_ASSERT(i1 != l1.begin() && i1 == l1.end());
   --i1; qDebug() << i1; Q_ASSERT(i1 != l1.begin() && i1 != l1.end());
   --i1; qDebug() << i1; Q_ASSERT(i1 != l1.begin() && i1 != l1.end());
   --i1; qDebug() << i1; Q_ASSERT(i1 == l1.begin() && i1 != l1.end());

   IterableLayoutAdapter<QLabel> l2(&l);
   auto i2 = l2.begin();
         qDebug() << i2; Q_ASSERT(i2 == l2.begin() && i2 != l2.end());
   ++i2; qDebug() << i2; Q_ASSERT(i2 != l2.begin() && i2 == l2.end());
   --i2; qDebug() << i2; Q_ASSERT(i2 == l2.begin() && i2 != l2.end());
}
Wolbrom answered 21/7, 2015 at 18:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.