Falmarri
Falmarri

Reputation: 48559

Clear all widgets in a layout in pyqt

Is there a way to clear (delete) all the widgets in a layout?

self.plot_layout = QtGui.QGridLayout()
self.plot_layout.setGeometry(QtCore.QRect(200,200,200,200))
self.root_layout.addLayout(self.plot_layout)
self.plot_layout.addWidget(MyWidget())

Now I want to replace the widget in plot_layout with a new widget. Is there an easy way to clear all the widgets in plot_layout? I don't see any method such.

Upvotes: 65

Views: 123156

Answers (15)

Dong He Zheng
Dong He Zheng

Reputation: 129

class AnyClass(QWidget):
    def __init__(self):
        super().__init__()

        self.layout = QVBoxLayout()
    
        ......
    
        self.setLayout(self.layout)
    
        ......
    
    def clear_layout(self)
        while self.layout.count() > 0:
            item = self.layout.itemAt(0)
            widget = item.widget()
            if widget is None:
                self.layout.removeItem(item)
            else:
                self.layout.removeWidget(widget)
        ......

Upvotes: 0

vitperov
vitperov

Reputation: 1397

There are problems if you have nested layouts but you are trying to delete widgets only. It can be solved if you check items type and if the item is a layout you should recursively call the function:

def clearLayout(layout):
    for i in reversed(range(layout.count())): 
        child = layout.itemAt(i)
        if child.widget() is not None:
            child.widget().deleteLater()
        elif child.layout() is not None:
            clearLayout(child.layout())

Upvotes: 0

Arthur Facredyn
Arthur Facredyn

Reputation: 1148

Most of the existing answers don't account for nested layouts, so I made a recursive function, that given a layout it will recursively delete everything inside it, and all the layouts inside of it. here it is:

def clearLayout(layout):
    print("-- -- input layout: "+str(layout))
    for i in reversed(range(layout.count())):
        layoutItem = layout.itemAt(i)
        if layoutItem.widget() is not None:
            widgetToRemove = layoutItem.widget()
            print("found widget: " + str(widgetToRemove))
            widgetToRemove.setParent(None)
            layout.removeWidget(widgetToRemove)
        elif layoutItem.spacerItem() is not None:
            print("found spacer: " + str(layoutItem.spacerItem()))
        else:
            layoutToRemove = layout.itemAt(i)
            print("-- found Layout: "+str(layoutToRemove))
            clearLayout(layoutToRemove)

I might not have accounted for all UI types, not sure. Hope this helps!

Upvotes: 5

BaiJiFeiLong
BaiJiFeiLong

Reputation: 4609

for i in reversed(range(layout.count())):
    if layout.itemAt(i).widget():
        layout.itemAt(i).widget().setParent(None)
    else:
        layout.removeItem(layout.itemAt(i))

Upvotes: 1

user1021810
user1021810

Reputation: 31

I had issues with solutions previously mentioned. There were lingering widgets that were causing problems; I suspect deletion was scheduled, but not finihsed. I also had to set the widgets parent to None. this was my solution:

def clearLayout(layout):
    while layout.count():
        child = layout.takeAt(0)
        childWidget = child.widget()
        if childWidget:
            childWidget.setParent(None)
            childWidget.deleteLater()

Upvotes: 3

user3369214
user3369214

Reputation: 431

That's how I clear a layout :

def clearLayout(layout):
    if layout is not None:
        while layout.count():
            child = layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            elif child.layout() is not None:
                clearLayout(child.layout())

Upvotes: 21

Blaa_Thor
Blaa_Thor

Reputation: 614

The answer from PALEN works well if you do not need to put new widgets to your layout.

for i in reversed(range(layout.count())): 
    layout.itemAt(i).widget().setParent(None)

But you will get a "Segmentation fault (core dumped)" at some point if you empty and fill the layout many times or with many widgets. It seems that the layout keeps a list of widget and that this list is limited in size.

If you remove the widgets that way:

for i in reversed(range(layout.count())): 
    widgetToRemove = layout.itemAt(i).widget()
    # remove it from the layout list
    layout.removeWidget(widgetToRemove)
    # remove it from the gui
    widgetToRemove.setParent(None)

You won't get that problem.

Upvotes: 30

Nadeem Douba
Nadeem Douba

Reputation: 1092

This may be a bit too late but just wanted to add this for future reference:

def clearLayout(layout):
  while layout.count():
    child = layout.takeAt(0)
    if child.widget():
      child.widget().deleteLater()

Adapted from Qt docs http://doc.qt.io/qt-5/qlayout.html#takeAt. Remember that when you are removing children from the layout in a while or for loop, you are effectively modifying the index # of each child item in the layout. That's why you'll run into problems using a for i in range() loop.

Upvotes: 42

PALEN
PALEN

Reputation: 2876

After a lot of research (and this one took quite time, so I add it here for future reference), this is the way I found to really clear and delete the widgets in a layout:

for i in reversed(range(layout.count())): 
    layout.itemAt(i).widget().setParent(None)

What the documentation says about the QWidget is that:

The new widget is deleted when its parent is deleted.

Important note: You need to loop backwards because removing things from the beginning shifts items and changes the order of items in the layout.

To test and confirm that the layout is empty:

for i in range(layout.count()): print i

There seems to be another way to do it. Instead of using the setParent function, use the deleteLater() function like this:

for i in reversed(range(layout.count())): 
    layout.itemAt(i).widget().deleteLater()

The documentation says that QObject.deleteLater (self)

Schedules this object for deletion.

However, if you run the test code specified above, it prints some values. This indicates that the layout still has items, as opposed to the code with setParent.

Upvotes: 136

SoloPilot
SoloPilot

Reputation: 1534

I use:

    while layout.count() > 0: 
        layout.itemAt(0).setParent(None)

Upvotes: 7

borovsky
borovsky

Reputation: 893

        for i in reversed (range(layout.count())):
            layout.itemAt(i).widget().close()
            layout.takeAt(i)

or

        for i in range(layout.count()):
            layout.itemAt(0).widget().close()
            layout.takeAt(0)

Upvotes: 0

Volodymyr Pavlenko
Volodymyr Pavlenko

Reputation: 671

You can use the close() method of widget:

for i in range(layout.count()): layout.itemAt(i).widget().close()

Upvotes: 14

joshua
joshua

Reputation: 2519

My solution to this problem is to override the setLayout method of QWidget. The following code updates the layout to the new layout which may or may not contain items that are already displayed. You can simply create a new layout object, add whatever you want to it, then call setLayout. Of course, you can also just call clearLayout to remove everything.

def setLayout(self, layout):
    self.clearLayout()
    QWidget.setLayout(self, layout)

def clearLayout(self):
    if self.layout() is not None:
        old_layout = self.layout()
        for i in reversed(range(old_layout.count())):
            old_layout.itemAt(i).widget().setParent(None)
        import sip
        sip.delete(old_layout)

Upvotes: 3

Harald Scheirich
Harald Scheirich

Reputation: 9764

A couple of solutions, if you are swapping between known views using a stacked widget and just flipping the shown index might be a lot easier than adding and removing single widgets from a layout.

If you want to replace all the children of a widget then the QObject functions findChildren should get you there e.g. I don't know how the template functions are wrapped in pyqt though. But you could also search for the widgets by name if you know them.

Upvotes: 1

user395760
user395760

Reputation:

From the docs:

To remove a widget from a layout, call removeWidget(). Calling QWidget.hide() on a widget also effectively removes the widget from the layout until QWidget.show() is called.

removeWidget is inherited from QLayout, that's why it's not listed among the QGridLayout methods.

Upvotes: 2

Related Questions