PySide: Removing a widget from a layout
Asked Answered
S

2

27

I'm trying to remove a Qt widget from a layout in a PySide application.

Here is a minimal example. It is a widget with 5 buttons in it, and the middle one is supposed to remove itself when clicked:

import sys
from PySide import QtGui

app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
buttons = [QtGui.QPushButton(str(x)) for x in xrange(5)]

def deleteButton():
    b = layout.takeAt(2)
    buttons.pop(2)
    del b
buttons[2].clicked.connect(deleteButton)

map(layout.addWidget, buttons)
widget.setLayout(layout)
widget.show()
app.exec_()

What actually happens is this:

What actually happens

The button is unclickable and clearly isn't taken into consideration for the layout computations, but its image stays in place.

According to the Qt documentation, the correct way of deleting all objects from a layout is:

while ((child = layout->takeAt(0)) != 0) {
    delete child;
}

Here I just want to delete the third button, so I just call takeAt(2), and then del b to call the destructor on that item. The button object is also .pop'd from the buttons list to make sure there is no leftover reference to the object. How does my code differ from the one in the Qt docs that would cause such a behavior?

Secco answered 28/3, 2012 at 0:2 Comment(1)
I just want to congratulate you on a really well formed question. I have been seeing so many lately that are simply a few sentences with no context, or, a massive amount of code that we are expected to read. This has a very clear problem stated, along with an extremely concise and runnable code example. Your pictures also clarify the issue even further. And you show what you have tried. Bravo!Landmeier
L
46

Super simple fix:

def deleteButton():
    b = layout.takeAt(2)
    buttons.pop(2)
    b.widget().deleteLater()

You first have to make sure you are addressing the actual button and not the QWidgetItem that is returned from the layout, and then call deleteLater() which will tell Qt to destroy the widget after this slot ends and control returns to the event loop.

Another example illustrates why the problem is occurring. Even though you take the layout item, the underlying widget is still parented to the original layouts widget.

def deleteButton():
    b = layout.takeAt(2)
    buttons.pop(2)
    w = b.widget()
    w.setParent(None)

This is not the preferred way, as it still leaves the cleanup of the object ambiguous. But it shows that clearing the parent allows it to leave the visual display. Use deleteLater() though. It properly cleans everything up.

Landmeier answered 28/3, 2012 at 0:11 Comment(3)
Perfect, thanks~ My hair also thanks you because it is not being pulled anymore.Secco
Hah. Yea I got bit by this too when I first started working with layouts that had children coming and going.Landmeier
Just to add to a very good answer: Q*Layouts are never parent of a widget because parent of a widget must be another widget and Q*Layout is not derived from QWidget. They merely act as a transfer agent for parenthood. Even if you remove the widget from a layout, the container widget still remains as the parent and your button is still there.Muire
A
0

The answer that 'jdi' provided is valid, although If anyone is interested, I tried implementing what is suggested in the Qt Documentation with the loop of every child Widget, and I got the following code working in Python PySide6:

def delete():
    while ((child := layout.takeAt(0)) != None):
        child.widget().deleteLater()
Ain answered 30/1, 2022 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.