DoctorDalek1963
DoctorDalek1963

Reputation: 21

How can I stop PyQt5 from deleting widgets when I hide them?

I'm using Python3.9 and PyQt5 and I've got a GUI with lots of widgets. I've got a selection screen of sorts, and when the user clicks on one of the options, I hide all the selection widgets and show all of the widgets for the actual app. That works fine.

However, I also have a reset button, which is supposed to hide all the widgets for the app and show the widgets for the selection, but when I click the reset button, I get RuntimeError: wrapped C/C++ object of type QPushButton has been deleted.

Clearly, my button objects are getting deleted, probably by some garbage collection, so when I try to show them again, I get this error. The app widgets are hidden properly, but then I just have a blank GUI.

import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QHBoxLayout, QWidget

class GUI(QMainWindow):
    def __init__(self):
        super().__init__()

        self.selection_button_1 = QtWidgets.QPushButton('Selection Button 1', self)
        self.selection_button_1.clicked.connect(self.select)
        self.selection_button_2 = QtWidgets.QPushButton('Selection Button 2', self)
        self.selection_button_2.clicked.connect(self.select)

        # When the user clicks either selection button, some stuff
        # is configured and self.show_app() is called

        self.app_button_1 = QtWidgets.QPushButton('App Button 1', self)
        self.app_button_2 = QtWidgets.QPushButton('App Button 2', self)

        self.reset_button = QtWidgets.QPushButton('Reset', self)
        self.reset_button.clicked.connect(self.reset)

        self.selection_hbox = QHBoxLayout()
        self.app_hbox = QHBoxLayout()

        self.selection_central_widget = QWidget()
        self.app_central_widget = QWidget()

        self.arrange_widgets()
        self.show_selection()

    def arrange_widgets(self) -> None:
        self.selection_hbox.addWidget(self.selection_button_1)
        self.selection_hbox.addWidget(self.selection_button_2)
        self.selection_central_widget.setLayout(self.selection_hbox)

        self.app_hbox.addWidget(self.app_button_1)
        self.app_hbox.addWidget(self.app_button_2)
        self.app_hbox.addWidget(self.reset_button)
        self.app_central_widget.setLayout(self.app_hbox)

    def show_selection(self) -> None:
        self.app_button_1.hide()
        self.app_button_2.hide()

        self.selection_button_1.show()
        self.selection_button_2.show()

        self.setCentralWidget(self.selection_central_widget)

    def show_app(self) -> None:
        self.selection_button_1.hide()
        self.selection_button_2.hide()

        self.app_button_1.show()
        self.app_button_2.show()

        self.setCentralWidget(self.app_central_widget)

    def select(self) -> None:
        # Configure some other stuff

        self.show_app()

    def reset(self) -> None:
        # Reset some other stuff

        self.show_selection()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = GUI()
    window.show()
    sys.exit(app.exec_())

Upvotes: 0

Views: 393

Answers (1)

eyllanesc
eyllanesc

Reputation: 244282

Explanation:

Many methods that replace QObjects have the policy that if the object to be replaced has the container as parent then it will be deleted, something like:

def replace_foo(self, new_foo_object):
    if self.foo_object.parent() is self:
        self.foo_object.deleteLater()
    self.foo_object = new_foo_object

And that's what happens with setCentralWidget().

Solution

A possible solution is that before replacing the widget is to set the parent of the current centralWidget to be None:

def show_selection(self) -> None:
    self.app_button_1.hide()
    self.app_button_2.hide()

    self.selection_button_1.show()
    self.selection_button_2.show()

    self.app_central_widget.setParent(None)
    self.setCentralWidget(self.selection_central_widget)

def show_app(self) -> None:
    self.selection_button_1.hide()
    self.selection_button_2.hide()

    self.app_button_1.show()
    self.app_button_2.show()

    self.selection_central_widget.setParent(None)
    self.setCentralWidget(self.app_central_widget)

But a better solution that avoids having a lot of code that hides or shows widgets is to use a QStackedWidget:

class GUI(QMainWindow):
    def __init__(self):
        super().__init__()

        self.stacked_widget = QStackedWidget()
        self.setCentralWidget(self.stacked_widget)

        self.widget_selection = self.create_selection()
        self.widget_app = self.create_app()

    def create_selection(self):
        self.selection_button_1 = QtWidgets.QPushButton("Selection Button 1")
        self.selection_button_1.clicked.connect(self.select)
        self.selection_button_2 = QtWidgets.QPushButton("Selection Button 2")
        self.selection_button_2.clicked.connect(self.select)
        container = QWidget()
        lay = QHBoxLayout(container)
        lay.addWidget(self.selection_button_1)
        lay.addWidget(self.selection_button_2)

        self.stacked_widget.addWidget(container)
        return container

    def create_app(self):
        self.app_button_1 = QtWidgets.QPushButton("App Button 1")
        self.app_button_2 = QtWidgets.QPushButton("App Button 2")
        self.reset_button = QtWidgets.QPushButton("Reset")
        self.reset_button.clicked.connect(self.reset)
        container = QWidget()
        lay = QHBoxLayout(container)
        lay.addWidget(self.app_button_1)
        lay.addWidget(self.app_button_2)
        lay.addWidget(self.reset_button)

        self.stacked_widget.addWidget(container)
        return container

    def show_selection(self) -> None:
        self.stacked_widget.setCurrentWidget(self.widget_selection)

    def show_app(self) -> None:
        self.stacked_widget.setCurrentWidget(self.widget_app)

    def select(self) -> None:
        # Configure some other stuff
        self.show_app()

    def reset(self) -> None:
        # Reset some other stuff
        self.show_selection()

Upvotes: 1

Related Questions