How to remove widget from the layout in PySide

I’d like to add close button to the single widget and remove the widget from the parent layout when the close button pressed.

GOAL

To remove single widget with close button as below.

Environment

Windows 10
Python 3.8.7
PySide2 5.15.2

Method

1. Create a widget with close button and set it in the layout

I create ItemWidget with close button and put the item and add button on the main window. Check “How To Insert Widget To The Layout Counting From The End” for detail about the implement of add button.

import sys
from PySide2.QtWidgets import *

class ItemWidget(QWidget):
    def __init__(self, id_str="", parent=None):
        super(ItemWidget, self).__init__(parent)
        self.id_str = id_str
        self._generateUI()
    def _generateUI(self):
        main_layout = QGridLayout()
        self.setLayout(main_layout)
        title = QLabel("title" + self.id_str)
        main_layout.addWidget(title, 0, 0, 1, 3)
        close_button = QPushButton("-")
        close_button.setFixedWidth(30)
        main_layout.addWidget(close_button, 0, 3, 1, 1)
        spinbox = QSpinBox()
        main_layout.addWidget(spinbox, 1, 0, 1, 4)

class MyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.id_num = 1
        self._generateUI()
    def _generateUI(self):
        main_widget = QWidget()
        self.main_layout = QVBoxLayout()
        main_widget.setLayout(self.main_layout)
        self.setCentralWidget(main_widget)
        item = ItemWidget(str(self.id_num))
        self.main_layout.addWidget(item)
        add_button = QPushButton("+")
        add_button.clicked.connect(self._addItem)
        self.main_layout.addWidget(add_button)
    def _addItem(self):
        self.id_num += 1
        item = ItemWidget(str(self.id_num))
        self.main_layout.insertWidget(self.main_layout.count()-1, item)

def launch():
    app = QApplication.instance()
    if not app:
        app = QApplication(sys.argv)
    widget = MyMainWindow()
    widget.show()
    app.exec_()

launch()

2. Connect close function to the signal “clicked” of the close button

Use deleteLater() to delete widget.

class ItemWidget(QWidget):
    def __init__(self, id_str="", parent=None):
        super(ItemWidget, self).__init__(parent)
        self.id_str = id_str
        self._generateUI()
    def _generateUI(self):
        main_layout = QGridLayout()
        self.setLayout(main_layout)
        title = QLabel("title" + self.id_str)
        main_layout.addWidget(title, 0, 0, 1, 3)
        close_button = QPushButton("-")
        close_button.setFixedWidth(30)
        close_button.clicked.connect(self._close_widget) # add to close the widget
        main_layout.addWidget(close_button, 0, 3, 1, 1)
        spinbox = QSpinBox()
        main_layout.addWidget(spinbox, 1, 0, 1, 4)
    def _close_widget(self):
        self.deleteLater() # main function to close widget

* There are many ways to remove widget from the parent layout. For example, some people use setParent(None) or something. However don’t use removeWidget() because it just removes widget without destroying it. Please refer to the discussion below.

3. Resize the parent widget when the child removed

The function deleteLater() just delete the widget without changing the size. So I changed size manually with adjustSize() as below.
Related article: How to detect the child widget destroy?

class MyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.id_num = 1
        self._generateUI()
    def _generateUI(self):
        main_widget = QWidget()
        self.main_layout = QVBoxLayout()
        main_widget.setLayout(self.main_layout)
        self.setCentralWidget(main_widget)
        item = ItemWidget(str(self.id_num))
        self.main_layout.addWidget(item)
        add_button = QPushButton("+")
        add_button.clicked.connect(self._addItem)
        self.main_layout.addWidget(add_button)
    def _addItem(self):
        self.id_num += 1
        item = ItemWidget(str(self.id_num))
        self.main_layout.insertWidget(self.main_layout.count()-1, item)
    def _deletedItem(self, item):
        self.id_num -= 1
        for i in range(3):
            QApplication.processEvents()
        self.adjustSize()

I don’t know why but adjustSize() doesn’t work correctly until some events are processed in the event loop. So I called QApplication.processEvents() in the for loop. Please let me know if you have any idea about this

Reference: PySide: What is the best way to resize the main window if one widget is hidden? in stackoverflow.com