2022-10-01

PySide6: チェックボックス付きの QTableView (2)

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。配布ライセンスは LGPL で公開されています。最新のバージョンは Qt6 に対応した PySide6(記事執筆時点で 6.3.2)です。

文字列とチェックボックスをマトリックス状に配置したウィジェットが必要になり、出来合いの QStandardItemModel を使ってチェックボックスを QTableView のセルに表示するサンプルを作りました [2]。間に合せで作ったようなものでも使えなくはないのですが、いまいち納得できませんでした。

欲しいのは、極めてたくさんのチェックボックスが並んだチェックシートみたいなものなのです。チェックボックスのチェック状態はチェック時ではなくて、あるタイミングでまとめて確認できれば良いというものです。

あれこれ試して行き着いたやりかたをサンプルで紹介します。このサンプルの解説をしようとするとそれだけで記事が完成しなくなるので、しません。読み手に判るような説明がすぐにできないということは、まだ自分でも理解しきれていない部分が多々ある証なのです。😅

下記の OS 環境で動作確認をしました。

Fedora Linux 37 preview x86_64
python3.10 python3.10-3.10.7-1.fc37.x86_64
PySide6 6.3.2
qt_tableview_checkbox_1.py
# Reference:
# https://stackoverflow.com/questions/62414356/add-a-checkbox-to-text-in-a-qtableview-cell-using-delegate
import sys
from typing import Any
from PySide6.QtCore import (
Qt,
QAbstractTableModel,
QModelIndex,
QPersistentModelIndex,
)
from PySide6.QtWidgets import (
QApplication,
QHeaderView,
QMainWindow,
QProxyStyle,
QStyledItemDelegate,
QTableView,
)
class MyContents:
def __init__(self, list_labels: list, header_labels: list):
self.list_labels = list_labels
self.header_labels = header_labels
def getRows(self):
return len(self.list_labels[0])
def getCols(self):
return len(self.header_labels)
def getData(self, row: int, col: int):
if col < self.getCheckColStart():
return self.list_labels[col][row]
else:
return None
def getColumnHeader(self, index: int):
return self.header_labels[index]
def getRowIndex(self, index: int):
return str(index + 1)
def getCheckColStart(self):
return len(self.list_labels)
class ProxyStyleCheckBoxCenter(QProxyStyle):
def subElementRect(self, element, opt, widget=None):
if element == self.SE_ItemViewItemCheckIndicator:
rect = super().subElementRect(element, opt, widget)
rect.moveCenter(opt.rect.center())
return rect
return super().subElementRect(element, opt, widget)
class CheckBoxDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index: QModelIndex):
value = index.data(Qt.CheckStateRole)
if value is None:
model = index.model()
model.setData(index, Qt.Unchecked, Qt.CheckStateRole)
super().initStyleOption(option, index)
class MyTableModel(QAbstractTableModel):
def __init__(self, data: MyContents):
super(MyTableModel, self).__init__()
self._data = data
self.check_states = dict()
def rowCount(self, index: QModelIndex):
return self._data.getRows()
def columnCount(self, index: QModelIndex):
return self._data.getCols()
def data(self, index: QModelIndex, role: Qt.ItemDataRole):
if role == Qt.DisplayRole:
row = index.row()
column = index.column()
value = self._data.getData(row, column)
return value
if role == Qt.CheckStateRole:
value = self.check_states.get(QPersistentModelIndex(index))
if value is not None:
return value
def setData(self, index: QModelIndex, value: Any, role: Qt.ItemDataRole = Qt.EditRole):
if role == Qt.CheckStateRole:
self.check_states[QPersistentModelIndex(index)] = value
self.dataChanged.emit(index, index, (role,))
return True
return False
def flags(self, index: QModelIndex):
return (
Qt.ItemIsEnabled
| Qt.ItemIsSelectable
| Qt.ItemIsUserCheckable
)
def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self._data.getColumnHeader(section)
elif orientation == Qt.Vertical:
return self._data.getRowIndex(section)
class Example(QMainWindow):
# Sample Data preparation
num_data = 1000
names = ['TEST' + str(x + 1).zfill(5) for x in range(num_data)]
list_label_names = [names]
num_check = 10
col_labels = ['NAME'] + ['check(%d)' % (x + 1) for x in range(num_check)]
def __init__(self):
super().__init__()
self.init_ui()
self.setWindowTitle('TableView')
self.resize(800, 400)
def init_ui(self):
table = QTableView()
table.setStyle(ProxyStyleCheckBoxCenter())
table.setWordWrap(False)
table.setAlternatingRowColors(True)
table.verticalHeader().setDefaultAlignment(Qt.AlignRight)
table.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeToContents
)
self.setCentralWidget(table)
# delegate custom
delegate = CheckBoxDelegate(table)
for col in range(len(self.list_label_names), len(self.col_labels)):
table.setItemDelegateForColumn(col, delegate)
# set table model
contents = MyContents(self.list_label_names, self.col_labels)
model = MyTableModel(contents)
table.setModel(model)
# _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_
# WRITE/READ TEST for CheckBox status
# _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_
# set default status
for row in range(contents.getRows()):
for col in range(contents.getCheckColStart(), contents.getCols()):
index = model.index(row, col)
model.setData(index, 2, role=Qt.CheckStateRole)
# get check status
for row in range(contents.getRows()):
for col in range(contents.getCheckColStart(), contents.getCols()):
index = model.index(row, col)
value = model.data(index, role=Qt.CheckStateRole)
print(row, col, value)
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
qt_tableview_checkbox_1.py の実行例

参考サイト

  1. bitWalk's: PySide6: チェックボックス付きの QTreeView [2022-03-24]
  2. bitWalk's: PySide6: チェックボックス付きの QTableView [2022-09-01]
  3. QTableView — Qt for Python
  4. QAbstractTableModel - Qt for Python
  5. QStyledItemDelegate - Qt for Python
  6. QProxyStyle - Qt for Python

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



0 件のコメント: