I have made a few different applications using TreeViews
and AbstractItemModels
but I came across something I do not understand. I realized that the view was calling the model's data
function asking for a Size
for the items as the role was SizeHintRole (see Qt Documentation). In all of the other cases, I don't ever recall needing to worry about returning a size
for the data
function. In the code below, I erroneously expected that data
would just be looking for the items in the list, and if you uncomment the first line, the view displays nothing because in fact the view is asking for the sizeHint
of the indices.
My question is, what circumstances require this? I have never needed to provide a sizeHint
before and I don't understand when it is required and when it is not.
window when the first line is commented
window when the first line is enabled, thus ignore the sizeHint request
import sys
from PyQt5 import QtCore, QtWidgets
class TreeList:
def __init__(self):
self._items = list()
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, root, parent=None):
super().__init__(parent)
self.root = root
def index(self, row: int, column: int, parent: QtCore.QModelIndex = ...) -> QtCore.QModelIndex:
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
pointer = self.root # type: TreeList
child = pointer._items[row]
else:
child = None
return self.createIndex(row, column, child)
def addItem(self, layer):
row = len(self.root._items)
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.root._items.append(layer)
self.endInsertRows()
def parent(self, child: QtCore.QModelIndex) -> QtCore.QModelIndex:
return QtCore.QModelIndex()
def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
if parent.isValid():
return 0
else:
return len(self.root._items)
def columnCount(self, parent: QtCore.QModelIndex = ...) -> int:
return 1
def hasChildren(self, parent: QtCore.QModelIndex = ...) -> bool:
return False if parent.isValid() else True
def data(self, index: QtCore.QModelIndex, role: int = ...):
# return self.root._items[index.row()]
if role == QtCore.Qt.SizeHintRole:
print(index.isValid(), index.row(), index.column())
return QtCore.QSize(100, 20)
else:
return self.root._items[index.row()]
def flags(self, index: QtCore.QModelIndex):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
class StackTreeView(QtWidgets.QTreeView):
def __init__(self, data=None, parent=None):
super().__init__(parent)
self.setModel(TreeModel(data))
def addItem(self, layer):
self.model().addItem(layer)
def test():
app = QtWidgets.QApplication(sys.argv)
data = TreeList()
data._items = ['one', 'two']
sys.excepthook = sys.__excepthook__
tree_view = StackTreeView(data)
tree_view.show()
tree_view.addItem('three')
tree_view.addItem('four')
sys.exit(app.exec_())
if __name__ == '__main__':
test()
Returning self.root._items[index.row()]
by default for all roles makes no sense at all. You should always explicitly check the role and only ever return a value that is appropriate for that specific role. If you don't have data specific to a role, you should return None
in PyQt (which is equivalent to an invalid QVariant
in C++).
See the Model Subclassing Reference for an outline of the basic requirements.