コンテンツにスキップ

第 7 章: モデル/ビューアーキテクチャ

これまでの章で使ってきたQListWidgetQTableWidgetのようなウィジェットは、手軽に項目を追加できて便利です。 しかし、これらのウィジェットは、表示するデータ(アイテム)そのものを内部に保持しています。 数百、数千、あるいは数百万といった大量のデータを扱おうとすると、すべてのデータをウィジェットにコピーするため、膨大なメモリを消費し、アプリケーションのパフォーマンスが著しく低下します。

この問題を解決するのが、Qt のモデル/ビューアーキテクチャです。

なぜモデル/ビューが必要なのか?

モデル/ビューアーキテクチャの核心は、データとその見た目を完全に分離することにあります。

  • モデル (Model): データそのものを管理します。データの構造(リスト、テーブルなど)や内容に責任を持ちますが、それがどのように画面に表示されるかは関知しません。
  • ビュー (View): データの見た目を担当します。QListView, QTableView, QTreeViewなどがあり、モデルに問い合わせて必要なデータだけを取得し、画面に表示します。
  • デリゲート (Delegate): 個々のアイテムの描画方法や編集方法を制御します。デフォルトのデリゲートで十分な場合も多いですが、カスタマイズすることでチェックボックスやプログレスバーなどをアイテムとして表示することも可能です。(この章では主にデフォルトのデリゲートを使用します)

この分離により、ビューは画面に見えている部分のデータだけをモデルに要求するため、たとえモデルが 100 万件のデータを持っていても、メモリ消費は最小限に抑えられ、高速な動作が可能になります。

簡単なモデル/ビュー: QStringListModel

まずは、最もシンプルなモデル/ビューの実装を見てみましょう。 Python のリスト(文字列)をQListViewで表示する例です。

# simple_model_view.py
import sys
from PySide6.QtCore import QStringListModel
from PySide6.QtWidgets import QApplication, QMainWindow, QListView

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QStringListModelの例")
        self.resize(300, 200)

        # 1. データソースとなるPythonのリスト
        fruits = ["Apple", "Banana", "Orange", "Grape", "Strawberry"]

        # 2. モデルの作成
        # QStringListModelは、文字列のリストを扱うための簡単なモデルです。
        self.model = QStringListModel(fruits)

        # 3. ビューの作成
        self.list_view = QListView()

        # 4. ビューにモデルを設定
        self.list_view.setModel(self.model)

        # 5. メインウィンドウにビューを設置
        self.setCentralWidget(self.list_view)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_win = MainWindow()
    main_win.show()
    sys.exit(app.exec())

QListWidgetと似ていますが、データ(fruitsリスト)とビュー(QListView)がQStringListModelを介して接続されている点が異なります。

カスタムモデルの作成: QAbstractTableModel

QStringListModelは便利ですが、文字列のリストしか扱えません。独自のデータ構造(オブジェクトのリスト、2 次元配列など)をテーブル形式で表示したい場合は、QAbstractTableModelを継承して独自のモデルクラスを作成する必要があります。

カスタムモデルを実装する際、最低限オーバーライド(再実装)が必要なメソッドは以下の 3 つです。

  1. rowCount(): モデルの行数を返す。
  2. columnCount(): モデルの列数を返す。
  3. data(index, role): 指定されたインデックス(index)と役割(role)に対応するデータを返す。これがモデル/ビューの心臓部です。

実践:2 次元配列をテーブルで表示する

Python の 2 次元リストをデータソースとして、QTableViewに表示するカスタムモデルを作成します。

# custom_table_model.py
import sys
from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView

# QAbstractTableModelを継承してカスタムモデルを作成
class CustomTableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._headers = ["名前", "年齢", "職業"]

    # (必須) 行数を返す
    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    # (必須) 列数を返す
    def columnCount(self, parent=QModelIndex()):
        return len(self._data[0]) if self.rowCount() > 0 else 0

    # (必須) 指定されたインデックスのデータを返す
    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if not index.isValid():
            return None

        # DisplayRoleは、アイテムをテキストとして表示する役割
        if role == Qt.ItemDataRole.DisplayRole:
            return str(self._data[index.row()][index.column()])

        return None

    # (任意) ヘッダー情報を返す
    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._headers[section]
            if orientation == Qt.Orientation.Vertical:
                return str(section + 1) # 行番号
        return None

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("カスタムテーブルモデルの例")
        self.resize(400, 300)

        # データソース (2次元リスト)
        table_data = [
            ["山田 太郎", 32, "エンジニア"],
            ["佐藤 花子", 28, "デザイナー"],
            ["鈴木 一郎", 45, "マネージャー"],
            ["田中 次郎", 25, "プログラマー"]
        ]

        self.table_view = QTableView()
        self.model = CustomTableModel(table_data)
        self.table_view.setModel(self.model)

        self.setCentralWidget(self.table_view)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_win = MainWindow()
    main_win.show()
    sys.exit(app.exec())

コードの解説

  • CustomTableModelクラスが、QAbstractTableModelを継承して作られています。
  • __init__でデータソース(_data)を受け取ります。
  • rowCountcolumnCountは、データソースの次元を返すだけの単純なものです。
  • dataメソッドが最も重要です。ビューは、各セルを描画するためにこのメソッドを呼び出します。roleをチェックし、DisplayRole(表示用データ要求)の場合に、_dataから対応する行・列のデータを返しています。
  • headerDataメソッドは、テーブルのヘッダー(列名や行番号)を表示するために実装します。これもDisplayRoleのときに適切な文字列を返します。

このアーキテクチャに慣れるには少し時間がかかるかもしれませんが、一度マスターすれば、非常に大規模で複雑なデータを扱うアプリケーションでも、高いパフォーマンスを維持したまま開発できるようになります。