Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

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

    General and Desktop
    2
    2
    38
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • P
      Paul E last edited by Paul E

      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()
      
      
      
      1 Reply Last reply Reply Quote 0
      • VRonin
        VRonin last edited by VRonin

        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

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        1 Reply Last reply Reply Quote 2
        • First post
          Last post