Question

I'm trying to wrap a List in QAbstractItemModel in Python, to allow me to view the list as a tree using the GUI goodness that comes with PyQt. To be precise I'm using PyQt5 and Python 3.3.

I tried to generate pointers to the list items explicitly but then had trouble Identifying the parent items. I don't assign them and I'm not sure how or if PyQt assigns them when you execute createIndex. I decided then to rewrite the code to explicitly identify the items that one accesses using the indices (row values) e.g. given a nested list ["A",["a",[1,2,3]],"B","C",["b",[4,5,6],"d"]] I can then point to 5 using [4,1,1], the parent is then retrieved using the same list minus the last item, [4,1].

My problem is this. The CREATEINDEX method crashes the code when I use a list generate from the row values. My, Minimum Broken Example (MBE) is below. Uncomment the two lines with "return self.createIndex(row,col,ptr)" in them to see the break down I describe.

from PyQt5.QtGui     import QFont, QColor
from PyQt5.QtCore    import QAbstractItemModel, QModelIndex, Qt, QAbstractListModel, QAbstractTableModel, QSize, QVariant
from PyQt5.QtWidgets import QApplication, QTreeView, QTableView, QListView

class NodeTree(QAbstractItemModel) :
 def __init__(self,parent,data) :
  super(NodeTree,self).__init__(parent)
  self.data   = data

 def rowCount(self, parent = QModelIndex()) :
  # int rowCount (self, QModelIndex parent = QModelIndex())
  if parent.isValid() :
   data = self.data
   for idx in parent.internalPointer() :
    data = data[idx]
   return len(data)
  else : 
   return len(self.data)  

 def columnCount(self, parent = QModelIndex()) :
  # int columnCount (self, QModelIndex parent = QModelIndex())
  return 1

 def data(self, index = QModelIndex(), role = None) :  
  # QVariant data (self, QModelIndex index, int role = Qt.DisplayRole)
  if role == 0 :
   if index.isValid() :
    return str(index.internalPointer())
   else : 
    return "No Data" 
  return None

 def index(self,row,col, parent = QModelIndex()):
  # QModelIndex index (self, int row, int column, QModelIndex parent = QModelIndex())
  # QModelIndex createIndex (self, int arow, int acolumn, int aid)
  # QModelIndex createIndex (self, int arow, int acolumn, object adata = 0)
  if parent.isValid() :
   ptr = [parent.internalPointer(),row]
#     return self.createIndex(row,col,ptr)
   return QModelIndex()
  else :
   ptr = [row]
#    return self.createIndex(row,col,ptr)
   return QModelIndex()
  return QModelIndex() 

 def parent(self, child = QModelIndex()): 
  # QObject parent (self)
  if child.isValid() :
   print(child.internalPointer())
  return QModelIndex()

if __name__ == "__main__" :
 # http://blog.mathieu-leplatre.info/filesystem-watch-with-pyqt4.html
 import sys

 app = QApplication(sys.argv)
 TreeView  = QTreeView()
 TreeModel = NodeTree(TreeView, [['A',['a',1]],['C','D'],['E','F']])
 TreeView.setModel(TreeModel)
 TreeView.show()
 app.exec_()

What I do not understand is how the QModelIndex is created within CREATEINDEX and why would it crash for a list generated on the fly within the INDEX method. To me the INTERNALPOINTER should be preserved between calls and passing a list around should then be fine.

The other thing that confuses me is when and why are parent and index called. As I understand it index traverses down the tree and parent traverses up. So a call to INDEX identifies the child of parent at (row,col) while PARENT a child and determines the parent. Is done with reference to the internal pointer ? If so why would QmodelIndex maintain it's own PARENT method. It seems as though Qt maintain it's own internal tree that establishes the hierarchy between items based upon calls between index and parent. That is there are always two trees the one for My model and the one on display.

Was it helpful?

Solution 2

