第 7 章: モデル/ビューアーキテクチャ
これまでの章で使ってきたQListWidgetやQTableWidgetのようなウィジェットは、手軽に項目を追加できて便利です。
しかし、これらのウィジェットは、表示するデータ(アイテム)そのものを内部に保持しています。
数百、数千、あるいは数百万といった大量のデータを扱おうとすると、すべてのデータをウィジェットにコピーするため、膨大なメモリを消費し、アプリケーションのパフォーマンスが著しく低下します。
この問題を解決するのが、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 つです。
rowCount(): モデルの行数を返す。columnCount(): モデルの列数を返す。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)を受け取ります。rowCountとcolumnCountは、データソースの次元を返すだけの単純なものです。dataメソッドが最も重要です。ビューは、各セルを描画するためにこのメソッドを呼び出します。roleをチェックし、DisplayRole(表示用データ要求)の場合に、_dataから対応する行・列のデータを返しています。headerDataメソッドは、テーブルのヘッダー(列名や行番号)を表示するために実装します。これもDisplayRoleのときに適切な文字列を返します。
このアーキテクチャに慣れるには少し時間がかかるかもしれませんが、一度マスターすれば、非常に大規模で複雑なデータを扱うアプリケーションでも、高いパフォーマンスを維持したまま開発できるようになります。