Search code examples
pythonuser-interfacepyqt5qt-designer

Creating a back button using PyQt5 in Python


1) I'm trying to create an app using PyQt5. When a login is succesfull I get to the select page created using the class Ui_select. This works fine. However when I try to logout using the back arrow for some reason the page crashes even though if I run the gui_select.py file first as if it's the main file the back arrow works without issue.

2) I created the Windows using Qt Designer but I'm trying to edit them to work without using the check if the file is main==> if name == "main": above app = QtWidgets.QApplication(sys.argv) since I only want the code creating the login window to be the main file.

*This is the code for the login page:

from PyQt5 import QtCore, QtGui, QtWidgets
import gui_select
import time, getpass

number = 2

users = {'m': 1} #Test the dictionary tommorow to see if it will work accordingly

class Ui_MainWindow(object):

    def openWindow(self, user_id, password):
        x = users.get(user_id)
        if x == int(password):  # Figure out how to make the dictionary work
            MainWindow.close()
            self.window = QtWidgets.QMainWindow()
            self.ui = gui_select.Ui_select()
            self.ui.setupUi(self.window)
            self.window.show()
        else:
            self.login.setText("Wrong Password, Please Retry")
            time.sleep(1)
            self.login.setText("Login")

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1099, 775)
        MainWindow.setStyleSheet("background-color: rgb(4, 112, 54)\n"
"")
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(490, 10, 111, 41))
        font = QtGui.QFont()
        font.setFamily("Broadway")
        font.setPointSize(35)
        self.label_2.setFont(font)
        self.label_2.setStyleSheet("color: rgb(255, 255, 255)")
        self.label_2.setObjectName("label_2")
        self.groupBox_login_info = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox_login_info.setGeometry(QtCore.QRect(390, 517, 274, 171))
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(14)
        self.groupBox_login_info.setFont(font)
        self.groupBox_login_info.setStyleSheet("color: rgb(255, 255, 255)")
        self.groupBox_login_info.setObjectName("groupBox_login_info")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox_login_info)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.lineEdit_password = QtWidgets.QLineEdit(self.groupBox_login_info)
        self.lineEdit_password.setStyleSheet("color: rgb(255, 255, 255)")
        self.lineEdit_password.setEchoMode(QtWidgets.QLineEdit.Password)
        self.lineEdit_password.setObjectName("lineEdit_password")
        self.gridLayout.addWidget(self.lineEdit_password, 1, 1, 1, 1)
        self.label_3 = QtWidgets.QLabel(self.groupBox_login_info)
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(18)
        font.setBold(True)
        font.setWeight(75)
        self.label_3.setFont(font)
        self.label_3.setStyleSheet("color: rgb(255, 255, 255)")
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
        self.label = QtWidgets.QLabel(self.groupBox_login_info)
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(18)
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setStyleSheet("color: rgb(255, 255, 255)")
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
        self.lineEdit_user_id = QtWidgets.QLineEdit(self.groupBox_login_info)
        self.lineEdit_user_id.setStyleSheet("color: rgb(255, 255, 255)")
        self.lineEdit_user_id.setObjectName("lineEdit_user_id")
        self.gridLayout.addWidget(self.lineEdit_user_id, 0, 1, 1, 1)
        self.login = QtWidgets.QPushButton(self.groupBox_login_info)
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(14)
        self.login.setFont(font)
        self.login.setObjectName("login")

        self.login.clicked.connect(lambda: self.openWindow(self.lineEdit_user_id.text(), self.lineEdit_password.text()))

        self.gridLayout.addWidget(self.login, 2, 0, 1, 2)
        self.horizontalLayout.addLayout(self.gridLayout)
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(130, 60, 791, 451))
        self.label_4.setMaximumSize(QtCore.QSize(791, 471))
        self.label_4.setText("")
        self.label_4.setPixmap(QtGui.QPixmap("Resource File\Landing page image.png"))
        self.label_4.setObjectName("label_4")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1099, 26))
        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.label_2.setText(_translate("MainWindow", "Blu"))
        self.groupBox_login_info.setTitle(_translate("MainWindow", "Login info:"))
        self.label_3.setText(_translate("MainWindow", "Password:"))
        self.label.setText(_translate("MainWindow", "User ID:"))
        self.login.setText(_translate("MainWindow", "Login"))


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_())

