Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - PyQt5 - MVC

I'm creating my first application in Python. Currently I'm implementing the first view with PyQt5 and the MVC pattern. Therefore I created a "main" class that is starting the controller by creating a new controller object (The first programming language I learned was Java, so maybe this is not necassary?). In this controller objects init method I'm creating the first view. The view class itself creates a new QWidget. After the QWidget is created the controller is calling a method of the view (QWidget). This method should show the login screen. To show the login screen a new class (Login) is created. That class is of the type QVBoxLayout and is added to the main view (QWidget). This combination leads to an application that is showing a window with the login. This means main class -> controller -> main window (QWidget) -> login (QVBoxLayout).

Here I have the following question: Is it the correct way to create a main window (QWidget) and use methods to add the inner layout to the window (using other files/classes)? Or should everything be written in one class?

Now I reached the point that my layout and window are displayed correctly. What is missing is the model and how the model is invoked. I searched how I can check if a button was pressed. I found button.clicked.connect(callfunction). This seems to be the right way but how can I call this function from the controller? So that the controller creates the application window and displays the login inside this window. Then the controller listens and waits until the button is pressed. Then the inputs will be forwarded to the model and in the model the credentials will be checked. Here is my sourcecode for the controller with my try on listening to the button:

class Controller(object):

    def __init__(self):
        # Applikation starten und Login anzeigen
        app = QApplication(sys.argv)
        widget = View()
        widget.showLogin(0, "")
        widget.loginButton.clicked.connect(self.loginPressed())
        sys.exit(app.exec_())

    def loginPressed(self):
        widget.showLogin(1, "err1")

The code for my Login class:

def __init__(self):
    super().__init__()

    # Zum vertikalen zentrieren des Inhalts
    self.addStretch()

    # Label (Bild) erstellen und zum Layout hinzufügen
    label = QLabel()
    pixmap = QPixmap(pathLogo)
    label.setPixmap(pixmap.scaledToWidth(logoWidth, Qt.SmoothTransformation))
    label.setAlignment(Qt.AlignCenter)
    self.addWidget(label)

    # Label für den Nutzername
    usernameLabel = QLabel("Username")
    usernameLabel.setAlignment(Qt.AlignCenter)
    usernameLabel.setStyleSheet("QLabel {color: #ffffff; font-size: 14px; font-weight:bold; "
                                "margin:50px, 0, 5px, 0 ;}")
    self.addWidget(usernameLabel)

    # Eingabefeld für den Nutzername
    uihbox = QHBoxLayout()
    uihbox.addStretch()
    usernameInput = QLineEdit()
    usernameInput.setFixedWidth(150)
    usernameInput.setStyleSheet(
        "QLineEdit {border-radius: 5px; padding: 4px; line-height:12px; padding-left: 5px;}")
    uihbox.addWidget(usernameInput)
    uihbox.addStretch()
    self.addLayout(uihbox)

    # Label für das Passwort
    passwortLabel = QLabel("Passwort")
    passwortLabel.setAlignment(Qt.AlignCenter)
    passwortLabel.setStyleSheet("QLabel {color: #ffffff; font-size: 14px; font-weight:bold; "
                                "margin:15px, 0, 5px, 0 ;}")
    self.addWidget(passwortLabel)

    # Eingabefeld für den Nutzername
    pihbox = QHBoxLayout()
    pihbox.addStretch()
    passwordInput = QLineEdit()
    passwordInput.setFixedWidth(150)
    passwordInput.setEchoMode(QLineEdit.Password)
    passwordInput.setStyleSheet(
        "QLineEdit {border-radius: 5px; padding: 4px; line-height:12px; padding-left: 5px;}")
    pihbox.addWidget(passwordInput)
    pihbox.addStretch()
    self.addLayout(pihbox)

    # Button erstellen
    bihbox = QHBoxLayout()
    bihbox.addStretch()
    loginButton = QPushButton("Login")
    loginButton.setStyleSheet("QPushButton {margin: 25px, 0, 0, 0; border-radius:5px; border: 1px solid white; "
                              "padding-right: 15px; padding-left: 15px; padding-top: 5px; padding-bottom:5px;"
                              "color:white; font-weight:bold; font-size: 14px;}")
    loginButton.setCursor(QCursor(Qt.PointingHandCursor))

    bihbox.addWidget(loginButton)
    bihbox.addStretch()
    self.addLayout(bihbox)

    # Zum vertikalen zentrieren des Inhalts
    self.addStretch()

