WhiteAtelier Archives
Another side of life, Behind the real.
2020-02-27 13:22:42 - Category : PySide
【PySide2】QTableViewとModelの作り方 - その1
簡単に使えるQTableWidgetに比べて、モデルを構築する必要のあるQTableView、大変そうで苦手だったのですが、最近なんとなく使い方に慣れてきた気がするので、一度記事にまとめてみます。今回は読み取りのみの非常にシンプルなQTableViewとQAbstractTableModelの扱い方についてです。

以下のコードをすべてまとめた最終のコードは、このページの一番下に掲載しています。

まずはテスト用のウィンドウを作る

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にあたる値を頂戴」ということで呼ばれます。

roleQt.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からデータを編集できるように改造してみましょう。