はじめに
PyQt6 を使っていて、少しハマったことがあったのでメモを残しておく。
概要は以下。
- ResizeToContents を設定している QHeaderView を使った QTableView で、アイテム数の少ないモデルに切り替えた際、Out of range を起こす。
もしかしたら私の使い方がおかしいのかもしれない……。
確認環境
OS: Windows 11 Home (22H2)
Shell: Git Bash (git version 2.41.0.windows.3)
Python: 3.10.6
PyQt: 6.5.2
確認用コード
少し長いかもしれない。(約120行)
# -*- coding: utf-8 -*-
import sys
from PyQt6 import QtCore, QtGui, QtWidgets
class _Model(QtCore.QAbstractTableModel):
def __init__(self, items, headers, *args, **kwargs):
super().__init__(*args, **kwargs)
self._items = items
self._headers = headers
def rowCount(self, *a, **k):
return len(self._items)
def columnCount(self, *a, **k):
return len(self._headers)
def flags(self, index):
return super().flags(index)
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
if False:
print('data', index.row(), index.column(),
role, self.roleNames().get(role, '<>'))
if index.isValid():
if role in (QtCore.Qt.ItemDataRole.DisplayRole,
QtCore.Qt.ItemDataRole.EditRole):
return self._items[index.row()][index.column()]
return None
def headerData(self, section, orientation,
role=QtCore.Qt.ItemDataRole.DisplayRole):
if orientation == QtCore.Qt.Orientation.Horizontal and \
role == QtCore.Qt.ItemDataRole.DisplayRole:
print('In headerData: ', section, 'of', len(self._headers))
try:
assert section < len(self._headers), \
'Not section(%d) < len(%d)' % (section, len(self._headers))
return self._headers[section]
except Exception as e:
print(str(e))
return '------'
return super().headerData(section, orientation, role)
class _HHeader(QtWidgets.QHeaderView):
def __init__(self, parent):
super().__init__(QtCore.Qt.Orientation.Horizontal, parent)
if True:
self.setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
class _Table(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setHorizontalHeader(_HHeader(self))
class Main(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._setupUI()
self.m1 = _Model(items=[['a', 'b', 'c'], ['a', 'b', 'c']],
headers=['#1', '#2', '#3'])
self.m2 = _Model(items=[],
headers=[])
def _setupUI(self):
self.setWindowTitle("test")
self.resize(600, 400)
menubar = self.menuBar()
filemenu = menubar.addMenu("テスト(&T)")
act = QtGui.QAction("Model #1", self)
act.triggered.connect(self.setModel1)
filemenu.addAction(act)
act = QtGui.QAction("Model #2a", self)
act.triggered.connect(self.setModel2a)
filemenu.addAction(act)
act = QtGui.QAction("Model #2b", self)
act.triggered.connect(self.setModel2b)
filemenu.addAction(act)
act = QtGui.QAction("閉じる(&X) ", self)
act.setShortcut("Ctrl+Q")
act.setStatusTip("ウィンドウを閉じる")
act.triggered.connect(self.close)
filemenu.addAction(act)
self.tv = _Table(self)
self.setCentralWidget(self.tv)
def setModel1(self):
print('setModel1')
self.tv.setModel(self.m1)
def setModel2a(self):
print('setModel2a')
self.tv.setModel(self.m2)
def setModel2b(self):
print('setModel2b')
self.tv.setModel(None)
self.tv.setModel(self.m2)
if __name__ == '__main__':
q = QtWidgets.QApplication(sys.argv)
m = Main()
m.show()
q.exec()
動作確認
1. 上記 test.py
を実行する。
$ python test.py
2. ウィンドウが立ち上がったら、メニューの [テスト] から [Model #1] を選ぶ。
⇒縦2,横3のテーブルが表示される。
3. メニューの [テスト] から [Model #2a]を選ぶ。
⇒エラーが発生。以下のようなメッセージが表示されるハズ。
setModel2a
In headerData: 0 of 0
Not section(0) < len(0)
In headerData: 1 of 0
Not section(1) < len(0)
In headerData: 2 of 0
Not section(2) < len(0)
アイテムがあるモデルから、アイテムの無いモデルへ切り替えると、エラーが発生する様子。
これを回避するには、いったんモデルを None にする必要があるみたい。
4. もう一度メニューの [テスト] から [Model #1] を選ぶ。
⇒縦2,横3のテーブルが表示される。
5. メニューの [テスト] から [Model #2b]を選ぶ。
⇒内部的には、いったん None を設定してから空のモデルに設定しているので、今度はエラーは発生しない……ハズ。
終わりに
ちなみに、別の環境(PyQt6 6.2.3 on Ubuntu 22.04)でも同様の動作をする。なので、私の使い方が間違っているのかもしれない。なにか勘違いをしているのかも? もしくは、いったん None を入れるのが正しいお作法……なのかもしれない?