Search code examples
pythonqtmodelpyqtqtreeview

View/Model/Controller : How to design hierarchical data variable


In PyQt4 Model-View tutorial the author
designs Node() Class to be used with QAbstractItemModel linked to QTreeView:

class Node(object):    
    def __init__(self, name, parent=None):        
        self._name = name
        self._children=[]
        self._parent=parent

        if parent is not None:
            parent.addChild(self)

    def typeInfo(self):
        return "NODE"

    def addChild(self, child):
        self._children.append(child)

    def name(self):
        return self._name

    def setName(self, name):
        self._name = name

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

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

    def parent(self):
        return self._parent

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

Then he proceeds with declaring a data variable itself. First he declares a rootNode variable to be used as a parent to all top level item-variables:

rootNode = Node("Root")

Then he declares two top level item-variables A and B: childNodeA0 and childNodeB0. And for each of these two top-level item-variables two child item-variables are declared (I renamed the variables to make things look more generic):

childNodeA0 = Node("childNodeA0",  rootNode)
childNodeA1 = Node("childNodeA1",  childNodeA0)
childNodeA2 = Node("childNodeA2",  childNodeA1)

childNodeB0 = Node("childNodeB0",  rootNode)
childNodeB1 = Node("childNodeB1",  childNodeB0)
childNodeB3 = Node("childNodeB3",  childNodeB1)

Here is the schematic representation of data stored in rootNode variable:

|------Root
        |------childNodeA0
                |------childNodeA1
                        |------childNodeA2

        |------childNodeB0
                |------childNodeB1
                        |------childNodeB3

Now I declare a third item variable called childNodeC0:

childNodeC0 = Node("childNodeC0",  rootNode)

I pass rootNode as its parent. Here is the schematics:

|------Root
        |------childNodeA0
                |------childNodeA1
                        |------childNodeA2

        |------childNodeB0
                |------childNodeB1
                        |------childNodeB3

        |------childNodeC0

Now using .addChild() method I append childNodeC0 variable as a child of childNodeB0. So childNodeC0 is a child of two variables: it is a child of rootNode and childNodeB0:

childNodeB0.addChild(childNodeC0)

Here is the schematics:

|------Root
        |------childNodeA0
                |------childNodeA1
                        |------childNodeA2   


        |------childNodeB0
                |------childNodeB1
                        |------childNodeB3    

                |------childNodeC0    

        |------childNodeC0

All goes as expected. But when this data variable is assigned to a "real" Model the childNodeC0 item-variable only shows up as a child of Root and not as a child of childNodeB0. Aside from it the childNodeB0 item-variable is shown in TreeView twice: as a child of Root (as it is supposed to be). And it is also shown as a child of itself!:

enter image description here

Question:

How to make TreeView to show a data stored in a root variable properly?

from PyQt4 import QtCore, QtGui
import sys

class Node(object):    
    def __init__(self, name, parent=None):        
        self._name = name
        self._children=[]
        self._parent=parent

        if parent is not None:
            parent.addChild(self)

    def typeInfo(self):
        return "NODE"

    def addChild(self, child):
        self._children.append(child)

    def name(self):
        return self._name

    def setName(self, name):
        self._name = name

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

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

    def parent(self):
        return self._parent

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

    def log(self, tabLevel=-1):
        output     = ""
        tabLevel += 1

        for i in range(tabLevel):
            output += "\t"

        output += "|------" + self._name + "\n"

        for child in self._children:
            output += child.log(tabLevel)

        tabLevel -= 1
        output += "\n"

        return output

    def __repr__(self):
        return self.log()

class SceneGraphModel(QtCore.QAbstractItemModel):    
    def __init__(self, root, parent=None):
        super(SceneGraphModel, self).__init__(parent)
        self._rootNode = root

    def rowCount(self, parent):    

        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()
            # print 'VALID: ', type(parent), parent.row(), parent.column()

        return parentNode.childCount()

    def columnCount(self, parent):   
        return 1

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

        node = index.internalPointer()

        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            if index.column() == 0:
                return node.name()            

    def setData(self, index, value, role=QtCore.Qt.EditRole): 
        if index.isValid():            
            if role == QtCore.Qt.EditRole:                
                node = index.internalPointer()
                node.setName(value)                
                return True
        return False    

    def headerData(self, section, orientation, role):   
        if role == QtCore.Qt.DisplayRole:
            if section == 0:
                return "Column #0"         

    def flags(self, index):         
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable    

    def parent(self, index):   

        node = self.getNode(index)
        parentNode = node.parent()

        if parentNode == self._rootNode:
            return QtCore.QModelIndex()

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

    def index(self, row, column, parent):   

        parentNode = self.getNode(parent)
        childItem = parentNode.child(row)

        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def getNode(self, index):    
        if index.isValid():
            node = index.internalPointer()
            if node:
                return node            
        return self._rootNode    








if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)

    rootNode   = Node("Root")
    childNodeA0 = Node("childNodeA0",  rootNode)
    childNodeA1 = Node("childNodeA1",  childNodeA0)
    childNodeA2 = Node("childNodeA2",  childNodeA1)

    childNodeB0 = Node("childNodeB0",  rootNode)
    childNodeB1 = Node("childNodeB1",  childNodeB0)
    childNodeB3 = Node("childNodeB3",  childNodeB1)

    childNodeC0 = Node("childNodeC0",  rootNode)
    childNodeB0.addChild(childNodeC0)    

    treeView=QtGui.QTreeView()    

    model = SceneGraphModel(rootNode)
    treeView.setModel(model)
    treeView.show()

    print rootNode

    sys.exit(app.exec_())

Solution

  • I think that with this data model you can't have a Node having 2 different parents. The addChild is confusing because it seems that you can do it. If you look in the scene graph model, it will ask the Node for it's parent (in parent(self, index)):

    parentNode = node.parent()
    

    and Node.parent() only returns the parent defined at the construction of the item:

    return self._parent
    

    If you want to have the same child at 2 different locations you may want to copy it or to improve the data model.