currently I am working on a python application which uses a PyQt5 GUI and ran into some trouble with the GUI. I have created a main window, which contains a button. When I click this button a second window opens in which the user inputs login data. After accepting the data by hitting another button the window changes its apperance to wait make the user verify his login. After doing so and confirming by clicking a third button the window changes one last time. Now the user is asked to specify a time period and confirming it by once again clicking a button. Afterwards the child window is supposed to close. And the application should return to the main window.
However when I close the child window after the last input the whole application closes. Which should not happen. On the other hand if I close the window in one of the prevoius steps it behaves the way I want it to. The child window is closed and the application keeps running. Therefore I expect the error to be somewhere in the last configuration of the child window, but I can't figure out where. The child window is build in a way that it is always the same window and only the widgets displayed change.
You can find a minimum working example down below.
Thanks for your help.
Main window
import sys
from random import randint
from PyQt5.QtWidgets import (
QVBoxLayout, QPushButton, QLabel, QMainWindow, QApplication, QWidget)
from Banking_App_Login_form import LoginForm
from test_period_form import PeriodForm
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.w = None # No external window yet.
self.button = QPushButton("Push for Window")
self.button.clicked.connect(self.show_new_window)
self.setCentralWidget(self.button)
def show_new_window(self):
if self.w is None:
self.w = LoginForm()
self.w.login_btn.clicked.connect(self.get_tan)
self.w.closed.connect(self.quit)
self.w.show()
else:
self.w.close() # Close window.
self.w = None # Discard reference.
self.show_new_window()
def get_tan(self):
self.w.change_form_tan()
self.w.ok_btn.clicked.connect(self.get_period)
# self.w.ok_btn.clicked.connect(self.close)
def get_period(self):
self.w.change_form_period()
self.w.ok_btn_2.clicked.connect(self.close)
def quit(self):
"""
Function for tasks to be performed after the child window is closed.
"""
print("Token revoked")
self.w = None
print(f"self.w is now {self.w}")
def close(self):
"""
Function to close the child window.
"""
self.w.close()
def main():
app = QApplication(sys.argv)
gui = MainWindow()
gui.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
Child window
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QFont, QCloseEvent
from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit, QLabel,
QApplication, QComboBox, QGridLayout, QSpacerItem,
QSizePolicy)
import sys
class LoginForm(QWidget):
"""
login form for banking application
...
Attributes
----------
None
Methods
-------
_create_layouts
_create_fonts
_create_tbs
_create_tbns
"""
# creates new signal to be emitted when window is closed
# signal must be instanciated before __init__
closed = pyqtSignal()
def __init__(self) -> None:
"""
Init Method of the Login form
...
Parameters
----------
None
Returns
-------
None
"""
super().__init__()
self._cursor_status = "standard"
self.setWindowTitle("LogIn")
self.resize(300, 120)
self.setMinimumSize(150, 100)
self._create_layouts()
self._create_fonts()
self._create_tbs()
self._create_btns()
self._create_lbls()
self._create_cmbs()
self.setLayout(self._main_layout)
self._main_layout.addWidget(self._lbl_user, 0, 0)
self._main_layout.addWidget(self._lbl_pwd, 1, 0)
self._main_layout.addWidget(self.tb_user, 0, 1)
self._main_layout.addWidget(self.tb_pwd, 1, 1)
self._main_layout.addWidget(self.login_btn, 2, 1)
def _create_layouts(self) -> None:
"""
Create Main layout for login form
...
Parameters
----------
Nonen
Return
------
None
"""
self._main_layout = QGridLayout()
def _create_fonts(self) -> None:
"""
Create the fonts used in the Login form
...
Parameters
----------
None
Returns
-------
None
"""
self._font = QFont()
self._font.setFamily("Calibri")
self._font.setPointSize(12)
def _create_lbls(self) -> None:
"""
Create label
...
Parameters
----------
None
Return
------
None
"""
self._lbl_user = QLabel("Benutzername")
self._lbl_user.setFixedSize(100, 25)
self._lbl_user.setFont(self._font)
self._lbl_pwd = QLabel("Passwort")
self._lbl_pwd.setFixedSize(100, 25)
self._lbl_pwd.setFont(self._font)
self._lbl_tan = QLabel("TAN bestätigen und Ok drücken")
self._lbl_tan.setFixedSize(220, 25)
self._lbl_tan.setFont(self._font)
self._lbl_zeitraum = QLabel("Zeitraum eingeben:")
self._lbl_zeitraum.setFixedSize(220, 25)
self._lbl_zeitraum.setFont(self._font)
self._lbl_start = QLabel("Startdatum")
self._lbl_start.setFixedSize(100, 25)
self._lbl_start.setFont(self._font)
self._lbl_ende = QLabel("Endatum")
self._lbl_ende.setFixedSize(110, 25)
self._lbl_ende.setFont(self._font)
def _create_cmbs(self) -> None:
self.cmb_von_monat = QComboBox()
self.cmb_von_monat.setFixedSize(70, 25)
self.cmb_von_monat.setFont(self._font)
self.cmb_von_jahr = QComboBox()
self.cmb_von_jahr.setFixedSize(70, 25)
self.cmb_von_jahr.setFont(self._font)
self.cmb_bis_monat = QComboBox()
self.cmb_bis_monat.setFixedSize(70, 25)
self.cmb_bis_monat.setFont(self._font)
self.cmb_bis_jahr = QComboBox()
self.cmb_bis_jahr.setFixedSize(70, 25)
self.cmb_bis_jahr.setFont(self._font)
def _create_tbs(self) -> None:
"""
Create textboxes
...
Parameters
----------
None
Returns
-------
None
"""
self.tb_user = QLineEdit()
self.tb_user.setFixedSize(175, 25)
self.tb_user.setFont(self._font)
self.tb_pwd = QLineEdit()
self.tb_pwd.setFixedSize(175, 25)
self.tb_pwd.setFont(self._font)
self.tb_pwd.setEchoMode(QLineEdit.EchoMode.Password)
def _create_btns(self) -> None:
"""
create buttons
...
Parameters
----------
None
Returns
-------
None
"""
self.login_btn = QPushButton("Login")
self.login_btn.setFont(self._font)
self.login_btn.setFixedSize(175, 25)
self.ok_btn = QPushButton("Ok")
self.ok_btn.setFont(self._font)
self.ok_btn.setFixedSize(100, 25)
self.ok_btn_2 = QPushButton("Ok")
self.ok_btn_2.setFont(self._font)
self.ok_btn_2.setFixedSize(100, 25)
def _create_spacer(self) -> None:
"""
create Spacers
...
Parameters
----------
None
Returns
-------
None
"""
self._fixed_spacer = QSpacerItem(
10, 20, QSizePolicy.Fixed, QSizePolicy.Minimum)
def change_form_tan(self) -> None:
"""
Change form apperance for TAN verification.
...
Parameters
----------
None
Returns
-------
None
"""
self.setWindowTitle("TAN bestätigen")
self.resize(250, 100)
# deletes widget from Layout but references inside the layout are
# still kept and the widget is shown
self._main_layout.removeWidget(self._lbl_user)
# deletes all references to the widget and allows python to purge it
self._lbl_user.deleteLater()
self._main_layout.removeWidget(self._lbl_pwd)
self._lbl_pwd.deleteLater()
self._main_layout.removeWidget(self.tb_user)
self.tb_user.deleteLater()
self._main_layout.removeWidget(self.tb_pwd)
self.tb_pwd.deleteLater()
self._main_layout.removeWidget(self.login_btn)
self.login_btn.deleteLater()
self._main_layout.addWidget(self._lbl_tan, 0, 0)
self._main_layout.addWidget(self.ok_btn, 1, 0)
def change_form_period(self) -> None:
"""
Change form apperance for period selection.
...
Parameters
----------
None
Returns
-------
None
"""
self.setWindowTitle("Zeitraum auswählen")
self.resize(300, 180)
self.setMinimumSize(300, 180)
self._create_spacer()
self._main_layout.removeWidget(self._lbl_tan)
self._lbl_tan.deleteLater()
self._main_layout.removeWidget(self.ok_btn)
self.ok_btn.deleteLater()
self._main_layout.addWidget(self._lbl_zeitraum, 0, 0)
self._main_layout.addItem(self._fixed_spacer, 1, 1)
self._main_layout.addWidget(self._lbl_start, 2, 0)
self._main_layout.addItem(self._fixed_spacer, 3, 1)
self._main_layout.addWidget(self._lbl_ende, 4, 0)
self._main_layout.addWidget(self.cmb_von_monat, 2, 1)
self._main_layout.addWidget(self.cmb_von_jahr, 2, 2)
self._main_layout.addWidget(self.cmb_bis_monat, 4, 1)
self._main_layout.addWidget(self.cmb_bis_jahr, 4, 2)
self._main_layout.addItem(self._fixed_spacer, 5, 1)
self._main_layout.addWidget(self.ok_btn_2, 6, 1)
self.cmb_von_monat.addItems(
["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"])
self.cmb_bis_monat.addItems(
["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"])
self.cmb_von_jahr.addItems([str(2021+i) for i in range(50)])
self.cmb_bis_jahr.addItems([str(2021+i) for i in range(50)])
def change_cursor(self) -> None:
"""
Change cursor to wait cursor
...
Parameters
----------
None
Return
------
None
"""
if self._cursor_status == "standard":
self._cursor_status = "wait"
QApplication.setOverrideCursor(Qt.WaitCursor)
else:
self._cursor_status = "standard"
QApplication.restoreOverrideCursor()
def closeEvent(self, event: QCloseEvent) -> None:
self.closed.emit()
super().closeEvent(event)
Not a real answer, but also more than a comment...
I tried to reproduce this by simplifying it even more, but the error does not persist here, i.e. the main window stays open when the child is closed:
from PyQt5 import QtWidgets, QtCore
class LoginForm(QtWidgets.QWidget):
closed = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self._main_layout = QtWidgets.QGridLayout()
self.setLayout(self._main_layout)
self.ok_btn_2 = QtWidgets.QPushButton("Ok")
self._main_layout.addWidget(self.ok_btn_2, 1, 1)
def closeEvent(self, event):
self.closed.emit()
super().closeEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.w = None # No external window yet.
self.button = QtWidgets.QPushButton("Push for Window")
self.button.clicked.connect(self.show_new_window)
self.setCentralWidget(self.button)
def show_new_window(self):
if self.w is None:
self.w = LoginForm()
self.w.ok_btn_2.clicked.connect(self.close)
self.w.closed.connect(self.quit)
self.w.show()
else:
self.w.close() # Close window.
self.w = None # Discard reference.
self.show_new_window()
def quit(self):
"""
Function for tasks to be performed after the child window is closed.
"""
print("Token revoked")
self.w = None
print(f"self.w is now {self.w}")
def close(self):
"""
Function to close the child window.
"""
self.w.close()
def main():
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
gui = MainWindow()
gui.show()
app.exec()
main() # I was running this in IPython, so I called it directly
Maybe you can figure out where the difference to your code is?
Some other ideas:
close
method is already part of QMainWindow
, which you override here. Why not connect directly to self.w.close
instead the forwarding via self.close
?QDialog
be an option for the LoginForm?