Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. QTreeView + custom model issue with sorting
Qt 6.11 is out! See what's new in the release blog

QTreeView + custom model issue with sorting

Scheduled Pinned Locked Moved Solved Qt for Python
5 Posts 3 Posters 1.4k 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.
  • V Offline
    V Offline
    voltron
    wrote on last edited by
    #1

    I need to display data in a single level tree structure (so they are appear like a simple list of items). The data come as list of dicts, like this

    data = [{"name": 1, "author": "user2", "created":"2023-10-31T12:40:22.083315Z", "attr":...}, {"name": 2, "author": "user1", "created":"2023-10-31T12:40:22.083315Z", "attr":...}...]
    

    To show this data in a tree view I have created a custom model

    class Node:
        def __init__(self, data={}):
            self.parent = None
            self.children = list()
            self.data = data
        
        def add_child_node(self, node):
            if node is None:
                return
        
            node.parent = self
            self.children.append(node)
        
        def delete_children(self):
            self.children.clear()
        
        def version(self):
            if self.data:
                return self.data.get("name")
            return None
    
    
    class MyModel(QAbstractItemModel):
        VERSION = Qt.UserRole + 1
        
        def __init__(self, parent=None):
            super().__init__(parent)
            self.root_node = Node()
        
        def clear(self):
            self.beginResetModel()
            self.root_node.delete_children()
            self.endResetModel()
        
        def add_data(self, data):
            previous_row_count = self.rowCount()
            self.beginInsertRows(QModelIndex(), previous_row_count, previous_row_count)
            for i in data:
                node = Node(i)
                self.root_node.add_child_node(node)
            self.endInsertRows()
        
        def index2node(self, index):
            if not index.isValid():
                return self.root_node
        
            return index.internalPointer()
        
        def data(self, index, role=Qt.DisplayRole):
            if not index.isValid():
                return None
        
            node = self.index2node(index)
            if role == Qt.DisplayRole:
                if index.column() == 0:
                    return node.data["name"]
                elif index.column() == 1:
                    return node.data["author"]
                elif index.column() == 2:
                    return node.data["created"]
                else:
                    return None
            elif role == Qt.ToolTipRole:
                if index.column() == 2:
                    return node.data["created"]
            elif role == MyModel.VERSION:
                return node.data["name"]
            else:
                return None
        
        def headerData(self, section, orientation, role):
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                if section == 0:
                    return "Version"
                elif section == 1:
                    return "Author"
                elif section == 2:
                    return "Created"
                else:
                    return None
            return None
        
        def rowCount(self, parent=QModelIndex()):
            node = self.index2node(parent)
            if node is None:
                return 0
        
            return len(node.children)
        
        def columnCount(self, parent=QModelIndex()):
            return 3
        
        def index(self, row, column, parent=QModelIndex()):
            if not self.hasIndex(row, column, parent):
                return QModelIndex()
        
            node = self.index2node(parent)
            if node is None:
                return QModelIndex()
        
            return self.createIndex(row, column, node.children[row])
        
        def parent(self, index):
            if not index.isValid():
                return QModelIndex()
        
            node = self.index2node(index)
            if node is not None:
                return self.index_of_parent_tree_node(node.parent)
            else:
                return QModelIndex()
        
        def index_of_parent_tree_node(self, parent_node):
            grand_parent_node = parent_node.parent
            if grand_parent_node is None:
                return QModelIndex()
        
            row = grand_parent_node.children.index(parent_node)
            return self.createIndex(row, 0, parent_node)
        
        def item_from_index(self, index):
            return self.index2node(index)
    

    The model is created and assigned to tree view, then populated by calling add_data() method whenever new portion of data arrived.

    model = MyModel()
    tree.setModel(model)
    ...
    model.add_data(data)
    

    This works fine and I have a tree view with three columns filled with information.

    But when I try to add sorting by using QSortFilterProxyModel only the first item is shown in the tree, even though the source model is populated correctly.

    Am I missing something in my model implementation or doing something wrong?

    JonBJ 1 Reply Last reply
    0
    • V voltron

      I need to display data in a single level tree structure (so they are appear like a simple list of items). The data come as list of dicts, like this

      data = [{"name": 1, "author": "user2", "created":"2023-10-31T12:40:22.083315Z", "attr":...}, {"name": 2, "author": "user1", "created":"2023-10-31T12:40:22.083315Z", "attr":...}...]
      

      To show this data in a tree view I have created a custom model

      class Node:
          def __init__(self, data={}):
              self.parent = None
              self.children = list()
              self.data = data
          
          def add_child_node(self, node):
              if node is None:
                  return
          
              node.parent = self
              self.children.append(node)
          
          def delete_children(self):
              self.children.clear()
          
          def version(self):
              if self.data:
                  return self.data.get("name")
              return None
      
      
      class MyModel(QAbstractItemModel):
          VERSION = Qt.UserRole + 1
          
          def __init__(self, parent=None):
              super().__init__(parent)
              self.root_node = Node()
          
          def clear(self):
              self.beginResetModel()
              self.root_node.delete_children()
              self.endResetModel()
          
          def add_data(self, data):
              previous_row_count = self.rowCount()
              self.beginInsertRows(QModelIndex(), previous_row_count, previous_row_count)
              for i in data:
                  node = Node(i)
                  self.root_node.add_child_node(node)
              self.endInsertRows()
          
          def index2node(self, index):
              if not index.isValid():
                  return self.root_node
          
              return index.internalPointer()
          
          def data(self, index, role=Qt.DisplayRole):
              if not index.isValid():
                  return None
          
              node = self.index2node(index)
              if role == Qt.DisplayRole:
                  if index.column() == 0:
                      return node.data["name"]
                  elif index.column() == 1:
                      return node.data["author"]
                  elif index.column() == 2:
                      return node.data["created"]
                  else:
                      return None
              elif role == Qt.ToolTipRole:
                  if index.column() == 2:
                      return node.data["created"]
              elif role == MyModel.VERSION:
                  return node.data["name"]
              else:
                  return None
          
          def headerData(self, section, orientation, role):
              if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                  if section == 0:
                      return "Version"
                  elif section == 1:
                      return "Author"
                  elif section == 2:
                      return "Created"
                  else:
                      return None
              return None
          
          def rowCount(self, parent=QModelIndex()):
              node = self.index2node(parent)
              if node is None:
                  return 0
          
              return len(node.children)
          
          def columnCount(self, parent=QModelIndex()):
              return 3
          
          def index(self, row, column, parent=QModelIndex()):
              if not self.hasIndex(row, column, parent):
                  return QModelIndex()
          
              node = self.index2node(parent)
              if node is None:
                  return QModelIndex()
          
              return self.createIndex(row, column, node.children[row])
          
          def parent(self, index):
              if not index.isValid():
                  return QModelIndex()
          
              node = self.index2node(index)
              if node is not None:
                  return self.index_of_parent_tree_node(node.parent)
              else:
                  return QModelIndex()
          
          def index_of_parent_tree_node(self, parent_node):
              grand_parent_node = parent_node.parent
              if grand_parent_node is None:
                  return QModelIndex()
          
              row = grand_parent_node.children.index(parent_node)
              return self.createIndex(row, 0, parent_node)
          
          def item_from_index(self, index):
              return self.index2node(index)
      

      The model is created and assigned to tree view, then populated by calling add_data() method whenever new portion of data arrived.

      model = MyModel()
      tree.setModel(model)
      ...
      model.add_data(data)
      

      This works fine and I have a tree view with three columns filled with information.

      But when I try to add sorting by using QSortFilterProxyModel only the first item is shown in the tree, even though the source model is populated correctly.

      Am I missing something in my model implementation or doing something wrong?

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #2

      @voltron Start by checking how many items you have and what their parentage is when accessed via the QSortFilterProxyModel indexes. Also since you do not say I presume your QSFPM actually does neither filtering nor sorting currently?

      Christian EhrlicherC V 2 Replies Last reply
      0
      • JonBJ JonB

        @voltron Start by checking how many items you have and what their parentage is when accessed via the QSortFilterProxyModel indexes. Also since you do not say I presume your QSFPM actually does neither filtering nor sorting currently?

        Christian EhrlicherC Offline
        Christian EhrlicherC Offline
        Christian Ehrlicher
        Lifetime Qt Champion
        wrote on last edited by
        #3

        @JonB beginInsertRows() is called with wrong parameters.

        Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
        Visit the Qt Academy at https://academy.qt.io/catalog

        V 1 Reply Last reply
        1
        • JonBJ JonB

          @voltron Start by checking how many items you have and what their parentage is when accessed via the QSortFilterProxyModel indexes. Also since you do not say I presume your QSFPM actually does neither filtering nor sorting currently?

          V Offline
          V Offline
          voltron
          wrote on last edited by
          #4

          @JonB yes, for now proxy model does not filter/sort items. I just want to get them all shown correctly.

          1 Reply Last reply
          0
          • Christian EhrlicherC Christian Ehrlicher

            @JonB beginInsertRows() is called with wrong parameters.

            V Offline
            V Offline
            voltron
            wrote on last edited by
            #5

            @Christian-Ehrlicher thanks for the pointer, changing call to

            self.beginInsertRows(QModelIndex(), previous_row_count, len(data))
            

            fixes it.

            1 Reply Last reply
            0
            • V voltron has marked this topic as solved on

            • Login

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