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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 の実行例
参考サイト
- bitWalk's: PySide6: チェックボックス付きの QTreeView [2022-03-24]
- bitWalk's: PySide6: チェックボックス付きの QTableView [2022-09-01]
- QTableView — Qt for Python
- QAbstractTableModel - Qt for Python
- QStyledItemDelegate - Qt for Python
- QProxyStyle - Qt for Python

にほんブログ村
#オープンソース

0 件のコメント:
コメントを投稿