Python - PyQt5 - MVC

5
我正在用Python创建我的第一个应用程序。目前,我正在使用PyQt5和MVC模式实现第一个视图。因此,我创建了一个“main”类,它通过创建新的控制器对象(我学习的第一种编程语言是Java,所以可能这不是必要的)来启动控制器。在这个控制器对象的init方法中,我正在创建第一个视图。视图类本身创建一个新的QWidget。在创建QWidget后,控制器调用了视图(QWidget)的一个方法。该方法应该显示登录屏幕。为了显示登录屏幕,创建了一个新类(Login)。该类是QVBoxLayout类型,并添加到主视图(QWidget)。这种组合导致一个显示带有登录窗口的应用程序。这意味着主类-> 控制器->主窗口(QWidget)->登录(QVBoxLayout)。
对于这点我有以下疑问:创建主窗口(QWidget)并使用方法将内部布局添加到窗口(使用其他文件/类)是正确的方式吗?还是应该在一个类中编写所有内容?
现在我已经达到了布局和窗口正确显示的地步。缺少的是模型以及如何调用模型。我搜索了如何检查按钮是否被按下。我找到了button.clicked.connect(callfunction)。这似乎是正确的方法,但是如何从控制器调用此函数呢?因此,控制器创建应用程序窗口并在其中显示登录。然后控制器侦听并等待按钮被按下。然后将输入转发到模型中,并在模型中检查凭据。以下是我的控制器源代码以及尝试侦听按钮:
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")

我的登录类代码:

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

我的View类:

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)

更多问题:

  • 我对MVC模式的理解正确吗?我使用了太多类吗?
  • 在控制器中应该监听按钮,还是视图调用控制器中的方法是正确的?
  • 此外,我实现了一个功能,可以通过错误代码调用登录QVBoxLayout以在视图底部显示错误。我没有找到一种从控制器类动态更改视图的方法。我唯一能想象的解决方案是“重绘”QWidget的内容并添加错误消息。这是正确的解决方案吗?

提前谢谢!


1
widget.loginButton.clicked.connect(self.loginPressed())更改为widget.loginButton.clicked.connect(self.loginPressed),去掉() - eyllanesc
将“widget”更改为“self.widget”。 - eyllanesc
展示你的 Login 类完整代码。 - eyllanesc
2
通常,MVC模式会有一个主类实例化视图和控制器,视图将接收到控制器的引用。不要让控制器实例化视图。 - 101
1
视图类应该定义按钮并设置其事件响应。按钮单击事件应该调用视图引用的控制器。 - 101
2个回答

9

重点不在于类的数量,而在于每个类的任务。

  • 模型是保存信息的实体。
  • 视图是显示信息的实体。
  • 控制器根据一定逻辑控制视觉数据的流动。

那么您认为您的类是否实现了上述功能?

在下面的示例中,我展示了如何重新分配这些任务,我的示例并不符合所有经典MCV规则,比如@101所指出的规则,因为GUI具有内部事件处理程序。

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

我很好奇,在这个例子中没有从模型传递数据到视图。如果视图在启动时需要一些数据,你通常会如何处理? - Robert Criqui
你为什么要在类名后面不加括号来定义类,只有在创建实例时才添加括号呢? - 10mjg

0
为了补充@eyllanesc上面所说的并将其抽象化,我使用MVC模型来处理几乎涉及向用户呈现信息的所有内容。因此,我的MVC也包含了一些后来从MVC派生出来的其他模型,并将其视为各种MVC口味,即使它是冰淇淋,无论你放什么口味的东西进去都是冰淇淋。
首先,它主要设计用于处理与数据库连接相关的问题,其中Model是数据库,View是GUI,Controller是中间层,负责从数据库到GUI以及从GUI到数据库的流程。然而,在我的版本中,中间层已经成为整个过程的工作马,因为它处理所有数据验证,并应用任何需要应用于数据的业务逻辑,无论数据流向何方。
说了这么多,我们可以退后一步,看看像Python/PyQt应用程序这样的东西:
Model = 后端,处理数据管理、存储和/或生产(数据关联语言-SQL、C等)

控制器 = 中间层处理数据验证和大部分业务规则的应用。注意:正如我上面所说,我略微偏离传统版本,将验证和业务规则转移到此部分,我这样做是因为这意味着数据只是数据,我可以将其应用于无法实现验证或规则的事物,例如低级串口等。当然,如果后端是数据库,则可能在其中实现一些数据规则,这些规则可能类似于业务规则,但在这个版本中,应该将它们保持最小,并更多地处理数据传播等问题。(仅适用于Python)

视图 = GUI前端处理向用户呈现数据。请注意,有时会在此处执行一些基本的数据验证,例如如果它只是数字,则确保它只是数字之类的操作。(主要是Python/PyQt)

每个实体都应该被制作成几乎独立于其他实体,并且前端完全与后端分离。这意味着,如果我想要替换中间层,我应该能够几乎无缝地进行替换,而不需要对前端或后端进行更改;同样,如果我用其他东西替换了前端和后端,并与中间层以前相同的连接,则不会影响其他任何内容。当然,这并不总是发生,因为有时会有影响所有三个层的更改,但是它们仍然可以独立完成,只需达成一致的是从GUI和后端和/或中间层发送/接收什么。

在前端编码时,可以通过不建立连接来实现。您只需调用一个占位符函数(最终将被中间层替换),该函数仅提供应从中间层获取的数据。从 GUI 方面来看,您不需要关心数据以何种格式存在,您只需要知道它将如何提供即可。发送返回数据的数据也是如此,只需为前端创建一个模拟情况的虚拟函数即可。这样,您就可以专注于向用户呈现数据并接收用户输入,而这就是全部。请记住,最重要的是保持简单和聪明。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接