使用Qt Designer和PyQt/PySide进行MVC设计 [英] MVC design with Qt Designer and PyQt / PySide

查看:163
本文介绍了使用Qt Designer和PyQt/PySide进行MVC设计的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

来自Java(+ SWT/Windowbuilder)的Python新手,并且难以确定如何在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设计器创建.ui布局文件.不要烦恼添加信号或插槽,因为通常从视图类内部将它们连接到函数通常会更容易.

使用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_amount函数从小部件中获取新值,执行逻辑并设置模型上的属性.

型号

模型类存储程序数据和状态以及一些宣布此数据更改的最小逻辑.此模型不应与Qt模型混淆(请参阅http: //qt-project.org/doc/qt-4.8/model-view-programming.html ),因为它并不是一回事.

该模型可能类似于:

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框架自动发生的.

UI源文件

为完整起见,此处包括示例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>

通过调用将其转换为main_view_ui.py

pyuic5 main_view.ui -o ..\views\main_view_ui.py

通过调用将资源文件mvc_app.qrc转换为mvc_app_rc.py

pyrcc5 mvc_app.qrc -o ..\mvc_app_rc.py

有趣的链接

为什么Qt滥用模型/视图术语?

Python newbie coming from Java (+SWT/Windowbuilder) and am having difficulty working out how to properly code a large desktop app in Python/Qt4(QtDesigner)/PySide.

I would like to keep any view logic in a controller class outside the .ui file (and it's .py conversion). Firstly as then the logic is independent of GUI framework and secondly as the .ui and resultant .py file get overwritten on any changes!.

Only examples I've found add action code to a monolithic MainWindow.py (generated from ui) or a MyForm.py (also generated from .ui). I can't see any way to link a POPO controller class to actions in QtDesigner.

Can anyone point me to workflows for creating a large scale application using QtDesigner in a scalable MVC/P methodology?

解决方案

Firstly, just be aware that Qt already uses the concept of views and models but that's not actually what you're after. In short it's a way to automatically link a widget (e.g. a QListView) to a data source (e.g. a QStringListModel) so that changes to the data in the model automagically appear in the widget and vice versa. This is a useful feature but it's a different thing to an application scale MVC design, although the two can be used together and it does offer some obvious shortcuts. Application scale MVC design however must be manually programmed.

Here's an example MVC application that has a single view, controller, and model. The view has 3 widgets that each independently listen for and react to changes to data in the model. The spin box and button can both manipulate data in the model via the controller.

The file structure is arranged like this:

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

Application

mvc_app.py would be responsible for instantiating each of the view, controllers, and model(s) and passing references between them. This can be quite minimal:

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

Views

Use Qt designer to create the .ui layout files to the extent that you assign variables names to widgets and adjust their basic properties. Don't bother adding signals or slots as it's generally easier just to connect them to functions from within the view class.

The .ui layout files are converted to .py layout files when processed with pyuic or pyside-uic. The .py view files can then import the relevant auto-generated classes from the .py layout files.

The view class(es) should contain the minimal code required to connect to the signals coming from the widgets in your layout. View events can call and pass basic information to a method in the view class and onto a method in a controller class, where any logic should be. It would look something like:

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)

The view doesn't do much apart from link widget events to the relevant controller function, and listen for changes in the model, which are emitted as Qt signals.

Controllers

The controller class(es) perform any logic and then sets data in the model. An example:

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

The change_amount function takes the new value from the widget, performs logic, and sets attributes on the model.

Model

The model class stores program data and state and some minimal logic for announcing changes to this data. This model shouldn't be confused with the Qt model (see http://qt-project.org/doc/qt-4.8/model-view-programming.html) as it's not really the same thing.

The model might look like:

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

Writes to the model automatically emit signals to any listening views via code in the setter decorated functions. Alternatively the controller could manually trigger the signal whenever it decides.

In the case where Qt model types (e.g. QStringListModel) have been connected with a widget then the view containing that widget does not need to be updated at all; this happens automatically via the Qt framework.

UI source file

For completion, the example main_view.ui file is included here:

<?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>

It is converted to main_view_ui.py by calling:

pyuic5 main_view.ui -o ..\views\main_view_ui.py

The resource file mvc_app.qrc is converted to mvc_app_rc.py by calling:

pyrcc5 mvc_app.qrc -o ..\mvc_app_rc.py

Interesting links

Why Qt is misusing model/view terminology?

这篇关于使用Qt Designer和PyQt/PySide进行MVC设计的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