Search code examples
pythonqtpysideqtreeviewqabstractitemmodel

PySide Qt insert row at leaf item and update view


I have this mostly working code that enables the user to add items to a tree structure. This is working fine as long as you are not adding items to a leaf item (item with no children).

If the item has no children then on the first time adding a child item, view will not update at all. You can get cause the view to update by rolling up the parent item and re-expanding it. Alternatively, adding a second child item causes the view to update (and you can see both items added).

How do I get the view to update after adding the first child to a leaf item?

UPDATE: You can also double click the new parent and its child will be displayed, but no expansion widget. It appears what is not happening is the little expand item widget is not displaying until one of the above conditions has been met.

from PySide import QtCore, QtGui
import sys


model_data = [
    [1, [2, [3, 4]]],
    [5, [6, 7]],
    [8, [9]],
    [10]
]


class MyData:

    def __init__(self, number, parent=None):
        ''''''
        self.number = number
        self.children = []
        self.parent = parent
        if parent is not None:
            parent.addChild(self)


    def row(self):
        ''''''
        row = None
        if self.parent is None:
            row = 0
        else:
            for i, item in enumerate(self.parent.children):
                if item == self:
                    row = i
                    break

        return row


    def addChild(self, item):
        ''''''
        self.children.append(item)
        item.parent = self


class TreeModel(QtCore.QAbstractItemModel):

    def __init__(self, top, *args, **kwargs):
        ''''''
        super(TreeModel, self).__init__(*args, **kwargs)
        self.__top = top


    def index(self, row, column, parent=QtCore.QModelIndex()):
        ''''''
        if parent.isValid():
            parent_node = parent.internalPointer()
            node = parent_node.children[row]
            index = self.createIndex(row, column, node)
        else:
            index = self.createIndex(row, column, self.__top)
        return index


    def parent(self, index):
        ''''''
        if index.isValid():
            node = index.internalPointer()
            parent = node.parent
            if parent is None:
                parent_index = QtCore.QModelIndex()
            else:
                parent_index = self.createIndex(parent.row(), 0, parent)
        else:
            parent_index = QtCore.QModelIndex()
        return parent_index


    def rowCount(self, index=QtCore.QModelIndex()):
        ''''''
        node = index.internalPointer()
        if node is None:
            count = 1
        else:
            count = len(node.children)
        return count


    def columnCount(self, index=QtCore.QModelIndex()):
        ''''''
        return 1


    def data(self, index, role=QtCore.Qt.DisplayRole):
        ''''''
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            node = index.internalPointer()
            data = str(node.number)

        else:
            data = None

        return data


    def addChild(self, index, child):
        self.beginInsertRows(index, self.rowCount(index), self.rowCount(index)+1)

        parent = index.internalPointer()
        parent.addChild(child)

        self.endInsertRows()


class Window(QtGui.QMainWindow):

    def __init__(self):
        super(Window, self).__init__()

        self.build_model()

        self.add_item_action = QtGui.QAction('New &Item', self)
        self.add_item_action.triggered.connect(self.add_item)

        self.view = QtGui.QTreeView()
        self.view.setModel(self.model)

        self.setCentralWidget(self.view)
        self.show()


    def build_model(self):
        def recurse(parent, children_data):
            for child_data in children_data:
                if isinstance(child_data, list):
                    recurse(child, child_data)
                else:
                    child = MyData(child_data, parent=parent)

        top = MyData(0)                      
        for i, next in enumerate(model_data):
            recurse(top, next) 
        self.model = TreeModel(top)


    def add_item(self):
        ''''''
        index = self.view.currentIndex()
        new_item = MyData(0)
        self.model.addChild(index, new_item)


    def contextMenuEvent(self, event):
        ''''''
        contextMenu = QtGui.QMenu()
        contextMenu.addAction(self.add_item_action)
        contextMenu.exec_(event.globalPos())


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec_())

Solution

  • The model won't always update properly unless it knows that a change to the layout has occured. So try this:

        def addChild(self, index, child):
            ...
            self.endInsertRows()
            self.layoutChanged.emit()