1

Suppose we have two windows, one with a button, the parent, and one empty, the child. Codes for parent and child are:

test_parent.py:

from PyQt5 import QtCore, QtGui, QtWidgets
from test_child import Ui_child

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(270, 208)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(90, 90, 89, 25))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        self.pushButton.clicked.connect(self.open_child)        

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

    def open_child(self):
        self.child = QtWidgets.QMainWindow()
        self.ui = Ui_child()
        self.ui.setupUi(self.child)
        self.child.show()
        MainWindow.hide()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

and test_child.py:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_child(object):
    def setupUi(self, child):
        child.setObjectName("child")
        child.resize(275, 176)
        self.centralwidget = QtWidgets.QWidget(child)
        self.centralwidget.setObjectName("centralwidget")
        child.setCentralWidget(self.centralwidget)

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

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

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_child()
    ui.setupUi(child)
    child.show()
    sys.exit(app.exec_())

With this code, when the user clicks the button, the parent is hiden and the child is shown. Now I want to show again the parent when the users closes the child. How can I do this?

Learning from masters
  • 2,032
  • 3
  • 29
  • 42

2 Answers2

1

A good solution is to create a custom signal for the child, and emit it when the window is closed.

Note that in order to do that, you have to correctly use pyuic generated files, which is not what you're doing: those files should never, ever be manually modified, as they are only meant for imports and are not intended for further implementation: there are lots of reasons for not doing that, and one of them is that their structure might lead to confusion about the object structure. You can read more about the correct usage of those files in the official PyQt guidelines about using Designer.

Most importantly, since those are simple python object classes (and not Qt widget classes), they don't allow method overriding, which is very important especially in cases like this one.

So, before going on with the example, rebuild those files with pyuic and create a new script for your program.

The concept is that the child class will have a closed signal, and the signal will be emitted within the closeEvent (which is called everytime the window is closed by the user or programmatically with close()); then main window creates the child (but doesn't show it) right in the __init__, and connects its custom signal with that window's show.

from PyQt5 import QtCore, QtGui, QtWidgets
from test_parent import Ui_MainWindow
from test_child import Ui_child

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.pushButton.clicked.connect(self.openChild)

        # create an instance of the child
        self.child = Child()
        # connect its closed signal to show() of this window
        self.child.closed.connect(self.show)

    def openChild(self):
        self.child.show()
        self.hide()


class Child(QtWidgets.QMainWindow, Ui_child):
    closed = QtCore.pyqtSignal()
    def __init__(self):
        super().__init__()
        self.setupUi(self)

    def closeEvent(self, event):
        self.closed.emit()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • I think I should do everything on qt designer, but i was just seeing on the net that everyone was doing pyuic and then modifying stuff, which I neither think it is appropriate. Nonetheless, it is not clear to me what do you mean by "rebuild those files with pyuic". You mean call pyuic for each one, create the py file from the ui (as i was doing) and then copying what I needed to create the script you showed me? Isn't that the opposite we are saying to do stuff on qt designer? – Learning from masters Jan 29 '21 at 14:48
  • 1
    "everyone was doing pyuic and then modifying stuff" luckily, not everyone, and whoever does it is almost always wrong in doing it (even worse, to suggest it): it's considered *bad* practice for a multitude of reasons and there are only very few cases for which (very limited) modification is actually required. What I meant is: get rid of those 2 files by rebuilding them with pyuic using your original ui files (in my example it's assumed that they will be named `test_parent.py` and `test_child.py`) then create a new file with the above code just as it is, that will be your actual program script. – musicamante Jan 29 '21 at 18:35
  • @musicamante in your example what does self.child.parentWidget() would return ? – pippo1980 Mar 09 '22 at 17:57
  • @pippo1980 it will return `None`, as the child was created without any parent (the `__init__` has no argument), nor `setParent()` was called on it. – musicamante Mar 09 '22 at 18:02
  • cool, being back to PyQt5, struggling with parent/child ! Is it the same of modal/non modal fromQt Designer or something else ? – pippo1980 Mar 09 '22 at 18:04
  • @musicamante is this https://stackoverflow.com/questions/37918012/pyqt-give-parent-when-creating-a-widget valid for PyQt5 too ? – pippo1980 Mar 09 '22 at 18:05
  • @pippo1980 Can you explain what you mean by "Is it the same of modal/non modal fromQt Designer or something else"? Regarding your other comment, yes, that didn't change in PyQt5, nor in PyQt6. – musicamante Mar 09 '22 at 18:08
  • thats why I am asking, used only Qt designer, didnt have a look inside ui and py files generated, only selected modal - non modal in Qt designer in my experiments. Does it plaays with widgets init ? making them having a parent ? – pippo1980 Mar 09 '22 at 18:15
  • @musicamante, Hi your answer doesnt work unless I remove from test_parent.py both self.pushButton.clicked.connect(self.openChild) and the entire def openChild – pippo1980 Mar 09 '22 at 18:56
  • @pippo1980 The fact that a widget is modal or not doesn't change the parent. but the parent can change the behavior of the modality: *if* it's `WindowModal` *and* there is a valid parent. I don't know what you did in your code, so I cannot answer on that, you probably didn't add the button to the UI or it has a different name; also, if you removed the `clicked` connection, removing the `openChild` shouldn't matter. – musicamante Mar 09 '22 at 18:59
-1

from PyQt5 import QtCore, QtGui, QtWidgets
from test_parent import Ui_MainWindow

class Ui_child(object):
    def setupUi(self, child):
        child.setObjectName("child")
        child.resize(275, 176)
        self.centralwidget = QtWidgets.QWidget(child)
        self.centralwidget.setObjectName("centralwidget")
        child.setCentralWidget(self.centralwidget)

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

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

    def closeEvent(self, event):
        self.parent = Ui_MainWindow()
        self.parent.setupUi(QtWidgets.QMainWindow())
        self.parent.show()
        self.hide()
        
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_child()
    ui.setupUi(child)
    child.show()
    sys.exit(app.exec_())
  • I cannot run now the parent file. It says "from test_child import Ui_child ImportError: cannot import name 'Ui_child'" – Learning from masters Jan 28 '21 at 17:24
  • This is *not* a valid answer: not only the `from test_parent import Ui_MainWindow` line creates a circular import (hence the import error noted by @Learningfrommasters), but it will also **not** work anyway: `closeEvent()` will never be called, since the class doesn't inherit from QWidget. Please always check your code before posting an answer. – musicamante Jan 29 '21 at 04:09