Why does expanding a row call QAbstractItemModel's data DisplayRole twice in QTreeView
Unsolved
General and Desktop
-
If my tree is :
+A
++B
++CWhen 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 CIs there anyway to make Qt only check the children once?
1 0 C
0 0 B
0 0 ASimilarly, 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