*This is the code for the option select page:

from PyQt5 import QtCore, QtGui, QtWidgets
import gui_login
import os
from PIL import Image

number = 2

this_dir = os.path.abspath(os.path.dirname(__file__))
some_image = os.path.join(this_dir, 'Resource File', 'Back_arrow_image.png')

class Ui_select(object):

    def openWindow(self):
        select.close()

        self.window = QtWidgets.QMainWindow()
        self.ui = gui_login.Ui_MainWindow()
        self.ui.setupUi(self.window)
        self.window.show()

    def back_arrow(self):
        self.back_button = QtWidgets.QPushButton(self.centralwidget)
        self.back_button.setGeometry(QtCore.QRect(770, 30, 221, 81))
        self.back_button.setObjectName("back_button")
        BlackArrow = Image.open(some_image)
        new_image = BlackArrow.resize((1920, 1920))
        new_image.save("WhiteArrow.png")
        self.back_button.setIcon(QtGui.QIcon("WhiteArrow.png"))
        self.back_button.clicked.connect(lambda: self.openWindow())

    def setupUi(self, select):
        select.setObjectName("select")
        select.resize(1090, 600)
        select.setStyleSheet("background-color: rgb(4, 112, 54)\n"
"")

        self.centralwidget = QtWidgets.QWidget(select)
        self.centralwidget.setObjectName("centralwidget")
        self.set_parameters = QtWidgets.QPushButton(self.centralwidget)
        self.set_parameters.setGeometry(QtCore.QRect(380, 300, 281, 71))
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(18)
        self.set_parameters.setFont(font)
        self.set_parameters.setStyleSheet("color: rgb(255, 255, 255)")
        self.set_parameters.setObjectName("set_parameters")
        self.set_parameters_2 = QtWidgets.QPushButton(self.centralwidget)
        self.set_parameters_2.setGeometry(QtCore.QRect(380, 390, 281, 71))
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(18)
        self.set_parameters_2.setFont(font)
        self.set_parameters_2.setStyleSheet("color: rgb(255, 255, 255)")
        self.set_parameters_2.setObjectName("set_parameters_2")
        self.set_parameters_3 = QtWidgets.QPushButton(self.centralwidget)
        self.set_parameters_3.setGeometry(QtCore.QRect(380, 210, 281, 71))
        font = QtGui.QFont()
        font.setFamily("Bahnschrift Light Condensed")
        font.setPointSize(18)
        self.set_parameters_3.setFont(font)
        self.set_parameters_3.setStyleSheet("color: rgb(255, 255, 255)")
        self.set_parameters_3.setObjectName("set_parameters_3")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(470, 60, 111, 41))
        font = QtGui.QFont()
        font.setFamily("Broadway")
        font.setPointSize(35)
        self.label_2.setFont(font)
        self.label_2.setStyleSheet("color: rgb(255, 255, 255)")
        self.label_2.setObjectName("label_2")
        select.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(select)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1090, 26))
        self.menubar.setObjectName("menubar")
        self.menuMenu = QtWidgets.QMenu(self.menubar)
        self.menuMenu.setObjectName("menuMenu")
        select.setMenuBar(self.menubar)
        self.back_arrow()
        self.statusbar = QtWidgets.QStatusBar(select)
        self.statusbar.setObjectName("statusbar")
        select.setStatusBar(self.statusbar)
        self.menubar.addAction(self.menuMenu.menuAction())

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

    def retranslateUi(self, select):
        _translate = QtCore.QCoreApplication.translate
        select.setWindowTitle(_translate("select", "MainWindow"))
        self.set_parameters.setText(_translate("select", "Set Parameters"))
        self.set_parameters_2.setText(_translate("select", "History"))
        self.set_parameters_3.setText(_translate("select", "Auto Detect"))
        self.label_2.setText(_translate("select", "Blu"))
        self.menuMenu.setTitle(_translate("select", "Menu "))


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

Thanks in advance


