Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Why does expanding a row call QAbstractItemModel's data DisplayRole twice in QTreeView



  • If my tree is :
    +A
    ++B
    ++C

    When I expand row A, Qt calls data(DisplayRole) for its children twice.
    1 0 C
    0 0 B
    0 0 A
    0 0 B
    1 0 C

    Is there anyway to make Qt only check the children once?
    1 0 C
    0 0 B
    0 0 A

    Similarly, is there any way to prevent Qt from running data(DisplayRole) on the visible rows whenever the window loses/gains focus?

    from PySide2 import QtGui, QtCore, QtUiTools, QtWidgets
    
    
    class Node(object):
        def __init__(self, name="", parent=None):
            self._parent = parent
            self._name = name
            self._children = []
    
        def children(self):
            return self._children
    
        def hasChildren(self):
            return bool(self.children())
    
        def parent(self):
            return self._parent
    
        def name(self):
            return self._name
    
        def set_name(self, name):
            self._name = name
    
        def type_info(self):
            return 'NODE'
    
        def columnCount(self):
            return 1
    
        def child_count(self):
            return len(self._children)
    
        def add_child(self, child):
            self._children.append(child)
            child._parent = self
    
        def insert_child(self, position, child):
            if 0 <= position < child_count:
                self._children.insert(position, child)
                child._parent = self
                return True
            return False
    
        def remove_child(self, position):
            if 0 <= position < len(self._children):
                child = self._children.pop(position)
                child._parent = None
                return True
            return False
    
        def child(self, row):
            if 0 <= row < self.child_count():
                return self._children[row]
    
        def row(self):
            if self._parent is not None:
                return self._parent._children.index(self)
            return -1
    
        def find_child_by_name(self, name):
            for child in self._children:
                if child.name() == name:
                    return child
            return None
    
        def log(self, tab_level=-1):
            output = ''
            tab_level += 1
    
            for i in range(tab_level):
                output += '\t'
    
            output += '|____' + self._name + '\n'
    
            for child in self._children:
                output += child.log(tab_level)
    
            tab_level -= 1
    
            return output
    
        def __repr__(self):
            return self.log()
    class TemplateTreeModel(QtCore.QAbstractItemModel):
        def __init__(self):
            QtCore.QAbstractItemModel.__init__(self, None)
            self._root_node = Node()
    
        def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
            if role == QtCore.Qt.DisplayRole:
                if section == 0:
                    return 'Templates'
                else:
                    return 'Type'
    
        def index(self, row, column, parent):
            if not self.hasIndex(row, column, parent):
                return QtCore.QModelIndex()
            node = parent.internalPointer() if parent.isValid() else self._root_node
            if node.children:
                return self.createIndex(row, column, node.child(row))
            else:
                return QtCore.QModelIndex()
    
        def parent(self, child):
            if not child.isValid():
                return QtCore.QModelIndex()
            node = child.internalPointer()
            if node.row() >= 0:
                return self.createIndex(node.row(), 0, node.parent())
            return QtCore.QModelIndex()
    
        def rowCount(self, parent=QtCore.QModelIndex()):
            node = parent.internalPointer() if parent.isValid() else self._root_node
            return node.child_count()
    
        def columnCount(self, parent=QtCore.QModelIndex()):
            return 1
    
        def hasChildren(self, parent= QtCore.QModelIndex()):
            node = parent.internalPointer() if parent.isValid() else self._root_node
            return node.hasChildren()
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
            if index.isValid():
                if role == QtCore.Qt.DisplayRole:
                    node = index.internalPointer()
                    print index.row(), index.column(), node.name()
                    return node.name()
    
        def setData(self, index, value, role=QtCore.Qt.EditRole):
            if role in (QtCore.Qt.EditRole,):
                node = index.internalPointer()
                node.set_name(value)
                self.dataChanged.emit(index, index)
                return True
            return False
    
        def indexFromItem(self, it):
            root_index = QtCore.QModelIndex()
            if isinstance(it, Node):
                parents = []
                while it is not self._root_node:
                    parents.append(it)
                    it = it.parent()
                root = self._root_node
                for parent in reversed(parents):
                    root = root.find_child_by_name(parent.name())
                    root_index =self.index(root.row(), 0, root_index)
            return root_index
    
        def item_from_path(self, path, sep):
            depth = path.split(sep)
            root = self._root_node
            for d in depth:
                root = root.find_child_by_name(d)
                if root is None: return None
            return root
    
        def appendRow(self, item, parent=None):
            self.appendRows([item], parent)
    
        def appendRows(self, items, parent=None):
            if isinstance(items, list):
                ix = self.indexFromItem(parent)
                self.insertRows(self.rowCount(ix), items, parent)
    
        def insertRows(self, position, items, parent=None):
            parent_index = self.indexFromItem(parent)
            self.beginInsertRows(parent_index, position, position + len(items) - 1)
            if parent is None:
                parent = self._root_node
            for item in items:
                parent.add_child(item)
            self.endInsertRows()
    
    app = QtWidgets.QApplication.instance()
    if not app:
        app = QtGui.QApplication(sys.argv)
    w = QtWidgets.QTreeView()
    model = TemplateTreeModel()
    w.setModel(model)
    model.appendRow(Node("A"))
    
    structure_list = [['A', ['B', 'C']]]
    for root, dirs in structure_list:
        depth = root.split(os.sep)
        if not root.startswith("A"):
            root = "A" + os.sep + root
        it = model.item_from_path(root, os.sep)
        model.appendRows([Node(_dir) for _dir in dirs], it)
    w.show()
    
    
    


  • For what I can see it gets called once for every children and once for every item that was moved as a result of the expansion.
    It also gets called every time you hover over stuff.
    The short answer is: data is needed for painting, it will be called A LOT, make it as efficient as possible.
    Qt6 decreased the number of calls with multiData but they are still a lot


Log in to reply