Search code examples
pythontreeviewpyqtdelete-rowqabstractitemmodel

pyqt treeview index error removing last row


I have a 2 column treeView with a simple heirarchy depth of 2; root > parent > child. I have a removeRows call in my treeView subclass that gets evaluated when the delete key is pressed. This seems to work fine when I delete any row but the last one.

I receive an IndexError: list index out of range, that seems to be coming from the node class child method. This seems to occur when the self.beginRemoveRows method is called in the model's removeRows. Strangely, the order of operations appears to be reversed, or threading is somehow causing a refresh to occur before the model knows about it's recent update.

With these code snippets I was hoping someone could provide a possible explanation or some ideas of what to try next to debug this.

from the QTreeView subclass,

def keyPressEvent(self, event):
    if event.key() == Qt.Key_Delete:
        index = self.currentIndex()
        self.model().removeRow(index.row())
    else:
        # call base class keyPressEvent
        QTreeView.keyPressEvent(self, event)  

from the QAbstractItemModel subclass,

def index(self, row, column, parent=QModelIndex()):
    parent_node = self.getNode(parent)
    child_item = parent_node.child(row)
    if child_item:
        return self.createIndex(row, column, child_item)
    else:
        return QModelIndex()

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

def removeRows(self, position, rows, parent=QModelIndex()):

    parent_node = self.getNode(parent)
    self.beginRemoveRows(parent, position, position + rows - 1)
    parent_node.removeChild(position)
    self.endRemoveRows()
    return True

from the node class,

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

def removeChild(self, position):

    if position < 0 or position >= len(self._children):
        return False

    child = self._children.pop(position)
    child._parent = None

    return True

Solution

  • It took me some time, but I finally found the cause of the bug: according to this mail in the index method you should check if the index exists before proceeding. Annoyingly it's not written in the method description, nor in qt4 or qt5 documentation.

    The correct implementation of the index method should be something on the line:

    def index(self, row, column, parent=QModelIndex()):
        if self.hasIndex(row, column, parentIndex):
            parent_node = self.getNode(parent) 
            child_item = parent_node.child(row)
            if child_item:
                return self.createIndex(row, column, child_item)
        else:
            return QModelIndex()
    

    In my application this solved the issue.