Nimish Bansal
Nimish Bansal

Reputation: 1759

wrapped c/c++ object of type QWidget has been deleted

class window25(QtWidgets.QMainWindow):
    def __init__(self):
        try:
            super(window25,self).__init__()
            self.lineedit=QtWidgets.QLineEdit()
            self.checkbox=QtWidgets.QCheckBox()
            self.optBox=QtWidgets.QRadioButton()
            self.btn=QtWidgets.QPushButton("press me")
            self.guichange()
            self.btn.clicked.connect(self.guichange)
            self.show()
        except Exception as E:
            print(E)
    def guichange(self):
        try:
            wid=QtWidgets.QWidget()
    #        self.setCentralWidget()
            myLayout=QtWidgets.QVBoxLayout()
            myLayout.addWidget(self.btn)
            myLayout.addWidget(random.choice([self.lineedit,self.checkbox,self.optBox]))
            wid.setLayout(myLayout)    
            self.setCentralWidget(wid)
        except Exception as E:
            print(E)


app=QtWidgets.QApplication([])
ex=window25()
sys.exit(app.exec_())

I was trying to do the experiment.Although I know about QStackedWidget to change windows but I tried something else that is pressing a button randomly changes the mainWindow Central widget.After a couple of success(i.e. pressing button 3,4 times work correctly") but after that I got an error

wrapped c/c++ object of type QCheckBox has been deleted

wrapped c/c++ object of type QLineEdit has been deleted

i am unable to understand which statement caused this error and why or where I am wrong

Upvotes: 3

Views: 6539

Answers (1)

eyllanesc
eyllanesc

Reputation: 244202

This is caused by the implementation and philosophy of Qt, reviewing the documentation observed:

[...]

QObjects organize themselves in object trees. When you create a QObject with another object as parent, the object will automatically add itself to the parent's children() list. The parent takes ownership of the object; i.e., it will automatically delete its children in its destructor. You can look for an object by name and optionally type using findChild() or findChildren().

[...]

As he says if the parent destroys the children too, and since all widgets have QObject as their predecessor, they also comply with those rules.

But why are some widget destroyed?

Each time you use setCentralWidget() the object that was previously the central widget is deleted.

What are the children of the CentralWidget()?

when the widgets are attached to a layout, these widgets are set as the parent to the widget that uses that layout.

wid = QtWidgets.QWidget()   
myLayout.addWidget(tmp)    
wid.setLayout(myLayout)    
self.setCentralWidget(wid) 

Why is not QPushButton eliminated?

What happens with QPushButton and sometimes with the other widgets is that before using setCentralWidget(), that is deleting the parent, it is changed from the parent:

myLayout=QtWidgets.QVBoxLayout() # some parent
myLayout.addWidget(self.btn)     # a new parent is established
[...]
self.setCentralWidget(wid)       # elimination of the old parent

Finally I will explain a random example using the following code:

class window25(QtWidgets.QMainWindow):
    counter = 0
    def __init__(self):
        try:
            super(window25,self).__init__()
            self.lineedit=QtWidgets.QLineEdit()
            self.lineedit.setObjectName("QLineEdit")
            self.checkbox=QtWidgets.QCheckBox()
            self.checkbox.setObjectName("QCheckBox")
            self.optBox=QtWidgets.QRadioButton()
            self.optBox.setObjectName("QRadioButton")
            self.btn=QtWidgets.QPushButton("press me")
            self.guichange()
            self.btn.clicked.connect(self.guichange)
            self.show()
        except Exception as E:
            print(E)
    def guichange(self):
        try:
            print("\ncall guichange")
            wid = QtWidgets.QWidget()
            wid.setObjectName("wid-{}".format(self.counter))
            myLayout=QtWidgets.QVBoxLayout()
            myLayout.addWidget(self.btn)
            tmp = random.choice([self.lineedit,self.checkbox,self.optBox])
            myLayout.addWidget(tmp)
            wid.setLayout(myLayout)    
            wid.destroyed.connect(lambda obj: print("delete: ",obj.objectName()))
            tmp.destroyed.connect(lambda obj: print("delete: ",obj.objectName()))
            self.setCentralWidget(wid)
            print("add: {} parent: {}".format(tmp.objectName(),tmp.parent().objectName()))
            self.counter += 1

        except Exception as E:
            print(E)

Explanation:

call guichange
QLineEdit                      # tmp is QLineEdit
add: QLineEdit parent: wid-0   # set to wid-0 as the parent of QLineEdit

call guichange
QLineEdit                      # tmp is QLineEdit
add: QLineEdit parent: wid-1   # set to wid-1 as new parent of QLineEdit
delete:  wid-0                 # delete old parent

call guichange
QRadioButton                     # tmp is QRadioButton
add: QRadioButton parent: wid-2  # set to wid-2 as the parent of QRadioButton
delete:  wid-1                   # delete old parent
delete:  QLineEdit               # message printed by the lambda function 
delete:  QLineEdit               # indicating that QLineEdit has been deleted

call guichange
wrapped C/C++ object of type QLineEdit has been deleted # you want to use QLineEdit 
                                                        # but it had been removed
                                                        # previously causing 
                                                        # that error message

If you want to exchange widgets the appropriate option is QStackedWidget as I show below:

class window25(QtWidgets.QMainWindow):
    def __init__(self):
        super(window25, self).__init__()
        self.lineedit = QtWidgets.QLineEdit()
        self.checkbox = QtWidgets.QCheckBox()
        self.optBox = QtWidgets.QRadioButton()
        self.btn = QtWidgets.QPushButton("press me")
        wid = QtWidgets.QWidget()
        myLayout=QtWidgets.QVBoxLayout()
        self.stacked = QtWidgets.QStackedWidget()
        self.stacked.addWidget(self.lineedit)
        self.stacked.addWidget(self.checkbox)
        self.stacked.addWidget(self.optBox)
        wid.setLayout(myLayout) 
        myLayout.addWidget(self.btn)
        myLayout.addWidget(self.stacked)
        self.setCentralWidget(wid)
        self.guichange()
        self.btn.clicked.connect(self.guichange)

    def guichange(self):
        widget = random.choice([self.lineedit,self.checkbox,self.optBox])
        self.stacked.setCurrentWidget(widget)

if __name__ == '__main__':
    app=QtWidgets.QApplication(sys.argv)
    ex=window25()
    ex.show()
    sys.exit(app.exec_())

Upvotes: 6

Related Questions