PyQtでのMVC その1 (データ表示)
大量のデータをまとめるために
多くのデータを扱うアプリケーションでは、 使い手がどのようなデータが存在しているかを理解しやすくする仕組みを提供しています。 例に挙げる以下のような公共のサービスであっても、元のデータに対するタイトルや内容、 説明といったデータが含まれていることがわかります。
- 検索エンジンの検索結果(タイトル、URL、本文など)
- オークションの商品(商品名、写真、現在価格、商品説明など)
- 動画サイトの動画(タイトル、サムネイル、再生数など)
このように大量のデータを扱うための代表的な手段としてMVCがあります。 MVCとはプログラムをModel、View、Controlerと3つの要素に分割する方式です。 それぞれの役割は以下の通りです。
- Model: データへのアクセス(いわゆるCRUD処理)
- View: 画面への表示(ユーザインタフェース、ユーザエクスペリエンス)
- Controler: ユーザーからの入出力制御
PyQtでのMVC
PyQtのMVCではModelを扱うクラスとViewを扱うクラスをそれぞれ用意します。 Controlerに該当する明確なクラスは無く、Viewを扱うクラスでユーザーからのイベントを受け、 Modelを扱うクラスを制御します。Modelを扱うクラスは、Viewを扱うクラスに対し、データの変更を伝えることで、Viewを扱うクラスが表示の制御をします。またModelを扱うクラスはViewを扱うクラスを介さずに直接アクセスすることもあります。
以上から、Viewはフロントエンドで、Modelはバックエンドという理解で良さそうです。
Viewを扱うクラス
Viewを扱う基底クラスはQAbstractItemViewクラスで、 一般的にはこれを継承したQListView、QTableView、QTreeViewといった具象クラスを使います(継承も可能です)。
Modelを扱うクラス
Modelを扱う基底クラスであるQAbstractItemModelを継承するか、 あらかじめ用意されたQStandardItemModelや"Q*Model"のような名前のクラスを使います(継承も可能です)。
QAbastractItemModelからクラスを設計する場合、次の5つの最低限必要なメソッドを実装する必要があります。
index()
: 何行何列目(必要であれば親ノードから何行目)といった情報をQModelIndexクラスのインスタンスとして返す(親クラスから継承したcreateIndex()
をそのまま使うことが多い)parent()
: 親ノードを表現したQModelIndexクラスのインスタンスrowCount()
: 全体で何行あるか(現在の階層に限る)columnCount()
: 全体で何列あるかdata()
: データの内容(修飾情報も含まれる)
サンプル
QTableViewとQAbstractItemModelを使い、Pythonの2次元リストの内容をそのまま表示します。
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class Model(QAbstractItemModel): def __init__(self, parent=None): super(Model, self).__init__(parent) self.items = [ ['たぬき','そば','温'], ['きつね','うどん','温'], ['月見','うどん','冷'], ['天ぷら','そば','温'], ] def index(self, row, column, parent=QModelIndex()): return self.createIndex(row, column, None) def parent(self, child): return QModelIndex() def rowCount(self, parent=QModelIndex()): return len(self.items) def columnCount(self, parent=QModelIndex()): if self.items: return max([len(item) for item in self.items]) return 0 def data(self, index, role=Qt.DisplayRole): if role == Qt.DisplayRole: try: return self.items[index.row()][index.column()] except: return None return class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) view = QTableView(self) model = Model(self) view.setModel(model) self.setCentralWidget(view) def main(): app = QApplication(sys.argv) w = MainWindow() w.show() w.raise_() app.exec_() if __name__ == '__main__': main()