Solution

  • If you run your code on a shell/prompt, you'll see the following error:

    Exception "unhandled NameError"
    name 'select' is not defined
    File: /tmp/gui_select.py, Line: 14
    

    This is because select is only declared within the if __name__ of the second file.

    The Importance of Being __main__

    When a python file is loaded, everything in its main indentation level is processed, even when the script is imported.
    In your case, what's within the __name__ check in the second file is not executed (because for that file, __name__ is actually gui_select), which means that that whole block will be ignored and select will not be created, hence the crash with the above error.

    The if __name__ check is not only good practice, but also often mandatory.
    If you move everything in that block outside the if, it will be executed as soon as you import the file from the main script; the result would be that when you launch the first script, it will import the second, which will be completely executed, so it will create the QApplication instance and the select window, and then immediately show it without going further until that window is closed; then it will just exit the program (due to the sys.exit call) without showing the login window at all.


    Now, while the problem can theoretically be avoided easily, there is a bigger problem in your case: you have modified the files generated by `pyuic` to create your program.

    This is highly discouraged, as it often results in a moltitude of problems and misunderstandings (like in this case), but also the main reason is that whenever you need to modify again the GUI for any reason, you'll get serious troubles merging your existing code with the new files created by pyuic. As shown in the official guidelines about using Designer (and partially suggested by the "WARNING" in those files), those scripts must never be manually modified and should always be used as imports.

    This are the steps you need to follow:

    • create a new script, eventually copying the functions you added for your classes so that they are not deleted and you can modify them without writing them from scratch;
    • generate again the .ui files with pyuic (ensure that the object name of the second window is select in Designer);
    • modify the new script with something like this:
    from PyQt5 import QtWidgets
    from gui_login import Ui_MainWindow
    from gui_select import Ui_select
    
    class LoginWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self):
            super().__init__()
            self.setupUi(self)
            self.login.clicked.connect(self.showSelect)
    
        def showSelect(self):
            user_id = self.lineEdit_user_id.text()
            password = self.lineEdit_password.text()
            x = users.get(user_id)
            if x == int(password):
                self.close()
                self.selectWindow = SelectWindow()
                self.selectWindow.show()
            else:
                # this is *wrong*, see below
                self.login.setText("Wrong Password, Please Retry")
                time.sleep(1)
                self.login.setText("Login")
    
    
    class SelectWindow(QtWidgets.QMainWindow, Ui_select):
        def __init__(self):
            super().__init__()
            self.setupUi(self)
            self.back_button.clicked.connect(self.showLogin)
    
        def showLogin(self):
            self.close()
            self.loginWindow = LoginWindow()
            self.loginWindow.show()
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        loginWindow = LoginWindow()
        loginWindow.show()
        sys.exit(app.exec_())
    

    Some considerations:

    • Qt already provides image scaling capabilities, you don't need to use PIL (also, you should not save a new image every time); if you need to scale an image, use image = QtGui.QPixmap('path_to_image.png').scaled(width, height, transformMode=QtCore.Qt.SmoothTransformation);
    • QPushButton automatically scales the icon according to its iconSize() property, so it's not clear why you want to scale the image to 1920x1920; if you want to specify a bigger size, use self.back_button.setIconSize(QtCore.QSize(width, height));
    • if you don't need to provide custom parameters to functions connected to signals, don't use lambda;
    • fixed geometries are generally considered bad practice for a moltitude of reasons: what you see on Designer is not exactly what you see on your screen when you run the program, and it rarely is what users will see on theirs; also, if the user (or the OS) resizes the window, parts of the UI will become unavailable; use layout managers instead;
    • blocking functions (like time.sleep(1) in the first file) should never be called in the main Qt thread, as they prevent not only interaction, but most importantly correct GUI update/drawing: in fact, you will probably see that the button text is not updated with the "wrong password" text; use a QMessageBox instead, or a QTimer connected to a slot that updates the label:
    class LoginWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        # ...
        def showSelect(self):
            # ...
            else:
                self.login.setText("Wrong Password, Please Retry")
                self.login.setEnabled(False)
                QtCore.QTimer.singleShot(2000, self.restoreLogin)
    
        def restoreLogin(self):
            self.login.setText("Login")
            self.login.setEnabled(True)
    
    • while with the suggested modifications this is not a real issue anymore, you should not name the other window/ui as self.window or self.ui, as it may lead to confusion: those attributes should refer to the window and ui of the current class instance, if they belong to another instance you should probably name them with something like self.otherWindow and self.otherUi; this is not a programmatical issue, but a code reading/reviewing one: using well chosen names improves reading and understanding, which is very important even if it's your own code;