Search code examples
pythonpyqtqtreeviewqabstractitemmodel

what triggers a QTreeView to ask for a SizeHintRole in the QAbstractItemModel.data() function


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

enter image description here

window when the first line is enabled, thus ignore the sizeHint request

enter image description here

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()

Solution

  • 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.