I am writing a PySide2-based applicatioon, which includes a QScrollArea that holds a lot of QPixmap images (or better: a list of QLabel's that in turn contain the pixmaps). That list of images can grow quite large over time, so when a certain number is reached I periodically remove some of these images from the scroll area - which works fine.
I do have the impression, however, that even after removing some of the images the memory consumption of my application is still the same. So removing the label widgets might not be sufficient. From the PySide2 docs on QLayout.removeWidget():
Removes the widget widget from the layout. After this call, it is the caller’s responsibility to give the widget a reasonable geometry or to put the widget back into a layout or to explicitly hide it if necessary.
In order to remove the widget I do the following:
while self.images_scroll_layout.count() > MAX_IMAGES:
to_remove = self.images_scroll_layout.itemAt(self.images_scroll_layout.count() - 1)
self.images_scroll_layout.removeItem(to_remove)
to_remove.widget().deleteLater()
So my question is: Do I need to manually destroy the labels/pixmaps I removed from the layout, or should they be garbage-collected automatically?
To understand the operation you have to have the following clear concepts:
A QObject will not be removed by the GC if it has a parent.
When a widget is added to a layout, then the widget is set as a child of the widget where the layout was established.
When using removeWidget() then only the widget is removed from the widget list that handles the layout, so the parent of the widget is still the widget that handles the layout.
To verify you can use the following code where the destroyed signal that indicates when a QObject is deleted will not be emitted.
from PySide2 import QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.add_button = QtWidgets.QPushButton(self.tr("Add"), clicked=self.add_widget)
self.remove_button = QtWidgets.QPushButton(
self.tr("Remove"), clicked=self.remove_widget
)
scrollarea = QtWidgets.QScrollArea(widgetResizable=True)
widget = QtWidgets.QWidget()
scrollarea.setWidget(widget)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.add_button)
lay.addWidget(self.remove_button)
lay.addWidget(scrollarea)
self.resize(640, 480)
self.label_layouts = QtWidgets.QVBoxLayout(widget)
self.counter = 0
def add_widget(self):
label = QtWidgets.QLabel(f"label {self.counter}")
self.label_layouts.addWidget(label)
self.counter += 1
def remove_widget(self):
item = self.label_layouts.itemAt(0)
if item is None:
return
widget = item.widget()
if widget is None:
return
widget.destroyed.connect(print)
print(f"widget: {widget} Parent: {widget.parentWidget()}")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
In conclusion: removeWidget() is not used to remove the widget from memory but only makes the layout not handle that widget, if you want to remove a widget you must use deleteLater().
def remove_widget(self):
item = self.label_layouts.itemAt(0)
if item is None:
return
widget = item.widget()
if widget is None:
return
widget.destroyed.connect(print)
widget.deleteLater()
Related
I have a QVBox layout that houses a QVBox layout and a QHBox layout. I use the other QVBox layout to hold dynamically created GUI objects and the QHBox layout to hold the buttons that add/remove those objects. Everything works correctly if I position the QHBox on top of the QVBox, but when I try to position the QHBox beneath the QVBox the objects aren't removed correctly but stay there "lingering" on top of the QHBox. I'll upload pictures to demonstrate the problem. First picture is before taking action, second is after creating a new object and third is after deleting the object
Here is the code that creates and deletes the new objects
def addClient(self):
if (len(self.clients) < 5):
client = clientComponent(self)
self.clients.append(client)
index = len(self.clients)-1
self.vLayout3.addWidget(self.clients[index])
client.setIndex(index)
self.clients[index].startButton.clicked.connect(partial(self.threadcontrol, '2', client.getIndex()))
self.clients[index].stopButton.clicked.connect(partial(self.clientstop, '0', client.getIndex()))
def deleteClient(self):
if (len(self.clients) > 1):
self.vLayout3.removeWidget(self.clients.pop())
This is where I complete the layout
def initializeUi(self):
self.mainWidget = QWidget(self)
self.setCentralWidget(self.mainWidget)
self.mainLayout = QVBoxLayout(self.mainWidget)
self.hLayout1 = QHBoxLayout()
self.hLayout2 = QHBoxLayout()
self.vLayout1 = QVBoxLayout()
self.vLayout2 = QVBoxLayout()
self.vLayout3 = QVBoxLayout()
self.addServer()
self.addClient()
self.serverBox = QGroupBox('Server')
self.clientBox = QGroupBox('Client')
self.traffic1 = QLabel('0.0Mb/s', self)
self.traffic1.setAlignment(Qt.AlignRight)
self.traffic2 = QLabel('0.0Mb/s', self)
self.traffic2.setAlignment(Qt.AlignCenter)
self.traffic3 = QLabel('0.0Mb/s', self)
self.traffic3.setAlignment(Qt.AlignLeft)
self.newClientButton = QPushButton('+', self)
self.deleteClientButton = QPushButton('-', self)
self.hLayout1.addWidget(self.traffic1)
self.hLayout1.addWidget(self.traffic2)
self.hLayout1.addWidget(self.traffic3)
self.hLayout2.addWidget(self.newClientButton)
self.hLayout2.addWidget(self.deleteClientButton)
self.vLayout2.addLayout(self.vLayout3)
self.vLayout2.addLayout(self.hLayout2)
self.mainLayout.addWidget(self.plot)
self.mainLayout.addLayout(self.hLayout1)
self.serverBox.setLayout(self.vLayout1)
self.mainLayout.addWidget(self.serverBox)
self.clientBox.setLayout(self.vLayout2)
self.mainLayout.addWidget(self.clientBox)
This is happening because your main window remains the parent of the client widgets after you remove them from the layout. You will see similar behaviour if you assign a widget a parent widget without adding it to any layout.
Removing the parent should resolve the issue.
def deleteClient(self):
if (len(self.clients) > 1):
client = self.clients.pop()
self.vLayout3.removeWidget(client)
client.setParent(None)
You may also need to make a call to adjustSize to resize the window to fit the remaining widgets
When you delete a widget from layout it still remains in parent widget's
object tree, so it gets displayed outside of any layout.
To remove a widget from the object tree call widget.setParent(None).
widget.deleteLater() also works.
Here is my MCVE(Qt4, Py2.7):
from PyQt4.QtGui import (QApplication, QWidget, QPushButton,
QVBoxLayout, QHBoxLayout)
app=QApplication([])
self = QWidget()
main_layout = QVBoxLayout(self)
clients = []
l2 = QHBoxLayout()
main_layout.addLayout(l2)
b_add = QPushButton('add', self)
l2.addWidget(b_add)
def addClient():
b = QPushButton(str(len(clients)), self)
clients.append(b)
main_layout.addWidget(b)
b_add.clicked.connect(addClient)
b_rm = QPushButton('rm', self)
l2.addWidget(b_rm)
def deleteClient():
b = clients.pop()
main_layout.removeWidget(b)
# comment out two following lines to get the behavior you observe
b.setParent(None)
self.adjustSize()
b_rm.clicked.connect(deleteClient)
self.show()
app.exec_()
On my system I also have to call self.adjustSize() after deletion to resize the main window
I'm trying to display an animated GIF on the left side of a QListWidgetItem in a QListWidget with the label text following. I've been reading that QLabels hold QMovies which can run GIF animations and that I'd need to create a custom widget and use that instead of the default QListWidgetItem, but I've had no luck. Does anyone how to do this? Am I over-complicating things?
I've written up a basic test case below:
#! /usr/bin/env python
from PySide2 import QtGui, QtWidgets, QtCore
class List_Widget_Gif(QtWidgets.QWidget):
def __init__(self, label_text, gif, parent=None):
super(List_Widget_Gif, self).__init__(parent)
# Layout
horizontal_box_layout = QtWidgets.QHBoxLayout()
# Create text label
self.text_label = QtWidgets.QLabel()
self.text_label.setText(label_text)
# Create label to apply GIF to (Apparently this is the best thing to use for GIF in this case?)
self.icon_label = QtWidgets.QLabel()
movie = QtGui.QMovie(gif, QtCore.QByteArray(), self)
self.icon_label.setMovie(movie)
movie.start()
# Add widgets to layout
horizontal_box_layout.addWidget(self.text_label)
horizontal_box_layout.addWidget(self.icon_label)
#Set the layout
self.setLayout(horizontal_box_layout)
class TestUI(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(TestUI, self).__init__(parent)
self.setObjectName("TestUI")
#Vars to pass
self.my_gif = "my_cool_animation.gif"
self.my_text = "This is awesome text"
def setup_UI(self):
#Create Default List Widget
list_widget = QtWidgets.QListWidget()
# Create Default List Widget Item
default_list_item = QtWidgets.QListWidgetItem()
# Create Custom List Widget with label and GIF motion
custom_list_widget_item = List_Widget_Gif(self.my_text, self.my_gif)
# Add default item to list widget
list_widget.insertItem(list_widget.count(), default_list_item)
# Set the default item to the custom one with the gif motion.
self.ui.layerList.setItemWidget(default_list_item, custom_list_widget_item)
#Set into UI
self.setCentralWidget(list_widget)
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication([])
test = TestUI()
test.setup_UI()
app.exec_()
First you have a typo (it should throw an exception) so you have to change self.ui.layerList to list_widget.
Correcting the above there are several possible causes of error:
The margins must be removed from the layout of the custom widget:
horizontal_box_layout.setContentsMargins(0, 0, 0, 0)
Do not use relative paths since they are the cause of silent errors, it is better to build the absolute path based on the location of another element. If I assume that the .gif is in the same folder as the .py then you can use that information to do so it changes:
import os
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
self.my_gif = os.path.join(CURRENT_DIR, "my_cool_animation.gif")
It works, you just need to resize the default item to the size of your custom item.
def setup_UI(self):
#Create Default List Widget
list_widget = QtWidgets.QListWidget()
# Create Default List Widget Item
default_list_item = QtWidgets.QListWidgetItem()
# Create Custom List Widget with label and GIF motion
custom_list_widget_item = List_Widget_Gif(self.my_text, self.my_gif)
# Add default item to list widget
list_widget.insertItem(list_widget.count(), default_list_item)
# Set the default item to the custom one with the gif motion.
list_widget.setItemWidget(default_list_item, custom_list_widget_item)
default_list_item.setSizeHint(custom_list_widget_item.size())
#Set into UI
self.setCentralWidget(list_widget)
self.show()
Output
I have a Grid layout in which I add Qlineedits at runtime.
while pushing the button I want to delete the last qline edit from the gridlaout
Why does this function delete all qlinedits at the same time ?
def deleate_widgets(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for widget in widgets:
if isinstance(widget, qtw.QLineEdit):
print("linedit: %s - %s" %(widget.objectName(), widget.text()))
widget.deleteLater() # all objects
How to change the code to only delete one widget at a time, preferably the last added widget ?
full code
#!/usr/bin/env python
"""
Interface to get the specific weight of each of the 5 containers
start_measurment_button starts thread /thread_worker
transfer_data button start query and send data to database
"""
import sys
import sqlite3
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import QtSql as qsql
from PyQt5 import sip
class AddWidget(qtw.QWidget):
'''
Interface with embedded SQL functions
'''
# Attribut Signal
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# your code will go here
self.mylist = []
# interface
# position
qtRectangle = self.frameGeometry()
centerPoint = qtw.QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
# size
self.resize(700, 410)
# frame title
self.setWindowTitle("add Widget")
# heading
heading_label = qtw.QLabel('add Widget')
heading_label.setAlignment(qtc.Qt.AlignHCenter | qtc.Qt.AlignTop)
# add Button
self.addwidget_button = qtw.QPushButton("add Widget")
self.getlistof_button = qtw.QPushButton("deleate")
self.main_layout = qtw.QGridLayout()
self.main_layout.addWidget(self.getlistof_button,0,0)
self.main_layout.addWidget(self.addwidget_button, 1, 0)
self.setLayout(self.main_layout)
self.show()
# functionality
self.addwidget_button.clicked.connect(self.add_widget)
# self.getlistof_button.clicked.connect(self.deleate_widgets_try)
def add_widget(self):
self.my_lineedit = qtw.QLineEdit()
self.mylist.append(self.my_lineedit)
self.main_layout.addWidget(self.my_lineedit)
def deleate_widgets(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for widget in widgets:
if isinstance(widget, qtw.QLineEdit):
print(widget)
# print("linedit: %s - %s" %(widget.objectName(), widget.text()))
# widget.deleteLater() # alle objects
#
# def deleate_widgets_try(self):
# widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
# my_iter = iter(widgets)
#
# if isinstance(my_iter, qtw.QLineEdit):
# next(my_iter.deleteLater()) # alle objects)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = AddWidget()
sys.exit(app.exec_())
Your function removes all widgets because you are cycling through all the widgets, from the first to the last.
Also, there is really no need to go through the whole layout, since you already keep a list of widgets that always appends the last one at the end.
Just pop out the last item from the list. Removing it from the layout shouldn't be necessary, as deleteLater() would take care of it, but that's just for demonstration purposes.
def deleate_widgets(self):
# remove the last item from the list
lastWidget = self.mylist.pop(-1)
self.main_layout.removeWidget(lastWidget)
lastWidget.deleteLater()
For the sake of completeness, your function should have done the following:
cycle the widgets through the layout backwards;
break the cycle as soon as the first (as in last) item is found;
def deleate_widgets(self):
widgets = [self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count())]
# use reversed() to cycle the list backwards
for widget in reversed(widgets):
if isinstance(widget, qtw.QLineEdit):
print("linedit: %s - %s" %(widget.objectName(), widget.text()))
widget.deleteLater()
# the line edit has been found, exit the cycle with break to avoid
# deleting further widgets
break
Also, there's really no use in creating instance attributes (self.someobject = ...) for objects that don't need a persistent reference, especially if you are creating those objects repeatedly (which will result in a constant overwrite of that attribute, making it useless) and you already are keeping them in an persistent data model object (usually a list, a tuple, a dictionary) like self.mylist in your case (and that has to be an instance attribute):
def add_widget(self):
# no need to create a "self.my_lineedit"
my_lineedit = qtw.QLineEdit()
self.mylist.append(my_lineedit)
self.main_layout.addWidget(my_lineedit)
Having seen your previous questions and comments, I strongly suggest you to better study and experiment with the Python data models, control flows and classes, as they are basic concepts (of Python and programming in general) that must be understood and internalized before attempting to do anything else.
i have this function that deleates the last QLineEdit widget from the QGridLayout
it checks if the index of the widget is the last one and if the widget is a instance of QLineEdit
---> deleates the widget
def deleate_lastlineedit(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for index, widget in enumerate(widgets):
if index == (self.main_layout.count()-1) and isinstance(widget, (qtw.QLineEdit,qtw.QLabel)):
widget.deleteLater()
break
I have added a Qlabel widget to the same row and want that the function deleates the last Qlabel and Qlinedit widget at the same time after pushing a button, so far its deleates one at a time, need to click the button two times.
I tried to insert an counter so the iteration stops not at one iteration but at two iterrations so it gets the two widgets but didnt had an effekt.
also inserted two versions of the function
one that deleates the qline edit and the other that deleates the qlabel
and connected them to the same button but didnt work either
self.getlistof_button.clicked.connect(self.deleate_lastlineedit)
self.getlistof_button.clicked.connect(self.deleate_lastqlabel)
so how can I deleate the two widgets at the same time ?
fullcode
#!/usr/bin/env python
"""
Creates an linedit when button pushed
dleates last linedit
"""
import sys
import sqlite3
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import QtSql as qsql
from PyQt5 import sip
class AddWidget(qtw.QWidget):
'''
Interface
'''
# Attribut Signal
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# your code will go here
# interface
# position
qtRectangle = self.frameGeometry()
centerPoint = qtw.QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
# size
self.resize(700, 410)
# frame title
self.setWindowTitle("add Widget")
# heading
heading_label = qtw.QLabel('add Widget')
heading_label.setAlignment(qtc.Qt.AlignHCenter | qtc.Qt.AlignTop)
# add Button
self.addwidget_button = qtw.QPushButton("add Widget")
self.getlistof_button = qtw.QPushButton("deleate")
self.linedittext_button = qtw.QPushButton("linedit text")
self.main_layout = qtw.QGridLayout()
self.main_layout.addWidget(self.getlistof_button,0,0)
self.main_layout.addWidget(self.addwidget_button, 1, 0)
self.main_layout.addWidget(self.linedittext_button, 2, 0)
self.setLayout(self.main_layout)
self.show()
# functionality
self.addwidget_button.clicked.connect(self.add_widget)
self.getlistof_button.clicked.connect(self.deleate_lastlineedit)
self.getlistof_button.clicked.connect(self.deleate_lastqlabel)
self.linedittext_button.clicked.connect(self.count)
def count(self):
x = self.main_layout.rowCount()
print(self.main_layout.rowCount()+1)
print(type(x))
def add_widget(self):
my_lineedit = qtw.QLineEdit()
x1 = (self.main_layout.rowCount()+1)
my_dynmic_label = qtw.QLabel("Dynamic")
self.main_layout.addWidget(my_dynmic_label,x1,0)
self.main_layout.addWidget(my_lineedit,x1,1)
def deleate_lastqlabel(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for index, widget in enumerate(widgets):
if index == (self.main_layout.count()-1) and isinstance(widget, qtw.QLabel):
# print("yes")
widget.deleteLater()
break
def deleate_lastlineedit(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for index, widget in enumerate(widgets):
if index == (self.main_layout.count()-1) and isinstance(widget, qtw.QLineEdit):
widget.deleteLater()
break
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = AddWidget()
sys.exit(app.exec_())
As the name suggests, deleteLater() deletes the object later.
Schedules this object for deletion.
The object will be deleted when control returns to the event loop.
If you add a print(self.main_layout.count()) after each deleteLater call in the cycle, you'll see that the count is still the same, and that's because the control is not yet returned to the event loop.
You should use layout.removeWidget() instead.
Besides that, it will not be enough anyway.
You are cycling through the whole list until you find the last element, but this means that the next-to-last will not be checked. A possible solution would be to do the for cycle twice, but doing it wouldn't be the smartest thing to do.
So, as I already suggested, you should use reversed().
Also, you need some form of control since you're going to remove two widgets, otherwise the cycle will break as soon as it finds the first match for isinstance.
def deleate_lastlineedit(self):
labelRemoved = editRemoved = False
widgets = [self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count())]
for widget in reversed(widgets):
if isinstance(widget, qtw.QLineEdit):
editRemoved = True
elif isinstance(widget, qtw.QLabel):
labelRemoved = True
else:
continue
# in this case, removeWidget is not necessary, since we're not
# checking the count, but I'll leave it anyway for completeness;
self.main_layout.removeWidget(widget)
widget.deleteLater()
if editRemoved and labelRemoved:
break
Since you only need to remove the last widgets, creating a generator for the whole widgets is unnecessary. As long as you always insert only QLabels and QLineEdits at the end of the layout, you can just use a while loop.
def deleate_lastlineedit(self):
labelRemoved = editRemoved = False
while not (labelRemoved and editRemoved):
widget = self.main_layout.itemAt(self.main_layout.count() - 1).widget()
# now here removeWidget *IS* required, otherwise the while loop will
# never exit
self.main_layout.removeWidget(widget)
widget.deleteLater()
if isinstance(widget, qtw.QLineEdit):
editRemoved = True
elif isinstance(widget, qtw.QLabel):
labelRemoved = True
PS: I've already suggested you to better study Python's control flows, please follow my advice: this is your FOURTH question with almost the same issue, and I only answered because I wanted to clarify the deleteLater() problem, but you wouldn't even have needed to ask it, if you'd follow my previous suggestions and answers. Please, do study and practice, you can't expect to code a GUI if you don't even understand the most elementary basics of its language.
I have a QVBox layout that houses a QVBox layout and a QHBox layout. I use the other QVBox layout to hold dynamically created GUI objects and the QHBox layout to hold the buttons that add/remove those objects. Everything works correctly if I position the QHBox on top of the QVBox, but when I try to position the QHBox beneath the QVBox the objects aren't removed correctly but stay there "lingering" on top of the QHBox. I'll upload pictures to demonstrate the problem. First picture is before taking action, second is after creating a new object and third is after deleting the object
Here is the code that creates and deletes the new objects
def addClient(self):
if (len(self.clients) < 5):
client = clientComponent(self)
self.clients.append(client)
index = len(self.clients)-1
self.vLayout3.addWidget(self.clients[index])
client.setIndex(index)
self.clients[index].startButton.clicked.connect(partial(self.threadcontrol, '2', client.getIndex()))
self.clients[index].stopButton.clicked.connect(partial(self.clientstop, '0', client.getIndex()))
def deleteClient(self):
if (len(self.clients) > 1):
self.vLayout3.removeWidget(self.clients.pop())
This is where I complete the layout
def initializeUi(self):
self.mainWidget = QWidget(self)
self.setCentralWidget(self.mainWidget)
self.mainLayout = QVBoxLayout(self.mainWidget)
self.hLayout1 = QHBoxLayout()
self.hLayout2 = QHBoxLayout()
self.vLayout1 = QVBoxLayout()
self.vLayout2 = QVBoxLayout()
self.vLayout3 = QVBoxLayout()
self.addServer()
self.addClient()
self.serverBox = QGroupBox('Server')
self.clientBox = QGroupBox('Client')
self.traffic1 = QLabel('0.0Mb/s', self)
self.traffic1.setAlignment(Qt.AlignRight)
self.traffic2 = QLabel('0.0Mb/s', self)
self.traffic2.setAlignment(Qt.AlignCenter)
self.traffic3 = QLabel('0.0Mb/s', self)
self.traffic3.setAlignment(Qt.AlignLeft)
self.newClientButton = QPushButton('+', self)
self.deleteClientButton = QPushButton('-', self)
self.hLayout1.addWidget(self.traffic1)
self.hLayout1.addWidget(self.traffic2)
self.hLayout1.addWidget(self.traffic3)
self.hLayout2.addWidget(self.newClientButton)
self.hLayout2.addWidget(self.deleteClientButton)
self.vLayout2.addLayout(self.vLayout3)
self.vLayout2.addLayout(self.hLayout2)
self.mainLayout.addWidget(self.plot)
self.mainLayout.addLayout(self.hLayout1)
self.serverBox.setLayout(self.vLayout1)
self.mainLayout.addWidget(self.serverBox)
self.clientBox.setLayout(self.vLayout2)
self.mainLayout.addWidget(self.clientBox)
This is happening because your main window remains the parent of the client widgets after you remove them from the layout. You will see similar behaviour if you assign a widget a parent widget without adding it to any layout.
Removing the parent should resolve the issue.
def deleteClient(self):
if (len(self.clients) > 1):
client = self.clients.pop()
self.vLayout3.removeWidget(client)
client.setParent(None)
You may also need to make a call to adjustSize to resize the window to fit the remaining widgets
When you delete a widget from layout it still remains in parent widget's
object tree, so it gets displayed outside of any layout.
To remove a widget from the object tree call widget.setParent(None).
widget.deleteLater() also works.
Here is my MCVE(Qt4, Py2.7):
from PyQt4.QtGui import (QApplication, QWidget, QPushButton,
QVBoxLayout, QHBoxLayout)
app=QApplication([])
self = QWidget()
main_layout = QVBoxLayout(self)
clients = []
l2 = QHBoxLayout()
main_layout.addLayout(l2)
b_add = QPushButton('add', self)
l2.addWidget(b_add)
def addClient():
b = QPushButton(str(len(clients)), self)
clients.append(b)
main_layout.addWidget(b)
b_add.clicked.connect(addClient)
b_rm = QPushButton('rm', self)
l2.addWidget(b_rm)
def deleteClient():
b = clients.pop()
main_layout.removeWidget(b)
# comment out two following lines to get the behavior you observe
b.setParent(None)
self.adjustSize()
b_rm.clicked.connect(deleteClient)
self.show()
app.exec_()
On my system I also have to call self.adjustSize() after deletion to resize the main window