bravo's blog

多分ググっても出てこないようなプログラミング記事を目指します!

PyQtでのMVC その1 (データ表示)

大量のデータをまとめるために

多くのデータを扱うアプリケーションでは、 使い手がどのようなデータが存在しているかを理解しやすくする仕組みを提供しています。 例に挙げる以下のような公共のサービスであっても、元のデータに対するタイトルや内容、 説明といったデータが含まれていることがわかります。

  • 検索エンジンの検索結果(タイトル、URL、本文など)
  • オークションの商品(商品名、写真、現在価格、商品説明など)
  • 動画サイトの動画(タイトル、サムネイル、再生数など)

このように大量のデータを扱うための代表的な手段としてMVCがあります。 MVCとはプログラムをModel、View、Controlerと3つの要素に分割する方式です。 それぞれの役割は以下の通りです。

PyQtでのMVC

PyQtMVCでは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次元リストの内容をそのまま表示します。

f:id:bravo:20160105024657p:plain

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

PR