2
votes

I am trying to load child items in a treeview

+top
  parent1
  parent2
  parent3

To start off I populate the model with parent node information only and attach it to a treeview, the tree looks like above.

self.mytreeview=QtGui.Qtreeview()
self.model=QtGui.QStandardItemModel(self.mytreeview)

def initialise_model(self):
    '''Populate the model with parent nodes only'''
    #open file and process each line, each file line has the parent node info
    ....

    for file_name_entry in fileobject:
        parent_item=QtGui.QStandardItem(str(file_name_entry).strip())
        parent_item.setData("this is a parent",QtCore.Qt.TooltipRole)
        self.model.appendRow(parent_item)
    self.mytreeview.setModel(self.model)

At this point none of the parents have any child information. What I want to do next is to update a parent node, when the user clicks on the parent node(just in time or lazy loading of a parents child information).

So when a parent node is clicked, and the signal is emitted

QtCore.QObject.connect(self.ui.treeView, QtCore.SIGNAL('clicked(QModelIndex)'), self.update_model)

update_model code should go fetch the children of that parent, update the model, refresh the tree and expand the parent node.

    def update_model(self,index):
        '''Update the model by adding children to selected/clicked parent node only'''
        parent_node=QtGui.QStandardItem(self.model.itemFromIndex(index))
        parent_item_index=index.row()
        #open the file called parent_node.txt and get all the child entries from the file...
        child_index=0
        for child_name_entry in parent_text_fileobject:
            child_item=QtGui.QStandardItem(str(child_name_entry).strip())
            child_item.setData("this is a child",QtCore.Qt.TooltipRole)
            parent_item.setChild(child_index,child_item)
            child_index=child_index+1
#Once all the children have been added update the model, by inserting a new row where the parent was
        self.model.removeRow(parent_item_index)#remove old parent
        self.model.insertRow(parent_item_index,parent_item)#insert new parent with its children

When I try to remove the selected parent node (the one with no children) and try inserting a new parent item (with children) it ends up addding the new parent(with children) above the parent node with no children so I end up with duplicated parent nodes

+top
  parent1
  parent2
  +parent2
    child_a
    child_b
    child_c  
  parent3

So just to recap, removing and inserting i.e.

#Once all the children have been added update the model, by inserting a new row where the parent was
        self.model.removeRow(parent_item_index)#remove old parent
        self.model.insertRow(parent_item_index,parent_item)#insert new parent with its children

is a bad idea. Looking at the comments its been suggested that AppendRow() might be the answer, but I am not so certain I believe that

self.model.appendRow(parent_item)#new parent with its children

Would simply add the parent to the bottom of my model and therefore the tree would still have duplicate parent nodes i.e

+top
  parent1
  **parent2**
  parent3
  **+parent2**
    child_a
    child_b
    child_c  

I think what I am looking for is a way to update an existing node in the model, so that the selected (parent node) can be updated with its children. e.g.

...
    self.model.UpdateExisitingNode(node_index)
...

Any suggestions on how to update an node in model? Note I am using Windows and PyQt

Regards

2
I confuse your description a little, to clarify I want to ask you child_a, child_b and child_c exist and just want to expand to see them? or Do you want to click on parent2 to create and add these children? - eyllanesc
If you've already populated the model, the tree will be automatically populated as well. So your question does not make much sense as it stands. Descriptions of code are no use - show your actual code in your question. - ekhumoro
Sounds as if you're you looking for something like a proxy model that fetches data on a just-in-time basis. - G.M.
I recommend using the first aproach (wuth appendRow). What you are doing now is harder to get right. - titusjan
@titusjan, the number of entries just parents could be several thousand and each parent node might have several hundred children, its seems time consuming to populate the entire model before attaching it to the treeview especially considering that some of the parent nodes may never be clicked on - Man Wa kileleshwa

2 Answers

1
votes

You are duplicating items because you are creating another parent item, you should only create new child items. For example:

from PyQt4 import QtGui


class Widget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.mytreeview = QtGui.QTreeView(self)
        self.setLayout(QtGui.QVBoxLayout())
        self.layout().addWidget(self.mytreeview)
        self.model = QtGui.QStandardItemModel(self.mytreeview)
        self.mytreeview.setModel(self.model)
        self.mytreeview.clicked.connect(self.update_model)
        self.initialise_model()

    def initialise_model(self):
        for text in ["parent1", "parent2", "parent3"]:
            item = QtGui.QStandardItem(text)
            self.model.appendRow(item)

    def update_model(self, index):
        parent = self.model.itemFromIndex(index)
        for text in ["children1", "children2", "children3"]:
            children = QtGui.QStandardItem("{}_{}".format(parent.text(), text))
            parent.appendRow(children)
        self.mytreeview.expand(index)


if __name__ == '__main__':
    import sys
    app = QtGui.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())
5
votes

To get this to work nicely, the parent items should show expansion arrows before they are populated with child items. It would also be nice to populate the top-level items when expaned by whatever means (i.e. via the mouse, the keyboard, and also programmatically), whilst at the same time ensuring that this only happens when required (i.e. only if the parent is currently empty).

This can all be achieved quite simply by setting an "Expandable" flag on the relevant items and then reimplementing the hasChildren method of the QStandardItemModel. With that in place, it is then very easy to write methods to populate the top-level items on demand.

Here is a simple demo based on your example code that implements all that:

import sys
from PyQt4 import QtCore, QtGui

class StandardItemModel(QtGui.QStandardItemModel):
    ExpandableRole = QtCore.Qt.UserRole + 500

    def hasChildren(self, index):
        if self.data(index, StandardItemModel.ExpandableRole):
            return True
        return super(StandardItemModel, self).hasChildren(index)

class Window(QtGui.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.mytreeview = QtGui.QTreeView()
        self.model = StandardItemModel(self.mytreeview)
        self.mytreeview.setModel(self.model)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.mytreeview)
        self.mytreeview.expanded.connect(self.update_model)
        self.initialise_model()

    def initialise_model(self):
        self.model.clear()
        for file_name_entry in fileobject:
            parent_item = QtGui.QStandardItem(file_name_entry.strip())
            parent_item.setData(True, StandardItemModel.ExpandableRole)
            parent_item.setData("this is a parent", QtCore.Qt.ToolTipRole)
            self.model.appendRow(parent_item)

    def update_model(self, index):
        parent_item = self.model.itemFromIndex(index)
        if not parent_item.rowCount():
            for child_name_entry in parent_text_fileobject:
                child_item = QtGui.QStandardItem(child_name_entry.strip())
                child_item.setData("this is a child", QtCore.Qt.ToolTipRole)
                parent_item.appendRow(child_item)

# dummy test data
fileobject = 'parent1 parent2 parent3'.split()
parent_text_fileobject = 'child_a child_b child_c'.split()

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())