Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QAbstractItemModel.dataChanged() for one row and two columns updates entire view
QtWS25 Last Chance

QAbstractItemModel.dataChanged() for one row and two columns updates entire view

Scheduled Pinned Locked Moved Unsolved General and Desktop
6 Posts 2 Posters 5.7k Views
  • 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.
  • A Offline
    A Offline
    altendky
    wrote on last edited by
    #1

    Re: How do I debug excessive QAbstractItemModel::data() callbacks and general extreme inefficiency (PyQt5)

    I have looked back at my QTreeView performance issues yet again and I'm seeing that if call dataChanged() for a single location it works fine and updates only that single location but as soon as I specify two columns it updates my entire view. I can individually update either one cell or the other or both in separate calls just fine.

    Is this expected? If not, are there any common causes you might suggest I look into?

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      How are you determining the range you pass to the signal ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      0
      • A Offline
        A Offline
        altendky
        wrote on last edited by
        #3

        When a message is received on the bus the corresponding message node is found and the columns are hardcoded (first snippet). The nodes (not model indexes) and columns are passed to changed() which converts them to model indexes including the specified columns. As the code is now (on a debugging branch) it updates only for a single message (id 0x0CFFAB30) and a single column (Columns.indexes.value is 3 (out of 6 columns total)). Putting the +1 with the second column (as commented) causes the entire view to update. Add another +1 to the first column so they are again equal and it updates a single cell in column 4.

        https://github.com/altendky/st/blob/b69467b613a4a3bc14d3216c355ed2d82983bb47/epyqlib/txrx.py#L291

        if msg.arbitration_id == 0x0CFFAB30:
            for column in [Columns.indexes.value]:#, Columns.indexes.dt, Columns.indexes.count]:
                self.changed.emit(
                    message, column,
                    message, column, # column+1 updates entire view
                    [Qt.DisplayRole])
                # for child in message.children:
                #     self.changed.emit(
                #         child, column,
                #         child, column,
                #         [Qt.DisplayRole])
        

        https://github.com/altendky/st/blob/b69467b613a4a3bc14d3216c355ed2d82983bb47/epyqlib/pyqabstractitemmodel.py#L205

        @pyqtSlot(TreeNode, int, TreeNode, int, list)
        def changed(self, start_node, start_column, end_node, end_column, roles):
            start_index = self.index_from_node(start_node)
            start_row = start_index.row()
            start_parent = start_index.parent()
            start_index = self.index(start_row, start_column, start_parent)
        
            if end_node is start_node:
                end_row = start_row
                end_parent = start_parent
            else:
                end_index = self.index_from_node(end_node)
                end_row = end_index.row()
                end_parent = end_index.parent()
        
            end_index = self.index(end_row, end_column, end_parent)
        
            self.dataChanged.emit(start_index, end_index, roles)
        
        1 Reply Last reply
        0
        • A Offline
          A Offline
          altendky
          wrote on last edited by
          #4

          I won't claim this is an SSCCE since I don't think I can call nearly 500 lines simple, but I think it is nearly an SCCE at least. Depends on Python3 and PyQt5 (available on PyPi). It creates a three-row/three-column model with view as well as some buttons to manually trigger changing of data and separate triggering of dataChanged() for one and two columns (only the second row in both cases).

          As in my full application the single update updates a single cell as expected but the double update updates all cells. In both the single and double column update the parents test equal and the rows and columns seem like what i would expect.

          dataChanged() emitted
          Start:r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fac9df157b8>)
          End  :r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fac9df15828>)
          Parents equal: True
          dataChanged() emitted
          Start:r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fac9df157b8>)
          End  :r1,c2 parent(<PyQt5.QtCore.QModelIndex object at 0x7fac9df15898>)
          Parents equal: True
          

          https://gist.github.com/d5e1dc68fdf7cfe490efac83b5eb2434

          #!/usr/bin/env python3
          
          #TODO: """DocString if there is one"""
          
          import string
          import functools
          import random
          import sys
          
          from PyQt5.QtCore import (Qt, QAbstractItemModel, QVariant,
                                    QModelIndex, pyqtSignal, pyqtSlot, QSize)
          from PyQt5.QtWidgets import (QWidget, QApplication, QTreeView, QMainWindow,
                                       QHBoxLayout, QVBoxLayout, QPushButton)
          
          # See file COPYING in this source tree
          __copyright__ = 'Copyright 2016, EPC Power Corp.'
          __license__ = 'GPLv2+'
          
          
          class TreeNode:
              def __init__(self,  tx=False, parent=None):
                  self.last = None
          
                  self.tx = tx
          
                  self.tree_parent = None
                  self.set_parent(parent)
                  self.children = []
          
              def set_parent(self, parent):
                  self.tree_parent = parent
                  if self.tree_parent is not None:
                      self.tree_parent.append_child(self)
          
              def append_child(self, child):
                  self.children.append(child)
                  child.tree_parent = self
          
              def child_at_row(self, row):
                  try:
                      return self.children[row]
                  except IndexError:
                      return None
          
              def row_of_child(self, child):
                  for i, item in enumerate(self.children):
                      if item == child:
                          return i
                  return -1
          
              def remove_child(self, row=None, child=None):
                  if child is None:
                      child = self.children[row]
          
                  self.children.remove(child)
          
                  return True
          
              def traverse(self, call_this, payload=None):
                  for child in self.children:
                      if len(child.children) == 0:
                          call_this(child, payload)
                      else:
                          child.traverse(child, call_this, payload)
          
              def __len__(self):
                  return len(self.children)
          
          
          unique_role = Qt.UserRole
          
          
          class PyQAbstractItemModel(QAbstractItemModel):
              root_changed = pyqtSignal(TreeNode)
          
              def __init__(self, root, checkbox_columns=None, editable_columns=None,
                           alignment=None, parent=None):
                  QAbstractItemModel.__init__(self, parent=parent)
          
                  self.root = root
                  self.checkbox_columns = checkbox_columns
                  self.editable_columns = editable_columns
          
                  if alignment is not None:
                      self.alignment = alignment
                  else:
                      self.alignment = Qt.AlignTop | Qt.AlignLeft
          
                  self.index_from_node_cache = {}
          
                  self.role_functions = {
                      Qt.DisplayRole: self.data_display,
                      unique_role: self.data_unique,
                      Qt.TextAlignmentRole: lambda index: int(self.alignment),
                      Qt.CheckStateRole: self.data_check_state,
                      Qt.EditRole: self.data_edit,
                      Qt.SizeHintRole: self.data_size_hint
                  }
          
              def headerData(self, section, orientation, role):
                  if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                      return QVariant(self.headers[section])
                  return QVariant()
          
              def data_display(self, index):
                  node = index.internalPointer()
          
                  try:
                      return node.fields[index.column()]
                  except IndexError:
                      return None
          
              def data_unique(self, index):
                  return index.internalPointer().unique()
          
              def data_check_state(self, index):
                  if self.checkbox_columns is not None:
                      if self.checkbox_columns[index.column()]:
                          node = index.internalPointer()
                          try:
                              return node.checked(index.column())
                          except AttributeError:
                              return None
          
              def data_edit(self, index):
                  node = index.internalPointer()
                  try:
                      get = node.get_human_value
                  except AttributeError:
                      value = node.fields[index.column()]
                  else:
                      try:
                          value = get()
                      except TypeError:
                          value = ''
          
                  if value is None:
                      value = ''
                  else:
                      value = str(value)
          
                  return value
          
              def data_size_hint(self, index):
                  return QSize(50, 50)
          
              def data(self, index, role):
                  if not index.isValid():
                      return None
          
                  try:
                      return self.role_functions[role](index=index)
                  except KeyError:
                      return None
          
              def flags(self, index):
                  flags = QAbstractItemModel.flags(self, index)
          
                  if not index.isValid():
                      return flags
          
                  if self.editable_columns is not None:
                      if self.editable_columns[index.column()]:
                          flags |= Qt.ItemIsEditable
          
                  if self.checkbox_columns is not None:
                      if self.checkbox_columns[index.column()]:
                          flags |= Qt.ItemIsUserCheckable
          
                  return flags
          
              def index(self, row, column, parent):
                  # TODO: commented out stuff ought to be good rather than
                  #       breaking stuff.
                  #
                  #       http://stackoverflow.com/questions/26680168/pyqt-treeview-index-error-removing-last-row
          
                  if not self.hasIndex(row, column, parent):
                      return QModelIndex()
          
                  # if not parent.isValid():
                  #     return QModelIndex()
          
                  # if row < 0 or column < 0:
                  #     return QModelIndex()
          
                  node = self.node_from_index(parent)
                  child = node.child_at_row(row)
          
                  if child is None:
                      return QModelIndex()
          
                  return self.createIndex(row, column, child)
          
              def columnCount(self, parent):
                  return len(self.headers)
          
              def rowCount(self, parent):
                  # TODO: this seems pretty particular to my present model
                  #       "the second column should NOT have the same children
                  #       as the first column in a row"
                  #       https://github.com/bgr/PyQt5_modeltest/blob/62bc86edbad065097c4835ceb4eee5fa3754f527/modeltest.py#L222
                  #
                  #       then again, the Qt example does just this
                  #       http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
                  if parent.column() > 0:
                      return 0
          
                  node = self.node_from_index(parent)
                  if node is None:
                      return 0
                  return len(node)
          
              def parent(self, child):
                  if not child.isValid():
                      return QModelIndex()
          
                  node = self.node_from_index(child)
          
                  if node is None:
                      return QModelIndex()
          
                  parent = node.tree_parent
          
                  if parent in [None, self.root]:
                      return QModelIndex()
          
                  grandparent = parent.tree_parent
                  if grandparent is None:
                      return QModelIndex()
                  row = grandparent.row_of_child(parent)
          
                  assert row != - 1
                  return self.createIndex(row, 0, parent)
          
              def node_from_index(self, index):
                  if index.isValid():
                      return index.internalPointer()
                  else:
                      return self.root
          
              def index_from_node(self, node):
                  # TODO  make up another role for identification?
                  try:
                      index = self.index_from_node_cache[node]
                  except KeyError:
                      if node is self.root:
                          index = QModelIndex()
                      else:
                          index = self.match(self.index(0, 0, QModelIndex()),
                                             unique_role,
                                             node.unique(),
                                             1,
                                             Qt.MatchRecursive)[0]
          
                          self.index_from_node_cache[node] = index
          
                  return index
          
              @pyqtSlot(TreeNode, int, TreeNode, int, list)
              def changed(self, start_node, start_column, end_node, end_column, roles):
                  start_index = self.index_from_node(start_node)
                  start_row = start_index.row()
                  start_parent = start_index.parent()
                  start_index = self.index(start_row, start_column, start_parent)
          
                  if end_node is start_node:
                      end_row = start_row
                      end_parent = start_parent
                  else:
                      end_index = self.index_from_node(end_node)
                      end_row = end_index.row()
                      end_parent = end_index.parent()
          
                  end_index = self.index(end_row, end_column, end_parent)
          
                  self.dataChanged.emit(start_index, end_index, roles)
                  print('dataChanged() emitted')
                  for name, index in [('Start', start_index), ('End', end_index)]:
                      print('{:5s}:r{},c{} parent({})'.format(name,
                                                              index.row(),
                                                              index.column(),
                                                              index.parent()))
                  print('Parents equal: {}'.format(start_index.parent()
                                                   == end_index.parent()))
          
              @pyqtSlot(TreeNode, int, int)
              def begin_insert_rows(self, parent, start_row, end_row):
                  self.beginInsertRows(self.index_from_node(parent), start_row, end_row)
          
              @pyqtSlot()
              def end_insert_rows(self):
                  self.index_from_node_cache = {}
                  self.endInsertRows()
          
              @pyqtSlot(TreeNode, int, int)
              def begin_remove_rows(self, parent, start_row, end_row):
                  self.beginRemoveRows(self.index_from_node(parent), start_row, end_row)
          
              @pyqtSlot()
              def end_remove_rows(self):
                  self.index_from_node_cache = {}
                  self.endRemoveRows()
          
              @pyqtSlot()
              def set_root(self, root):
                  self.beginResetModel()
                  self.root = root
                  self.endResetModel()
                  self.root_changed.emit(root)
          
          
          class AbstractColumns:
              def __init__(self, **kwargs):
                  for member in self._members:
                      try:
                          value = kwargs[member]
                      except KeyError:
                          value = None
                      finally:
                          setattr(self, member, value)
          
                  object.__setattr__(self, '_length', len(self.__dict__))
          
                  invalid_parameters = set(kwargs.keys()) - set(self.__dict__.keys())
                  if len(invalid_parameters):
                      raise ValueError('Invalid parameter{} passed: {}'.format(
                          's' if len(invalid_parameters) > 1 else '',
                          ', '.join(invalid_parameters)))
          
              @classmethod
              def __len__(cls):
                  return len(cls._members)
          
              @classmethod
              def indexes(cls):
                  return cls(**dict(zip(cls._members, range(len(cls._members)))))
          
              @classmethod
              def fill(cls, value):
                  return cls(**dict(zip(cls._members, [value] * len(cls._members))))
          
              def __iter__(self):
                  for i in range(len(self)):
                      yield self[i]
          
              @functools.lru_cache(maxsize=None)
              def index_from_attribute(self, index):
                  for attribute in self.__class__._members:
                      if index == getattr(self.__class__.indexes, attribute):
                          return attribute
          
                  raise IndexError('column index out of range')
          
              def __getitem__(self, index):
                  if index < 0:
                      index += len(self)
                  return getattr(self, self.index_from_attribute(index))
          
              def __setitem__(self, index, value):
                  if index < 0:
                      index += len(self)
                  return setattr(self, self.index_from_attribute(index), value)
          
              def __getattr__(self, name, value):
                  if name in self._members:
                      object.__getattr__(self, name, value)
                  else:
                      raise TypeError("Attempted to get attribute {}"
                                      .format(name))
          
              def __setattr__(self, name, value):
                  if name in self._members:
                      object.__setattr__(self, name, value)
                  else:
                      raise TypeError("Attempted to set attribute {}"
                                      .format(name))
          
          
          class Columns(AbstractColumns):
              _members = ['name', 'letter', 'number']
          
          Columns.indexes = Columns.indexes()
          
          
          class Node(TreeNode):
              def __init__(self, name, letter='a', number=0):
                  TreeNode.__init__(self)
                  self.fields = Columns(name=name, letter=letter, number=number)
          
                  self.possibilities = Columns(letter=string.ascii_letters.lower(),
                                               number=range(10))
          
              def randomize(self, _):
                  letters = set(self.possibilities.letter)
                  letters.remove(self.fields.letter)
                  self.fields.letter = random.choice(list(letters))
          
                  numbers = set(self.possibilities.number)
                  numbers.remove(self.fields.number)
                  self.fields.number = random.choice(list(numbers))
          
              def unique(self):
                  return object.__repr__(self)
          
          
          class Model(PyQAbstractItemModel):
              def __init__(self, root, parent=None):
                  PyQAbstractItemModel.__init__(self, root=root, parent=parent)
          
                  self.headers = Columns(name='Name', letter='Letter', number='Number')
          
              def data_clicked(self):
                  self.root.traverse(call_this=Node.randomize)
          
              def single_clicked(self):
                  node = self.root.children[1]
                  self.changed(node, 1, node, 1, [])
          
              def double_clicked(self):
                  node = self.root.children[1]
                  self.changed(node, 1, node, 2, [])
          
          
          def main():
              app = QApplication(sys.argv)
          
              root = Node(name='Root', letter='-', number=-1)
              a = Node(name='A', letter='a', number=1)
              b = Node(name='B', letter='b', number=2)
              c = Node(name='C', letter='c', number=3)
              root.append_child(a)
              root.append_child(b)
              root.append_child(c)
          
              model = Model(root=root)
          
              window = QWidget()
          
              vlayout = QVBoxLayout()
              window.setLayout(vlayout)
          
              tree_view = QTreeView()
              tree_view.setModel(model)
              vlayout.addWidget(tree_view)
          
              hlayout = QHBoxLayout()
          
              data = QPushButton()
              data.setText('Change Data')
              data.clicked.connect(model.data_clicked)
              hlayout.addWidget(data)
          
              single = QPushButton()
              single.setText('Update Single')
              single.clicked.connect(model.single_clicked)
              hlayout.addWidget(single)
          
              double = QPushButton()
              double.setText('Update Double')
              double.clicked.connect(model.double_clicked)
              hlayout.addWidget(double)
          
              vlayout.addLayout(hlayout)
          
              window.show()
              return app.exec_()
          
          
          if __name__ == '__main__':
              sys.exit(main())
          
          1 Reply Last reply
          0
          • A Offline
            A Offline
            altendky
            wrote on last edited by
            #5

            I added a button to trigger dataChanged() for two rows but only one column. Same result, all are updated.

            dataChanged() emitted
            Start:r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fbcb0084898>)
            End  :r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fbcb0084908>)
            Parents equal: True
            dataChanged() emitted
            Start:r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fbcb0084898>)
            End  :r1,c2 parent(<PyQt5.QtCore.QModelIndex object at 0x7fbcb0084978>)
            Parents equal: True
            dataChanged() emitted
            Start:r1,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fbcb0084898>)
            End  :r2,c1 parent(<PyQt5.QtCore.QModelIndex object at 0x7fbcb0084978>)
            Parents equal: True
            

            https://gist.github.com/7a178479d2d469efb00d4c6915adbc86

            #!/usr/bin/env python3
            
            #TODO: """DocString if there is one"""
            
            import string
            import functools
            import random
            import sys
            
            from PyQt5.QtCore import (Qt, QAbstractItemModel, QVariant,
                                      QModelIndex, pyqtSignal, pyqtSlot, QSize)
            from PyQt5.QtWidgets import (QWidget, QApplication, QTreeView, QMainWindow,
                                         QHBoxLayout, QVBoxLayout, QPushButton)
            
            # See file COPYING in this source tree
            __copyright__ = 'Copyright 2016, EPC Power Corp.'
            __license__ = 'GPLv2+'
            
            
            class TreeNode:
                def __init__(self,  tx=False, parent=None):
                    self.last = None
            
                    self.tx = tx
            
                    self.tree_parent = None
                    self.set_parent(parent)
                    self.children = []
            
                def set_parent(self, parent):
                    self.tree_parent = parent
                    if self.tree_parent is not None:
                        self.tree_parent.append_child(self)
            
                def append_child(self, child):
                    self.children.append(child)
                    child.tree_parent = self
            
                def child_at_row(self, row):
                    try:
                        return self.children[row]
                    except IndexError:
                        return None
            
                def row_of_child(self, child):
                    for i, item in enumerate(self.children):
                        if item == child:
                            return i
                    return -1
            
                def remove_child(self, row=None, child=None):
                    if child is None:
                        child = self.children[row]
            
                    self.children.remove(child)
            
                    return True
            
                def traverse(self, call_this, payload=None):
                    for child in self.children:
                        if len(child.children) == 0:
                            call_this(child, payload)
                        else:
                            child.traverse(child, call_this, payload)
            
                def __len__(self):
                    return len(self.children)
            
            
            unique_role = Qt.UserRole
            
            
            class PyQAbstractItemModel(QAbstractItemModel):
                root_changed = pyqtSignal(TreeNode)
            
                def __init__(self, root, checkbox_columns=None, editable_columns=None,
                             alignment=None, parent=None):
                    QAbstractItemModel.__init__(self, parent=parent)
            
                    self.root = root
                    self.checkbox_columns = checkbox_columns
                    self.editable_columns = editable_columns
            
                    if alignment is not None:
                        self.alignment = alignment
                    else:
                        self.alignment = Qt.AlignTop | Qt.AlignLeft
            
                    self.index_from_node_cache = {}
            
                    self.role_functions = {
                        Qt.DisplayRole: self.data_display,
                        unique_role: self.data_unique,
                        Qt.TextAlignmentRole: lambda index: int(self.alignment),
                        Qt.CheckStateRole: self.data_check_state,
                        Qt.EditRole: self.data_edit,
                        Qt.SizeHintRole: self.data_size_hint
                    }
            
                def headerData(self, section, orientation, role):
                    if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                        return QVariant(self.headers[section])
                    return QVariant()
            
                def data_display(self, index):
                    node = index.internalPointer()
            
                    try:
                        return node.fields[index.column()]
                    except IndexError:
                        return None
            
                def data_unique(self, index):
                    return index.internalPointer().unique()
            
                def data_check_state(self, index):
                    if self.checkbox_columns is not None:
                        if self.checkbox_columns[index.column()]:
                            node = index.internalPointer()
                            try:
                                return node.checked(index.column())
                            except AttributeError:
                                return None
            
                def data_edit(self, index):
                    node = index.internalPointer()
                    try:
                        get = node.get_human_value
                    except AttributeError:
                        value = node.fields[index.column()]
                    else:
                        try:
                            value = get()
                        except TypeError:
                            value = ''
            
                    if value is None:
                        value = ''
                    else:
                        value = str(value)
            
                    return value
            
                def data_size_hint(self, index):
                    return QSize(50, 50)
            
                def data(self, index, role):
                    if not index.isValid():
                        return None
            
                    try:
                        return self.role_functions[role](index=index)
                    except KeyError:
                        return None
            
                def flags(self, index):
                    flags = QAbstractItemModel.flags(self, index)
            
                    if not index.isValid():
                        return flags
            
                    if self.editable_columns is not None:
                        if self.editable_columns[index.column()]:
                            flags |= Qt.ItemIsEditable
            
                    if self.checkbox_columns is not None:
                        if self.checkbox_columns[index.column()]:
                            flags |= Qt.ItemIsUserCheckable
            
                    return flags
            
                def index(self, row, column, parent):
                    # TODO: commented out stuff ought to be good rather than
                    #       breaking stuff.
                    #
                    #       http://stackoverflow.com/questions/26680168/pyqt-treeview-index-error-removing-last-row
            
                    if not self.hasIndex(row, column, parent):
                        return QModelIndex()
            
                    # if not parent.isValid():
                    #     return QModelIndex()
            
                    # if row < 0 or column < 0:
                    #     return QModelIndex()
            
                    node = self.node_from_index(parent)
                    child = node.child_at_row(row)
            
                    if child is None:
                        return QModelIndex()
            
                    return self.createIndex(row, column, child)
            
                def columnCount(self, parent):
                    return len(self.headers)
            
                def rowCount(self, parent):
                    # TODO: this seems pretty particular to my present model
                    #       "the second column should NOT have the same children
                    #       as the first column in a row"
                    #       https://github.com/bgr/PyQt5_modeltest/blob/62bc86edbad065097c4835ceb4eee5fa3754f527/modeltest.py#L222
                    #
                    #       then again, the Qt example does just this
                    #       http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
                    if parent.column() > 0:
                        return 0
            
                    node = self.node_from_index(parent)
                    if node is None:
                        return 0
                    return len(node)
            
                def parent(self, child):
                    if not child.isValid():
                        return QModelIndex()
            
                    node = self.node_from_index(child)
            
                    if node is None:
                        return QModelIndex()
            
                    parent = node.tree_parent
            
                    if parent in [None, self.root]:
                        return QModelIndex()
            
                    grandparent = parent.tree_parent
                    if grandparent is None:
                        return QModelIndex()
                    row = grandparent.row_of_child(parent)
            
                    assert row != - 1
                    return self.createIndex(row, 0, parent)
            
                def node_from_index(self, index):
                    if index.isValid():
                        return index.internalPointer()
                    else:
                        return self.root
            
                def index_from_node(self, node):
                    # TODO  make up another role for identification?
                    try:
                        index = self.index_from_node_cache[node]
                    except KeyError:
                        if node is self.root:
                            index = QModelIndex()
                        else:
                            index = self.match(self.index(0, 0, QModelIndex()),
                                               unique_role,
                                               node.unique(),
                                               1,
                                               Qt.MatchRecursive)[0]
            
                            self.index_from_node_cache[node] = index
            
                    return index
            
                @pyqtSlot(TreeNode, int, TreeNode, int, list)
                def changed(self, start_node, start_column, end_node, end_column, roles):
                    start_index = self.index_from_node(start_node)
                    start_row = start_index.row()
                    start_parent = start_index.parent()
                    start_index = self.index(start_row, start_column, start_parent)
            
                    if end_node is start_node:
                        end_row = start_row
                        end_parent = start_parent
                    else:
                        end_index = self.index_from_node(end_node)
                        end_row = end_index.row()
                        end_parent = end_index.parent()
            
                    end_index = self.index(end_row, end_column, end_parent)
            
                    self.dataChanged.emit(start_index, end_index, roles)
                    print('dataChanged() emitted')
                    for name, index in [('Start', start_index), ('End', end_index)]:
                        print('{:5s}:r{},c{} parent({})'.format(name,
                                                                index.row(),
                                                                index.column(),
                                                                index.parent()))
                    print('Parents equal: {}'.format(start_index.parent()
                                                     == end_index.parent()))
            
                @pyqtSlot(TreeNode, int, int)
                def begin_insert_rows(self, parent, start_row, end_row):
                    self.beginInsertRows(self.index_from_node(parent), start_row, end_row)
            
                @pyqtSlot()
                def end_insert_rows(self):
                    self.index_from_node_cache = {}
                    self.endInsertRows()
            
                @pyqtSlot(TreeNode, int, int)
                def begin_remove_rows(self, parent, start_row, end_row):
                    self.beginRemoveRows(self.index_from_node(parent), start_row, end_row)
            
                @pyqtSlot()
                def end_remove_rows(self):
                    self.index_from_node_cache = {}
                    self.endRemoveRows()
            
                @pyqtSlot()
                def set_root(self, root):
                    self.beginResetModel()
                    self.root = root
                    self.endResetModel()
                    self.root_changed.emit(root)
            
            
            class AbstractColumns:
                def __init__(self, **kwargs):
                    for member in self._members:
                        try:
                            value = kwargs[member]
                        except KeyError:
                            value = None
                        finally:
                            setattr(self, member, value)
            
                    object.__setattr__(self, '_length', len(self.__dict__))
            
                    invalid_parameters = set(kwargs.keys()) - set(self.__dict__.keys())
                    if len(invalid_parameters):
                        raise ValueError('Invalid parameter{} passed: {}'.format(
                            's' if len(invalid_parameters) > 1 else '',
                            ', '.join(invalid_parameters)))
            
                @classmethod
                def __len__(cls):
                    return len(cls._members)
            
                @classmethod
                def indexes(cls):
                    return cls(**dict(zip(cls._members, range(len(cls._members)))))
            
                @classmethod
                def fill(cls, value):
                    return cls(**dict(zip(cls._members, [value] * len(cls._members))))
            
                def __iter__(self):
                    for i in range(len(self)):
                        yield self[i]
            
                @functools.lru_cache(maxsize=None)
                def index_from_attribute(self, index):
                    for attribute in self.__class__._members:
                        if index == getattr(self.__class__.indexes, attribute):
                            return attribute
            
                    raise IndexError('column index out of range')
            
                def __getitem__(self, index):
                    if index < 0:
                        index += len(self)
                    return getattr(self, self.index_from_attribute(index))
            
                def __setitem__(self, index, value):
                    if index < 0:
                        index += len(self)
                    return setattr(self, self.index_from_attribute(index), value)
            
                def __getattr__(self, name, value):
                    if name in self._members:
                        object.__getattr__(self, name, value)
                    else:
                        raise TypeError("Attempted to get attribute {}"
                                        .format(name))
            
                def __setattr__(self, name, value):
                    if name in self._members:
                        object.__setattr__(self, name, value)
                    else:
                        raise TypeError("Attempted to set attribute {}"
                                        .format(name))
            
            
            class Columns(AbstractColumns):
                _members = ['name', 'letter', 'number']
            
            Columns.indexes = Columns.indexes()
            
            
            class Node(TreeNode):
                def __init__(self, name, letter='a', number=0):
                    TreeNode.__init__(self)
                    self.fields = Columns(name=name, letter=letter, number=number)
            
                    self.possibilities = Columns(letter=string.ascii_letters.lower(),
                                                 number=range(10))
            
                def randomize(self, _):
                    letters = set(self.possibilities.letter)
                    letters.remove(self.fields.letter)
                    self.fields.letter = random.choice(list(letters))
            
                    numbers = set(self.possibilities.number)
                    numbers.remove(self.fields.number)
                    self.fields.number = random.choice(list(numbers))
            
                def unique(self):
                    return object.__repr__(self)
            
            
            class Model(PyQAbstractItemModel):
                def __init__(self, root, parent=None):
                    PyQAbstractItemModel.__init__(self, root=root, parent=parent)
            
                    self.headers = Columns(name='Name', letter='Letter', number='Number')
            
                def data_clicked(self):
                    self.root.traverse(call_this=Node.randomize)
            
                def single_clicked(self):
                    node = self.root.children[1]
                    self.changed(node, 1, node, 1, [])
            
                def double_clicked(self):
                    node = self.root.children[1]
                    self.changed(node, 1, node, 2, [])
            
                def double_row_clicked(self):
                    node = self.root.children[1]
                    other_node = self.root.children[2]
                    self.changed(node, 1, other_node, 1, [])
            
            
            def main():
                app = QApplication(sys.argv)
            
                root = Node(name='Root', letter='-', number=-1)
                a = Node(name='A', letter='a', number=1)
                b = Node(name='B', letter='b', number=2)
                c = Node(name='C', letter='c', number=3)
                root.append_child(a)
                root.append_child(b)
                root.append_child(c)
            
                model = Model(root=root)
            
                window = QWidget()
            
                vlayout = QVBoxLayout()
                window.setLayout(vlayout)
            
                tree_view = QTreeView()
                tree_view.setModel(model)
                vlayout.addWidget(tree_view)
            
                hlayout = QHBoxLayout()
            
                data = QPushButton()
                data.setText('Change Data')
                data.clicked.connect(model.data_clicked)
                hlayout.addWidget(data)
            
                single = QPushButton()
                single.setText('Update Single')
                single.clicked.connect(model.single_clicked)
                hlayout.addWidget(single)
            
                double = QPushButton()
                double.setText('Update Double Column')
                double.clicked.connect(model.double_clicked)
                hlayout.addWidget(double)
            
                double_row = QPushButton()
                double_row.setText('Update Double Row')
                double_row.clicked.connect(model.double_row_clicked)
                hlayout.addWidget(double_row)
            
                vlayout.addLayout(hlayout)
            
                window.show()
                return app.exec_()
            
            
            if __name__ == '__main__':
                sys.exit(main())
            
            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #6

              Indeed, it looks like something is not working as it should.

              Can you reproduce this directly with C++ ?

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved