Python 使用Qt设计器和PyQt/PySide进行MVC设计

Python 使用Qt设计器和PyQt/PySide进行MVC设计,python,model-view-controller,pyqt,pyside,qt-designer,Python,Model View Controller,Pyqt,Pyside,Qt Designer,Python新手来自Java(+SWT/Windowbuilder),我很难用Python/Qt4(QtDesigner)/PySide正确编写大型桌面应用程序 我希望将控制器类中的任何视图逻辑保留在.ui文件之外(以及它的.py转换)。首先,逻辑独立于GUI框架,其次,.ui和生成的.py文件在任何更改时都会被覆盖 我发现的唯一示例是将操作代码添加到MainWindow.py(从ui生成)或MyForm.py(也从.ui生成)。我找不到任何方法将POPO控制器类链接到QtDesigner中的操

Python新手来自Java(+SWT/Windowbuilder),我很难用Python/Qt4(QtDesigner)/PySide正确编写大型桌面应用程序

我希望将控制器类中的任何视图逻辑保留在.ui文件之外(以及它的.py转换)。首先,逻辑独立于GUI框架,其次,.ui和生成的.py文件在任何更改时都会被覆盖

我发现的唯一示例是将操作代码添加到MainWindow.py(从ui生成)或MyForm.py(也从.ui生成)。我找不到任何方法将POPO控制器类链接到QtDesigner中的操作


有人能告诉我在可伸缩MVC/p方法中使用QtDesigner创建大规模应用程序的工作流吗?

首先,请注意Qt已经使用了视图和模型的概念,但实际上这并不是您想要的。简而言之,这是一种自动将小部件(例如QListView)链接到数据源(例如QStringListModel)的方法,以便模型中的数据更改自动显示在小部件中,反之亦然。这是一个有用的特性,但它与应用程序级MVC设计不同,尽管两者可以一起使用,并且它确实提供了一些明显的快捷方式。然而,应用程序规模的MVC设计必须手动编程

下面是一个示例MVC应用程序,它有一个视图、控制器和模型。该视图有3个小部件,每个小部件都独立地侦听和响应模型中数据的更改。旋转框和按钮都可以通过控制器操纵模型中的数据

文件结构如下所示:

project/
    mvc_app.py              # main application with App class
    mvc_app_rc.py           # auto-generated resources file (using pyrcc.exe or equivalent)
    controllers/
        main_ctrl.py        # main controller with MainController class
        other_ctrl.py
    model/
        model.py            # model with Model class
    resources/
        mvc_app.qrc         # Qt resources file
        main_view.ui        # Qt designer files
        other_view.ui
        img/
            icon.png
    views/
        main_view.py        # main view with MainView class
        main_view_ui.py     # auto-generated ui file (using pyuic.exe or equivalent)
        other_view.py
        other_view_ui.py
应用
mvc_app.py
将负责实例化每个视图、控制器和模型,并在它们之间传递引用。这可能非常小:

import sys
from PyQt5.QtWidgets import QApplication
from model.model import Model
from controllers.main_ctrl import MainController
from views.main_view import MainView


class App(QApplication):
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.model = Model()
        self.main_controller = MainController(self.model)
        self.main_view = MainView(self.model, self.main_controller)
        self.main_view.show()


if __name__ == '__main__':
    app = App(sys.argv)
    sys.exit(app.exec_())
意见 使用Qt designer创建.ui布局文件,以便为小部件指定变量名称并调整其基本属性。不要费心添加信号或插槽,因为从view类中将它们连接到函数通常更容易

使用pyuic或pyside uic处理时,.ui布局文件将转换为.py布局文件。然后,.py视图文件可以从.py布局文件导入相关的自动生成类

视图类应该包含连接来自布局中小部件的信号所需的最少代码。视图事件可以调用基本信息并将其传递给视图类中的一个方法和控制器类中的一个方法,其中应该包含任何逻辑。它看起来像:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import pyqtSlot
from views.main_view_ui import Ui_MainWindow


class MainView(QMainWindow):
    def __init__(self, model, main_controller):
        super().__init__()

        self._model = model
        self._main_controller = main_controller
        self._ui = Ui_MainWindow()
        self._ui.setupUi(self)

        # connect widgets to controller
        self._ui.spinBox_amount.valueChanged.connect(self._main_controller.change_amount)
        self._ui.pushButton_reset.clicked.connect(lambda: self._main_controller.change_amount(0))

        # listen for model event signals
        self._model.amount_changed.connect(self.on_amount_changed)
        self._model.even_odd_changed.connect(self.on_even_odd_changed)
        self._model.enable_reset_changed.connect(self.on_enable_reset_changed)

        # set a default value
        self._main_controller.change_amount(42)

    @pyqtSlot(int)
    def on_amount_changed(self, value):
        self._ui.spinBox_amount.setValue(value)

    @pyqtSlot(str)
    def on_even_odd_changed(self, value):
        self._ui.label_even_odd.setText(value)

    @pyqtSlot(bool)
    def on_enable_reset_changed(self, value):
        self._ui.pushButton_reset.setEnabled(value)
该视图除了将小部件事件链接到相关的控制器函数,并侦听模型中的更改(这些更改作为Qt信号发出)之外,没有做太多的工作

控制器 控制器类执行任何逻辑,然后在模型中设置数据。例如:

from PyQt5.QtCore import QObject, pyqtSlot


class MainController(QObject):
    def __init__(self, model):
        super().__init__()

        self._model = model

    @pyqtSlot(int)
    def change_amount(self, value):
        self._model.amount = value

        # calculate even or odd
        self._model.even_odd = 'odd' if value % 2 else 'even'

        # calculate button enabled state
        self._model.enable_reset = True if value else False
change\u amount
函数从小部件获取新值,执行逻辑,并在模型上设置属性

模型 model类存储程序数据和状态,以及一些用于宣布此数据更改的最小逻辑。这个模型不应该与Qt模型()混淆,因为它实际上不是一回事

模型可能如下所示:

from PyQt5.QtCore import QObject, pyqtSignal


class Model(QObject):
    amount_changed = pyqtSignal(int)
    even_odd_changed = pyqtSignal(str)
    enable_reset_changed = pyqtSignal(bool)

    @property
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, value):
        self._amount = value
        self.amount_changed.emit(value)

    @property
    def even_odd(self):
        return self._even_odd

    @even_odd.setter
    def even_odd(self, value):
        self._even_odd = value
        self.even_odd_changed.emit(value)

    @property
    def enable_reset(self):
        return self._enable_reset

    @enable_reset.setter
    def enable_reset(self, value):
        self._enable_reset = value
        self.enable_reset_changed.emit(value)

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

        self._amount = 0
        self._even_odd = ''
        self._enable_reset = False
写入模型会通过
setter
函数中的代码自动向任何侦听视图发出信号。或者,控制器可以在决定何时手动触发信号

如果Qt模型类型(例如QStringListModel)已与小部件连接,则不需要更新包含该小部件的视图;这是通过Qt框架自动实现的

用户界面源文件 为了完成此操作,此处包含示例
main_view.ui
文件:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>93</width>
    <height>86</height>
   </rect>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout">
    <item>
     <widget class="QSpinBox" name="spinBox_amount"/>
    </item>
    <item>
     <widget class="QLabel" name="label_even_odd"/>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton_reset">
      <property name="enabled">
       <bool>false</bool>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
通过调用以下命令将资源文件
mvc\u app.qrc
转换为
mvc\u app\u rc.py

pyuic5 main_view.ui -o ..\views\main_view_ui.py
pyrcc5 mvc_app.qrc -o ..\mvc_app_rc.py
有趣的链接

好的,我现在开始了解事物是如何结合在一起的。在我看来,main_view.py是将模型链接到ui_main_view.py并向main_cont.py发送信号的粘合层。这就是我无法理解如何使用qtDesigner的原因。感谢您的详细解释。添加了一些代码示例和指向代码生成器的链接,该代码生成器可以自动创建大部分代码。只是不要创建
Ui\u MainWindow()
对象,而是手动在视图中构建小部件(如许多教程所示)。我没有使用状态机API,但我不明白为什么不能同时使用它们。@thinwybk有点复杂了,你能问个新问题吗?