I tried the same and also found that python then crashes. The problem is the interaction between C++ and the python garbage collector. When you create the QModelIndex you pass in the python list object. PySide stores a pointer to this in the c++ structure but does not tell the GC about it. That means nothing that python sees references the list and the GC will free it. Meanwhile the C++ QModelIndex gets passed around till it reaches the view. The view calls data(index) (or any other function) which puts the c++ QModelIndex back into python code. PySide then creates a new QModelIndex object. But now the internal pointer points to something that no longer exist and all kinds of fun ensures.

Note: If you store the list in a persistent structure (one that lives longer than the function) then your approach works. But that defeats the purpose of using a path list as internal pointer.

OTHER TIPS

I guess that your aid is obtain from this input list

[['A',['a',1]],['C','D'],['E','F']]

the following output

enter image description here

Your code fails mainly on these points (in my opinion):

  • You don't split Tree model and Tree item data and this generate some confusion.
  • Related to the first point, you compute some properties on the basis of your data and not only on the basis of Qt ModelIndex.

In the following code I try to reach as close as possible a one to one relationship with qt example code (C++) for the TreeView component (as in here)

from PyQt5.QtCore    import (
   QAbstractItemModel, 
   QModelIndex, 
   Qt, 
   QVariant
)

from PyQt5.QtWidgets import QApplication, QTreeView

class TreeItem(object):
    def __init__(self, content, parentItem):
        self.content = content
        self.parentItem = parentItem
        self.childItems = []

    def appendChild(self, item):
        self.childItems.append(item)

    def child(self, row):
        return self.childItems[row]

    def childCount(self):
        return len(self.childItems)

    def columnCount(self):
        return 1

    def data(self, column):
        if self.content != None and column == 0:
           return QVariant(self.content)

        return QVariant()

    def parent(self):
        return self.parentItem

    def row(self):
        if self.parentItem:
            return self.parentItem.childItems.index(self)
        return 0

class NodeTree(QAbstractItemModel):
   def __init__(self,parent,data) :
      super(NodeTree,self).__init__(parent)
      self.rootItem = TreeItem(None, None)
      self.parents = {0 : self.rootItem}
      self.setupModelData(self.rootItem, data)

   def setupModelData(self, root, data):
      for el in data:
         if isinstance(el, list):
            item = TreeItem("Node", root)
            self.setupModelData(item, el)
         else:
            item = TreeItem(el, root)
         root.appendChild(item)

   def rowCount(self, parent = QModelIndex()):
      if parent.column() > 0:
         return 0
      if not parent.isValid():
         p_Item = self.rootItem
      else:
         p_Item = parent.internalPointer()
      return p_Item.childCount() 

   def columnCount(self, parent = QModelIndex()):
      return 1

   def data(self, index, role):
       if not index.isValid():
           return QVariant()

       item = index.internalPointer()
       if role == Qt.DisplayRole:
           return item.data(index.column())
       if role == Qt.UserRole:
           if item:
               return item.content

       return QVariant()

   def headerData(self, column, orientation, role):
       if (orientation == Qt.Horizontal and
                role == Qt.DisplayRole):
           return QVariant("Content")

       return QVariant()

   def index(self, row, column, parent):
       if not self.hasIndex(row, column, parent):
          return QModelIndex()

       if not parent.isValid():
          parentItem = self.rootItem
       else:
          parentItem = parent.internalPointer()

       childItem = parentItem.child(row)
       if childItem:
          return self.createIndex(row, column, childItem)
       else:
          return QModelIndex()

   def parent(self, index):
       if not index.isValid():
          return QModelIndex()

       childItem = index.internalPointer()
       if not childItem:
          return QModelIndex()

       parentItem = childItem.parent()

       if parentItem == self.rootItem:
          return QModelIndex()

       return self.createIndex(parentItem.row(), 0, parentItem)

if __name__ == "__main__" :
   # http://blog.mathieu-leplatre.info/filesystem-watch-with-pyqt4.html
   import sys

   app = QApplication(sys.argv)
   TreeView  = QTreeView()
   TreeModel = NodeTree(TreeView, [['A',['a',1]],['C','D'],['E','F']])
   TreeView.setModel(TreeModel)
   TreeView.show()
   app.exec_()

I hope this can help you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top