Clear all widgets in a layout in pyqt
Asked Answered
S

15

65

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.

Shoifet answered 24/12, 2010 at 21:7 Comment(0)
O
136

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.

Orr answered 27/10, 2012 at 19:50 Comment(6)
for i in reversed(range(layout.count()-1)): is correct to prevent an out of bounds errorAeniah
You should use takeAt() instead of itemAt(). takeAt() will also remove spacers/stretches (that do not have a parent) from QBoxLayout: widget = layout.takeAt(i).widget(); if widget is not None: widget.setParent(None)Jarrodjarrow
Are you sure about the setParent(None) approche? I dont think this will delete the Widget. "The new widget is deleted when its parent is deleted." sound to me like a recursive call of deleteLater() when it is called for the parent.Measured
I wrote a simple test that shows that setParent(None) doesn't delete the Widget. Even if this command answers the question in the way of clearing the layout, it is should not be recommended. This will lead to a memory leak!Measured
The layout.item(i).widget().setParent(None) not delete the widget, its set parent holder as none. That means widget in memory but not attached to any parents. So it causes a memory leak. You should use deleteLater() functionAppolonia
This and the other answers here were a great help, but I noticed a small glitch. Setting the widget's parent to None will cause the widget to become a window that may briefly appear on the display. You can hide it first to prevent this, or use deleteLater. See pythonguis.com/faq/pyqt-widgets-appearing-as-separate-windowsSarah
T
42

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.

Theriault answered 8/4, 2012 at 23:56 Comment(1)
I assume you mean child.widget().deleteLater()... Thanks a ton for this - appears to be a fairly elegant solution. If deleteLater() gives me any trouble I may try close() or setParent(None) as others suggest.Philanthropic
C
30

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.

Cheerly answered 15/8, 2014 at 16:18 Comment(3)
Thanks a lot, it was driving me crazy the fact I was getting "Process finished with exit code 139" after clearing and adding more items few times.Judijudicable
setParent(None) doesn't delete the widgets. See my comments and answers on palens answer. This is also the reason for the Segmentation fault. Use deleteLater() instead.Measured
python widget.setAttribute(QtCore.Qt.WA_DeleteOnClose) widget.close() gridLayout.removeWidget(widget) It works for me perfectlyClaudeclaudel
T
21

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())
Teleutospore answered 15/4, 2014 at 14:42 Comment(1)
Apparently taken from https://mcmap.net/q/297758/-remove-all-items-from-a-layout?Too
K
14

You can use the close() method of widget:

for i in range(layout.count()): layout.itemAt(i).widget().close()
Kettledrum answered 28/11, 2011 at 16:21 Comment(2)
this I presume wont delete them but just close them, you can test it with accepted answer test : for i in range(layout.count()): print i / print(i)Pulliam
PyQt6 documentation for QWidget::close() method states that it will delete the widget if the flag Qt::WA_DeleteOnClose is setPiecedyed
K
7

I use:

    while layout.count() > 0: 
        layout.itemAt(0).setParent(None)
Kong answered 8/10, 2014 at 15:57 Comment(1)
for my version of PyQT, had to add a .widget() call in the middle: layout.itemAt(0).widget().setParent(None)Disinclination
I
5

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!

Irresolute answered 5/8, 2018 at 3:9 Comment(3)
Upvote for the recursive version. I also like that you added the None check that a lot of other answers omitted. I also had problems with calling removeWidget before setParents, like others suggested.Concernment
That being said, I think this code can be cleaned up a bit. If I read the other comments correctly then using takeAt() instead of itemAt() will deal with spacers implicitly so you could probably get rid of the elif branch. The other thing is that you call widget() and itemAt() more often than you need to. Calling them once and using a variable would make the code more readable.Concernment
One more thing: I think your variable names are off a little. When you call itemAt() then the result variable should be called item, not layout, because the function returns a QLayoutItem, not a QLayout. This also makes it a bit confusing what your function actually expects as input. Your input variable is called layout, but it is really expecting a QLayoutItem, right? Naming is important here to make the code more understandable.Concernment
N
3

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)
Nabal answered 19/4, 2011 at 0:38 Comment(0)
F
2

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.

Fitment answered 24/12, 2010 at 21:31 Comment(2)
But removeWidget() takes a widget as an argument removeWidget (self, QWidget w). I don't have the reference to the widget.Shoifet
@Falmarri: grid.itemAt(idx).widget() to go by index.Fitment
A
2

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()
Arana answered 6/5, 2021 at 22:52 Comment(0)
A
1

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.

Anglo answered 25/12, 2010 at 16:42 Comment(2)
Well this is my first Qt app (other than just messing with it) so I'm sure I'm doing a lot of things really inefficiently. For not plot_layout will only have 1 widget at a time (I made it a gridlayout for future expansion). The widget it has is a custom widget that extends matplotlib's FigureCanvasQTAgg. I have a lot of plots, and what I want to do is when the user clicks on a plot in a treeview (this is working), I want to show that plot.Shoifet
Maybe it might be a better idea to move the plotting code from the FigureCanvasQTAgg subclasses to a separate place, just updating a given Figure on request. Then you can simply redraw the plot instead of messing with widgets and layouts. See eli.thegreenplace.net/files/prog_code/qt_mpl_bars.py.txt for example code.Magnetomotive
C
1
for i in reversed(range(layout.count())):
    if layout.itemAt(i).widget():
        layout.itemAt(i).widget().setParent(None)
    else:
        layout.removeItem(layout.itemAt(i))
Cardew answered 6/12, 2021 at 15:46 Comment(0)
T
0
        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)
Thies answered 15/12, 2012 at 23:15 Comment(0)
L
0

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())
Louralourdes answered 5/10, 2023 at 18:17 Comment(0)
S
0
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)
        ......
Species answered 26/6, 2024 at 22:30 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.