pythonqtpyqtpyqt5qt5

PyQt5 How to add a pyuic5 generated Python class into QStackedWidget?


I've this in my app.py:

from PyQt5 import QtWidgets
from screens import Ui_home, Ui_login_page


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        
        self.home = Ui_home()
        self.home.setupUi(self)

        self.login = Ui_login_page()
        self.login.setupUi(self)
        
        self.screens = QtWidgets.QStackedWidget()
        self.screens.addWidget(self.home)
        self.screens.addWidget(self.login)

        self.home.pushButton.clicked.connect(
            lambda: self.screens.setCurrentWidget(self.login)
        )


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()

In above code, Ui_home & Ui_login_page screens are generated from a *.ui file which is generated from PyQt5 Designer. When I run this code, I get this error:

self.screens.addWidget(self.home)
TypeError: addWidget(self, w: Optional[QWidget]): argument 1 has unexpected type 'Ui_home'

I know that self.setCurrentWidget() requires a QWidget, and the code generated from pyuic5 inherits the class from Python object. How can I fix this so that my home and login screens can be added into the stack widget?


Solution

  • The following things are wrong or don't make sense:

    Make the stacked widget as central widget, then create appropriate QWidget instances and call setupUi() on each one of them, finally add those widgets to the stacked widget:

    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            
            self.screens = QtWidgets.QStackedWidget()
            self.setCentralWidget(self.screens)
    
            self.home = QtWidgets.QWidget()
            self.home_ui = Ui_home()
            self.home_ui.setupUi(self.home)
    
            self.login = QtWidgets.QWidget()
            self.login_ui = Ui_login_page()
            self.login_ui.setupUi(self.login)
            
            self.screens.addWidget(self.home)
            self.screens.addWidget(self.login)
    
            self.home_ui.pushButton.clicked.connect(
                lambda: self.screens.setCurrentWidget(self.login)
            )
    

    Note that a more appropriate approach (also explained in the official guidelines about using Designer) is to use multiple inheritance against composition (which is what is shown above); that approach provides better modularization since each "page" will have its own UI elements as instance attributes, avoiding the need for a further ui object.

    class HomePage(QtWidgets.QWidget, Ui_home):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setupUi(self)
    
    
    class LoginPage(QtWidgets.QWidget, Ui_login_page):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setupUi(self)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            
            self.screens = QtWidgets.QStackedWidget()
            self.setCentralWidget(self.screens)
    
            self.home = HomePage()
            self.login = LoginPage()
            
            self.screens.addWidget(self.home)
            self.screens.addWidget(self.login)
    
            # note that now it's "self.home", not "self.home_ui"
            self.home.pushButton.clicked.connect(
                lambda: self.screens.setCurrentWidget(self.login)
            )
    

    In the code above the difference seems minimal (controversially both pointless and unnecessarily verbose), but the point is that creating a subclass for each page or container widget allows proper and simpler implementation and debugging (see encapsulation), such as the possibility of adding custom signals, creating internal functions, and simplify access to object attributes.