Search code examples
pythonpyside6

Deleting a parent layout in on_change function (SIGSEGV Error)


I have a QComboBox, cbo_box, nested in a frame, self.ui.frameFilterControls, with: cbo_box.currentTextChanged.connect(self.choice_changed). In self.choice_changed(), I call a function which clears the frames layout, including cbo_box, and then regenerates the layout with appropriate controls (including a new cbo_box):

layout = self.ui.frameFilterControls.layout()
if layout is not None:
    #clear layout
    for i in reversed(range(layout.count())):
        layout.itemAt(i).widget().setParent(None)

The problem I am having is that when self.choice_changed() returns, it throws a SIGSEGV error:

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

I am guessing because it has no-where to return to, but I am not sure. I am wondering if it is possible to instead of calling the function that deletes and recreates the controls in the on_change of cbo_box if I could schedule it to be evaluated at the next form update, or something along those lines?


EDIT:

I have produced a minimal example which reproduces the behavior.

from PySide6 import QtCore, QtWidgets
from PySide6.QtWidgets import (QApplication, QListWidgetItem, QGridLayout, QVBoxLayout, QLineEdit, QComboBox)
import sys

#from output of designer -> simple main form with a frame for a starting point
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(381, 357)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.frame = QtWidgets.QFrame(self.centralwidget)
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.pushButton = QtWidgets.QPushButton(self.frame)
        self.pushButton.setObjectName("pushButton")
        self.verticalLayout_2.addWidget(self.pushButton)
        self.verticalLayout.addWidget(self.frame)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 381, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))


class MainGUI(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainGUI, self).__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.set_frame_controls()

    def set_frame_controls(self):
        layout = self.ui.frame.layout()
        if layout is not None:
            #clear layout
            for i in reversed(range(layout.count())):
                layout.itemAt(i).widget().setParent(None)

        cbo_box = QComboBox()
        cbo_box.addItem("test 1")
        cbo_box.addItem("test 2")
        cbo_box.currentTextChanged.connect(self.choice_changed)
        layout.addWidget(cbo_box)

    def choice_changed(self):
        print("choice_changed")
        cbo_box = self.sender()
        self.set_frame_controls()
        print("end choice changed")

def main():
    app = QApplication(sys.argv)
    form = MainGUI()
    form.show()
    app.exit(app.exec_())

if __name__ == '__main__':
    main()

The console output of the above program when I run it and switch the combo box from "test 1" to "test 2" is as follows:

choice_changed
end choice changed

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

The behavior I would like is for the frame to be regenerated entirely, without the error. Thanks!


Solution

  • If you want to remove a widget from a layout it is not enough to set a null parent since this does not remove it from the internal list that the layout has but only removes the object causing the SIGSEGV. Instead you must use deleteLater() which if it ensures the correct elimination of the elements (for example the internal list of the layout).

    def set_frame_controls(self):
        layout = self.ui.frame.layout()
        if layout is not None:
            for i in reversed(range(layout.count())):
                widget = layout.itemAt(i).widget()
                if widget is not None:
                    widget.deleteLater()
    
        cbo_box = QComboBox()
        cbo_box.addItem("test 1")
        cbo_box.addItem("test 2")
        cbo_box.currentTextChanged.connect(self.choice_changed)
        layout.addWidget(cbo_box)