9

I have a QWidget in a dialog. Over the course of the program running, several QCheckBox * objects are added to the layout like this:

QCheckBox *c = new QCheckBox("Checkbox text");
ui->myWidget->layout()->addWidget(c);

This works fine for all the checkboxes. However, I also have a QPushButton called "clear" in my dialog, which when it is pressed should empty everything out of myWidget, leaving it blank like it was before any of the QCheckboxes were added. I've been looking around online and in the docs but I am having trouble finding a way to do this. I found this question which I thought was similar to my problem, and tried their solution like this:

void myClass::on_clear_clicked()
{
  while(ui->myWidget->layout()->count() > 0)
  {
    QLayoutItem *item = ui->myWidget->layout()->takeAt(0);
    delete item;
  }
}

This however did not seem to do anything. It's worth noting that I'm not sure if this is translated from his answer correctly; it was a bit unclear how the function given should be implemented, so I made my best educated guess. If anyone knows what I can change in the above to make it work (or just a different way that would work), it would be greatly appreciated.

Tarod
  • 6,732
  • 5
  • 44
  • 50
thnkwthprtls
  • 3,287
  • 11
  • 42
  • 63

4 Answers4

14

You can try this:

    while ( QLayoutItem* item = ui->myWidget->layout()->takeAt( 0 ) )
    {
        Q_ASSERT( ! item->layout() ); // otherwise the layout will leak
        delete item->widget();
        delete item;
    }
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
Nejat
  • 31,784
  • 12
  • 106
  • 138
  • ...sorry, I thought this was working, but it actually wasn't :/ thanks though – thnkwthprtls Mar 26 '14 at 14:42
  • item->widget() returns 0 if the layout item is a spacer or a child layout. This will crash – galinette Apr 04 '17 at 10:30
  • This will work but you have to check if `item->widget()` is valid and also it's preferred to call the object's [`deleteLater`](https://doc.qt.io/qt-5/qobject.html#deleteLater) as opposed to just deleting it. Just patch that line with `if (auto widget = item->widget()) widget->deleteLater();` – iKlsR May 03 '17 at 17:29
  • @iKlsR Neither is true: `delete` has defined behavior when passed a null pointer - it does nothing. It's fine to delete widgets whenever, unless this code is within a slot invoked from the widget *and* it uses the widget context (i.e. `this`). – Kuba hasn't forgotten Monica Mar 05 '18 at 16:48
  • This code would be slightly more resilient by adding `Q_ASSERT(sender() != item->widget() || ! sender())` - this will avoid hard to debug problems due to deleting a sender widget. It's much easier to fail hard in such a case. It'll have no performance impact in release builds. – Kuba hasn't forgotten Monica Mar 05 '18 at 16:57
  • @KubaOber Not disagreeing, can't remember where I used this but what I do remember is qt throwing exceptions when calling delete, I can also recall reading that to defer as above from the docs so maybe it was some weird use case where the widget was in use. – iKlsR Mar 05 '18 at 19:14
10

The wonderful thing about layouts is that they handle a deletion of a widget automatically. So all you really need is to iterate over the widgets and you're done. Since you want to wipe out all children of a given widget, simply do:

for (auto widget: ui->myWidget::findChildren<QWidget*>
                                            ({}, Qt::FindDirectChildrenOnly))
  delete widget;

No need to worry about layouts at all. This works whether the children were managed by a layout or not.

If you want to be really correct, you would need to ignore the widgets that are child widgets but are stand-alone windows. This would be the case if this was in general-purpose library code:

for (auto widget: ui->myWidget::findChildren<QWidget*>
                                            ({}, Qt::FindDirectChildrenOnly)) 
  if (! widget->windowFlags() & Qt::Window) delete widget;

Alternatively, if you only want to delete the children managed by a given layout and its sublayouts:

void clearWidgets(QLayout * layout) {
   if (! layout)
      return;
   while (auto item = layout->takeAt(0)) {
      delete item->widget();
      clearWidgets(item->layout());
   }
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • The clearWidgets recursive function was the only way I found to be able to delete nested layouts / widgets that were created programmatically. Not having a UI handle to things makes things hard, but the above function was the secret sauce I needed! – Neil Ruggiero Jun 10 '20 at 20:25
2

Given that you have a widget hierarchy consisting of cascaded layouts containing widgets then you should better go for the following.

Step 1: Delete all widgets

    QList< QWidget* > children;
    do
    {
       children = MYTOPWIDGET->findChildren< QWidget* >();
       if ( children.count() == 0 )
           break;
       delete children.at( 0 );
    }
    while ( true );

Step 2: Delete all layouts

    if ( MYTOPWIDGET->layout() )
    {
        QLayoutItem* p_item;
        while ( ( p_item = MYTOPWIDGET->layout()->takeAt( 0 ) ) != nullptr )
            delete p_item;
        delete MYTOPWIDGET->layout();
    }

After step 2 your MYTOPWIDGET should be clean.

boto
  • 455
  • 2
  • 13
0

PySide2 Solution:

from PySide2 import QtWidgets


def clearLayout(layout: QtWidgets.QLayout):
    for i in reversed(range(layout.count())):
        item = layout.itemAt(i)
        if isinstance(item, QtWidgets.QLayout):
            clearLayout(item)
        elif item.widget():
            item.widget().setParent(None)
        else:
            layout.removeItem(item)
BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28