以下のコードをすべてまとめた最終のコードは、このページの一番下に掲載しています。
まずはテスト用のウィンドウを作る
QTableViewの挙動を確認するために、QTableViewを含んだウィンドウを表示するプログラムを書きます
このスクリプトを実行すると、下画像のようなウィンドウが立ち上がります。真ん中にQTableViewがありますが、モデルを設定していないため、何も表示されません。
データクラスの用意
次に、表示させたいデータのデータ構造を用意します。今回はサンプルコードのため、ここで特別に作成しますが、実際は、文字列リストだったり、dictだったり、すでに何らかの形で存在しているでしょう。
import dataclasses
from typing import List
@dataclasses.dataclass
class CustomData:
name: str
age: int
country: str
def toList(self) -> list:
return [self.name, self.age, self.country]
@classmethod
def toHeaderList(cls) -> List[str]:
return ["NAME", "AGE", "COUNTRY"]
非常に簡単なデータ構造です。Python3.7からdataclassesが使用できるようになりましたが、今回のようなケースにぴったりです。
dataclassesの公式ドキュメントはdataclasses --- データクラス — Python 3.8.2 ドキュメントです。
toList()
関数とtoHeaderList()
関数は、後々データ取得の時にリストの形でほしいので実装しています。
モデルの構築
必要なモジュールのimport
QTableViewを目的としたモデルクラスは、QAbstractItemModel
か、それを継承したQAbstractTableModel
から選ぶことになります。今回はQAbstractTableModel
を使用します。
from PySide2.QtCore import (
Qt,
QModelIndex,
QAbstractTableModel
)
モデルクラスの作成
モデルクラスを作ります。モデルクラスはQAbstractTableModel
を継承して作ります。
また、作ったモデルクラスには以下5つの関数が必ず必要です。
__init__(self, *)
data(self, index, role)
rowCount(self, parent=QModelIndex())
columnCount(self, parent=QModelIndex())
headerData(self, section, orientation, role)
では、まずひな形のクラスを作成しましょう
class SimpleTableModel(QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
def data(self, index: QModelIndex, role: int) -> Any:
pass
def rowCount(self, parent=QModelIndex()) -> int:
pass
def columnCount(self, parent=QModelIndex()) -> int:
pass
def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole):
pass
__init__(self, *)
コンストラクタです。必ず基底クラスのコンストラクタを呼び出しましょう。また、ここではテストとして、表示用のデータを用意しています。
def __init__(self, parent=None):
super().__init__(parent)
self.custom_data: List[CustomData] = [
CustomData(name="Taro", age=24, country="Japan"),
CustomData(name="Jiro", age=20, country="Japan"),
CustomData(name="David", age=32, country="USA"),
CustomData(name="Wattson", age=15, country="US")
]
data(self, index, role)
とても大事な関数です。QTableViewから内部的に呼ばれるとき、「index
で指定されたセルのrole
にあたる値を頂戴」ということで呼ばれます。
role
がQt.DisplayRole
の時、そのセルに対する文字列を要求されているので、データを返してあげましょう。この時、セルの行列番号の指定は、index.row()
とindex.column()
で取得できます。
def data(self, index: QModelIndex, role: int) -> Any:
if role == Qt.DisplayRole:
# この時IndexErrorが発生するときは、rowCount()かcolumnCount()が間違っている
# ので、try-exceptでIndexErrorをキャッチするのは、私は良くないと思う
# ※存在しないことを仕様とするなら、ちゃんとエラーチェックしてね
return self.custom_data[index.row()].toList()[index.column()]
rowCount(self, parent=QModelIndex())
行カウントです。今回はデータの個数と一致します。
def rowCount(self, parent=QModelIndex()) -> int:
return len(self.custom_data)
columnCount(self, parent=QModelIndex())
列カウントです。今回はCustomDataの中のデータ個数に一致します。ヘッダリストを取得して、その長さとすることにしました。
def columnCount(self, parent=QModelIndex()) -> int:
return len(CustomData.toHeaderList())
headerData(self, section, orientation, role)
ヘッダーに関するdata関数です。考え方はdata()
と同じです。QTableViewが内部的に呼ぶとき、「このテーブルのorientation
(垂直方向か水平方向か)のsection
番目のヘッダのrole
にあたる値を頂戴」ということで呼ばれます。
今回はrole == Qt.DisplayRole
の時のみ書きます。また、水平方向のヘッダは表示しますが、垂直方向のヘッダは表示しない(空文字)ことにします。
def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole):
if role == Qt.DisplayRole:
# The QTableView wants a header text
if orientation == Qt.Horizontal:
# BE CAREFUL about IndexError
return CustomData.toHeaderList()[section]
return "" # There is no vertical header
完成
これでモデルクラスができました。あとはQTableViewに、作ったモデルをsetModel
するだけです。
class TestWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.root_widget: QWidget = QWidget()
self.layout: QVBoxLayout = QVBoxLayout()
self.table: QTableView = QTableView()
self.layout.addWidget(self.table)
self.root_widget.setLayout(self.layout)
self.table.setModel(SimpleTableModel()) # ココでモデルをセット!
self.setCentralWidget(self.root_widget)
self.setWindowTitle('Test of QTableView')
テストコードを実行すると…
めでたく、データが入ったテーブルが表示されるようになりましたね!
全コード
次回
ただこのテーブルは、一度表示してしまうとデータの更新もできないし、QTableViewから編集もできません。次はQTableViewからデータを編集できるように改造してみましょう。