Использование списков в качестве внутреннего указателя в QModelIndex при наследовании QAbstrctitemModel, PyQt

Я пытаюсь обернуть список в QAbstractItemModel в Python, чтобы позволить мне просматривать список в виде дерева, используя совершенство графического интерфейса, которое поставляется с PyQt. Если быть точным, я использую PyQt5 и Python 3.3.

Я попытался явно сгенерировать указатели на элементы списка, но потом у меня возникли проблемы с идентификацией родительских элементов. Я не назначаю их, и я не уверен, как и если PyQt назначит их, когда вы выполняете createIndex. Затем я решил переписать код, чтобы явно идентифицировать элементы, к которым можно получить доступ, используя индексы (значения строк), например. задан вложенный список ["A",["a",[1,2,3]],"B","C",["b",[4,5,6],"d"]] I затем может указывать на 5, используя [4,1,1], затем родитель извлекается с использованием того же списка за вычетом последнего элемента, [4,1].

Моя проблема заключается в следующем. Метод CREATEINDEX приводит к сбою кода, когда я использую список, сгенерированный из значений строки. Мой минимальный неработающий пример (MBE) приведен ниже. Раскомментируйте две строки с «return self.createIndex(row,col,ptr)» в них, чтобы увидеть разбивку, которую я описываю.

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_()

Чего я не понимаю, так это того, как QModelIndex создается в CREATEINDEX и почему он падает для списка, сгенерированного на лету в методе INDEX. Для меня INTERNALPOINTER должен быть сохранен между вызовами, и передача списка должна быть в порядке.

Другая вещь, которая меня смущает, это когда и почему вызываются родитель и индекс. Насколько я понимаю, индекс проходит вниз по дереву, а родительский - вверх. Таким образом, вызов INDEX идентифицирует дочерний элемент родителя в (строка, столбец), а PARENT является дочерним элементом и определяет родителя. Делается ли это со ссылкой на внутренний указатель? Если да, то почему QmodelIndex поддерживает собственный метод PARENT. Кажется, что Qt поддерживает собственное внутреннее дерево, которое устанавливает иерархию между элементами на основе вызовов между индексом и родителем. То есть всегда есть два дерева: одно для Моей модели и одно на дисплее.


person Carel    schedule 28.03.2014    source источник


Ответы (2)


Я попробовал то же самое и также обнаружил, что python затем дает сбой. Проблема заключается во взаимодействии между C++ и сборщиком мусора Python. Когда вы создаете QModelIndex, вы передаете объект списка python. PySide хранит указатель на this в структуре c++, но не сообщает об этом сборщику мусора. Это означает, что ничто из того, что видит python, не ссылается на список, и GC освободит его. Тем временем C++ QModelIndex передается до тех пор, пока не достигнет представления. Представление вызывает данные (индекс) (или любую другую функцию), которая помещает С++ QModelIndex обратно в код Python. Затем PySide создает новый объект QModelIndex. Но теперь внутренний указатель указывает на то, чего больше не существует, и гарантирует всевозможные развлечения.

Примечание. Если вы храните список в постоянной структуре (которая живет дольше, чем функция), ваш подход работает. Но это противоречит цели использования списка путей в качестве внутреннего указателя.

person Goswin von Brederlow    schedule 23.07.2015
comment
Это во многом объясняет его поведение. В итоге я выбрал метод, описанный в ответе Сальваторе. Он становится действительно мощным, когда смешивается с небольшой мета-магией, например. вы можете обернуть исходную структуру данных в дерево. Использование списка для индекса по-прежнему привлекательно при сравнении нескольких деревьев, но я так и не прошел этот этап. Возможно, можно хранить ключи в хранилище значений ключей? Тогда можно было бы просто передать строку и просмотреть список, или это тоже будет GC'd? - person Carel; 23.07.2015

Я предполагаю, что ваша помощь получена из этого списка ввода

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

следующий вывод

введите здесь описание изображения

Ваш код не работает в основном по этим пунктам (на мой взгляд):

  • Вы не разделяете модель дерева и данные элемента дерева, и это создает некоторую путаницу.
  • Что касается первого пункта, вы вычисляете некоторые свойства на основе ваших данных, а не только на основе Qt ModelIndex.

В следующем коде я пытаюсь достичь как можно более близкого отношения один к одному с qt пример кода (C++) для компонента TreeView (как в здесь)

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_()

Я надеюсь это тебе поможет.

person Salvatore Avanzo    schedule 18.04.2014