def showError(self, errCode):
    errMsg = QLabel(err1)
    errMsg.setAlignment(Qt.AlignCenter)
    errMsg.setStyleSheet("QLabel {color:red;}")
    self.addWidget(errMsg)
    self.addStretch()

And my View class:

class View(QWidget):

    # Methode um das Fenster der Applikation zu initialisieren
    def __init__(self):
        super().__init__()

        # Breite und Höhe setzen
        self.resize(initWidth, initHeight)

        # Titel und Icon setzen
        self.setWindowTitle(appTitle)
        self.setWindowIcon(QIcon(pathFavicon))

        # Hintergrund mit der bgColor füllen
        self.setAutoFillBackground(True)
        p = self.palette()
        p.setColor(self.backgroundRole(), QColor(bgColor))
        self.setPalette(p)

        # Anzeigen des Fensters
        self.show()

    # Methode, um Login zu zeigen
    def showLogin(self, err, errcode):
        # Laden des Inhalts mittels Login
        if (err != 0):
            vbox = Login()
            vbox.showError(errcode)
        if (err == 0):
            vbox = Login()

        self.setLayout(vbox)

Further questions:

  • Is my understanding of the MVC pattern correct or do I use to many classes?
  • Should I listen on the button in the controller or is it correct that the view is invoking a method in the controller?
  • Further I implemented that I can call the login QVBoxLayout with an error code to display an error at the bottom of the view. I have not found a way to dynamically change the view from the controller class. The only solution I could imagine is that I "repaint" the content of the QWidget with the added error message. Is this the correct solution?

Thanks in advance!

like image 429
Christoph Avatar asked Sep 08 '26 04:09

Christoph


1 Answers

The idea is not the number of classes, but what is the task of each class.

  • A model is the entity that keeps the information.
  • The view is the entity that shows the information.
  • And the controller is the one that controls the flow of sight data according to a certain logic.

So do you think your classes implement the above?

In the following example I show how those tasks are redistributed, my example does not meet all the classic MCV rules like the one pointed out by @101 because the GUIs have an internal event handler.

import sys
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets


class Model:
    def __init__(self):
        self.username = ""
        self.password = ""

    def verify_password(self):
        return self.username == "USER" and self.password == "PASS"


class View(QtWidgets.QWidget):
    verifySignal = QtCore.pyqtSignal()

    def __init__(self):
        super(View, self).__init__()
        self.username = ""
        self.password = ""
        self.initUi()

    def initUi(self):
        lay = QtWidgets.QVBoxLayout(self)
        title = QtWidgets.QLabel("<b>LOGIN</b>")
        lay.addWidget(title, alignment=QtCore.Qt.AlignHCenter)

        fwidget = QtWidgets.QWidget()
        flay = QtWidgets.QFormLayout(fwidget)
        self.usernameInput = QtWidgets.QLineEdit()
        self.usernameInput.textChanged.connect(partial(setattr, self, "username"))
        self.passwordInput = QtWidgets.QLineEdit(echoMode=QtWidgets.QLineEdit.Password)
        self.passwordInput.textChanged.connect(partial(setattr, self, "password"))
        self.loginButton = QtWidgets.QPushButton("Login")
        self.loginButton.clicked.connect(self.verifySignal)

        flay.addRow("Username: ", self.usernameInput)
        flay.addRow("Password: ", self.passwordInput)
        flay.addRow(self.loginButton)

        lay.addWidget(fwidget, alignment=QtCore.Qt.AlignHCenter)
        lay.addStretch()

    def clear(self):
        self.usernameInput.clear()
        self.passwordInput.clear()

    def showMessage(self):
        messageBox = QtWidgets.QMessageBox(self)
        messageBox.setText("your credentials are valid\n Welcome")
        messageBox.exec_()
        self.close()

    def showError(self):
        messageBox = QtWidgets.QMessageBox(self)
        messageBox.setText("your credentials are not valid\nTry again...")
        messageBox.setIcon(QtWidgets.QMessageBox.Critical)
        messageBox.exec_()


class Controller:
    def __init__(self):
        self._app = QtWidgets.QApplication(sys.argv)
        self._model = Model()
        self._view = View()
        self.init()

    def init(self):
        self._view.verifySignal.connect(self.verify_credentials)

    def verify_credentials(self):
        self._model.username = self._view.username
        self._model.password = self._view.password
        self._view.clear()
        if self._model.verify_password():
            self._view.showMessage()
        else:
            self._view.showError()

    def run(self):
        self._view.show()
        return self._app.exec_()


if __name__ == '__main__':
    c = Controller()
    sys.exit(c.run())
like image 116
eyllanesc Avatar answered Sep 10 '26 19:09

eyllanesc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!