Projektowanie MVC z Qt Designer i PyQt / PySide

Początkujący Python pochodzący z Javy (+SWT / Windowbuilder) i mam problemy z poprawnym zakodowaniem dużej aplikacji desktopowej w Python/Qt4(QtDesigner)/PySide.

Chciałbym zachować dowolną logikę widoku w klasie kontrolera poza .plik ui (i jest to konwersja. py). Po pierwsze, jak wtedy logika jest niezależna od frameworka GUI, a po drugie, jak .interfejs użytkownika i wynikowy plik .py zostają nadpisane na wszelkie zmiany!.

Tylko przykłady, które znalazłem Dodaj kod akcji do monolitu MainWindow.py (wygenerowany z ui) lub MyForm.py (również generowane z .ui). Nie widzę sposobu na powiązanie klasy kontrolera POPO z akcjami w QtDesigner.

Czy ktoś może wskazać mi przepływy pracy przy tworzeniu aplikacji wielkoskalowej przy użyciu QtDesigner w skalowalnej metodologii MVC / P?

Author: 101, 2014-11-02

1 answers

Po pierwsze, pamiętaj, że Qt już używa koncepcji widoków i modeli, ale nie o to ci chodzi. W skrócie jest to sposób na automatyczne powiązanie widżetu (np. QListView) ze źródłem danych (np. QStringListModel), dzięki czemu zmiany danych w modelu automatycznie pojawiają się w widżecie i odwrotnie. Jest to przydatna funkcja, ale to inna rzecz niż projekt MVC w skali aplikacji, chociaż oba mogą być używane razem i oferują pewne oczywiste na skróty. Projekt skali aplikacji MVC musi być jednak zaprogramowany ręcznie.

Oto przykładowa aplikacja MVC, która ma pojedynczy widok, kontroler i model. Widok ma 3 widżety, z których każdy niezależnie nasłuchuje i reaguje na zmiany danych w modelu. Pole obrotowe i przycisk mogą manipulować danymi w modelu za pomocą kontrolera.

mvc_app

Struktura plików jest ułożona w następujący sposób:

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

Zastosowanie

mvc_app.py would odpowiada za tworzenie instancji każdego z widoków, kontrolerów i modeli oraz przekazywanie odniesień między nimi. To może być dość minimalne:

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_ctrl)
        self.main_view.show()


if __name__ == '__main__':
    app = App(sys.argv)
    sys.exit(app.exec_())

Views

Użyj Qt designer, aby utworzyć .Pliki Układów interfejsu użytkownika w zakresie, w jakim można przypisać nazwy zmiennych do widżetów i dostosować ich podstawowe właściwości. Nie przejmuj się dodawaniem sygnałów lub slotów, ponieważ ogólnie łatwiej jest po prostu podłączyć je do funkcji z poziomu klasy view.

The .Pliki Układów interfejsu użytkownika są konwertowane do .py Pliki układów graficznych przetwarzane za pomocą pyuic lub pyside-UIC. Pliki widoku .py mogą następnie importować odpowiednie automatycznie wygenerowane klasy z plików układu. py.

Klasa (klasy) widoku powinna zawierać minimalny kod wymagany do połączenia się z sygnałami pochodzącymi z widżetów w układzie. Zdarzenia widoku mogą wywoływać i przekazywać podstawowe informacje do metody w klasie view oraz do metody w klasie kontrolera, gdzie powinna znajdować się Dowolna logika. Wyglądałoby to tak:

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)

Widok nie robi wiele, oprócz wydarzeń widżetu link do odpowiedniej funkcji kontrolera, i słuchać zmian w modelu, które są emitowane jako sygnały Qt.

Kontrolery

Klasy kontrolerów wykonują dowolną logikę, a następnie ustawiają dane w modelu. Przykład:

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

Funkcja change_amount pobiera nową wartość z widżetu, wykonuje logikę i ustawia atrybuty modelu.

Model

Klasa modelu przechowuje dane programu i stan oraz niektóre minimalna logika ogłaszania zmian w tych danych. Tego modelu nie należy mylić z Modelem Qt (zobacz http://qt-project.org/doc/qt-4.8/model-view-programming.html ) ponieważ to nie to samo.

Model może wyglądać następująco:

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

Zapis do modelu Automatycznie emituje sygnały do dowolnych odsłuchów za pomocą kodu w funkcji setter. Alternatywnie sterownik może ręcznie wyzwalać sygnał, gdy tylko zdecyduje.

W przypadku w przypadku gdy typy modeli Qt (np. QStringListModel) zostały połączone z widżetem, widok zawierający ten widżet nie musi być w ogóle aktualizowany; dzieje się to automatycznie za pośrednictwem frameworka Qt.

Plik źródłowy UI

Aby zakończyć, przykład main_view.ui plik znajduje się tutaj:

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

Jest on konwertowany na main_view_ui.py przez wywołanie:

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

Plik zasobu mvc_app.qrc jest konwertowany na mvc_app_rc.py przez wywołanie:

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

Python MVC code generator

Biorąc pod uwagę powtarzalność kodu potrzebnego dla każdego widgetu, używam generatora kodu do automatycznego generowania wielu kodu gołych kości w rzeczywistym widoku, kontrolerze i Module modelu. Jest dość przestarzały, ale może zaoszczędzić dużo czasu dla klas z wieloma atrybutami. Można go znaleźć na https://github.com/morefigs/Py-Qt-MVC .

Ciekawe linki

Dlaczego Qt nadużywa terminologii model/widok?

 23
Author: 101,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-10 08:46:19