Ответ 1
Во-первых, просто знайте, что 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 в той степени, в которой вы назначаете имена переменных для виджетов и настраиваете их основные свойства. Не беспокойтесь о добавлении сигналов или слотов, так как обычно проще просто подключить их к функциям из класса представления.
Файлы макетов .ui преобразуются в файлы макетов .py при обработке с помощью Pyuic или Pyside-UIC. Файлы представления .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
функции 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>
Он конвертируется в 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 неправильно использует терминологию модель/представление?