python - Qt DesignerとPyQt / PySideを使用したMVCデザイン

原文 python model-view-controller pyqt pyside qt-designer

Java(+ SWT / Windowbuilder)から来たPython初心者で、Python / Qt4(QtDesigner)/ PySideで大規模なデスクトップアプリを適切にコーディングする方法を見つけるのに苦労しています。

.uiファイルの外部のコントローラークラスにビューロジックを保持したいと思います(そして.py変換です)。第一に、ロジックはGUIフレームワークから独立しており、第二に、.uiおよび結果の.pyファイルが変更時に上書きされるためです。

私が見つけた例だけが、モノリシックMainWindow.py(uiから生成)またはMyForm.py(.uiからも生成)にアクションコードを追加します。 POPOコントローラークラスをQtDesignerのアクションにリンクする方法がわかりません。

QtDesignerを使用してスケーラブルなMVC / P方法論で大規模なアプリケーションを作成するためのワークフローを教えてください。
答え
まず、Qtが既にビューとモデルの概念を使用していることに注意してください。しかし、それは実際にはあなたが求めているものではありません。要するに、ウィジェット(QListViewなど)をデータソース(QStringListModelなど)に自動的にリンクして、モデル内のデータへの変更がウィジェットに自動的に表示されるようにする方法です。これは便利な機能ですが、アプリケーションスケールのMVC設計とは異なりますが、2つを一緒に使用でき、いくつかの明らかなショートカットを提供します。ただし、アプリケーションスケールのMVC設計は手動でプログラムする必要があります。

以下に、単一のビュー、コントローラー、モデルを持つMVCアプリケーションの例を示します。このビューには3つのウィジェットがあり、それぞれがモデル内のデータの変更を個別にリッスンして反応します。スピンボックスとボタンはどちらも、コントローラーを介してモデル内のデータを操作できます。

mvc_app

ファイル構造は次のように配置されます。

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レイアウトファイルを作成します。ビュークラス内から関数に接続するだけで簡単になるため、信号やスロットを追加する必要はありません。

.uiレイアウトファイルは、pyuicまたはpyside-uicで処理されると.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モデル(see 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


興味深いリンク

Why Qt is misusing model/view terminology?
関連記事

php - 子プロセスを定期的に再起動する

python - Numpyがa + = bとa = a + bを別々に扱う理由

python - if文のPython変数スコープ[複製]

python - Jinja2でのテンプレートの動的な使用

python - sqlite3とsqlalchemyの違いは何ですか?

python - Pythonは `<itertools.combinations object at 0x10049b470>`を返します-これにアクセスするにはどうすればよいですか?

python - Python:オブジェクトの保存とpickleの使用。ファイル名の拡張子

python - PythonでHTTP GET引数を取得する

python - PyQt:システムトレイアプリケーションのメニューを表示

php - PHPで引数の配列をアンパックする