Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QAbstractItemModel referencing invalid QModelIndex after row deletion.



  • I have an implementation of QAbstractItemModel for a sidebar in the main window of my app using Qt 5.10.0. Deleting a row in that sidebar seems to update the model correctly at first (the sidebar is redrawn and the row is no longer there) but then any action thereafter in the sidebar (like clicking one item) causes the model to try to access the deleted row. Somehow it seems like I’m not notifying the model correctly of the change.

    My removeRows implementation is as follows (with debugging info included):

    bool removeRows(int row, int numberOfRows, const QModelIndex& parentIndex) override
    {
        printf("Before parent item %p at row %d.\n", parentIndex.internalPointer(), parentIndex.row());
        {
            count lookUp = 0;
            auto childIndex = parentIndex.child(lookUp, 0);
            while (childIndex.isValid()) {
                printf("   %llu: Child %p at row %d.\n", lookUp, childIndex.internalPointer(), childIndex.row());
    
                childIndex = parentIndex.child(++lookUp, 0);
            }
        }
    
        auto parentItem = this->itemFromIndex(parentIndex);
    
        this->beginRemoveRows(parentIndex, row, row + numberOfRows - 1);
    
        for (count indexCount = 0; indexCount < static_cast<count>(numberOfRows); ++indexCount) {
            auto itemIndex = parentIndex.child(row, 0);
            printf("   Trying to remove item %p at row %d.\n", itemIndex.internalPointer(), row);
    
            parentItem->removeChildAtIndex(row);
        }
    
        this->endRemoveRows();
    
        emit layoutChanged();
    
        printf("After parent item %p at row %d.\n", parentIndex.internalPointer(), parentIndex.row());
        {
            count lookUp = 0;
            auto childIndex = parentIndex.child(lookUp, 0);
            while (childIndex.isValid()) {
                printf("   %llu: Child %p at row %d.\n", lookUp, childIndex.internalPointer(), childIndex.row());
    
                childIndex = parentIndex.child(++lookUp, 0);
            }
        }
    
        return true;
    }
    

    This produces the following output during a deletion:

    Before parent item 0x60d000078528 at row -1.
       0: Child 0x611000189758 at row 0.
       1: Child 0x6110001a01d8 at row 1.
       2: Child 0x61100018a298 at row 2.
       3: Child 0x6110001a0458 at row 3.
       4: Child 0x61100018a518 at row 4.
       5: Child 0x6110001a06d8 at row 5.
       6: Child 0x61100018a798 at row 6.
       7: Child 0x6110001a0958 at row 7.
    Getting item from index 0x61100018a298 at row 2.
    Getting item from index 0x61100018a298 at row 2.
    Getting item from index 0x611000189758 at row 0.
    Getting item from index 0x61100018a298 at row 2.
       Trying to remove item 0x61100018a298 at row 2.
    After parent item 0x60d000078528 at row -1.
       0: Child 0x611000189758 at row 0.
       1: Child 0x6110001a01d8 at row 1.
       2: Child 0x6110001a0458 at row 2.
       3: Child 0x61100018a518 at row 3.
       4: Child 0x6110001a06d8 at row 4.
       5: Child 0x61100018a798 at row 5.
       6: Child 0x6110001a0958 at row 6.
    

    and the stack dump when the invalid reference takes place looks like this:

    NxA::MyApp::SidebarModel::parent(QModelIndex const&) const SidebarModel.cpp:501
    QTreeView::isIndexHidden(QModelIndex const&) const 0x0000000103ab1ea7
    QTreeView::visualRect(QModelIndex const&) const 0x0000000103aa7cbe
    QAbstractItemView::update(QModelIndex const&) 0x0000000103a57cd8
    QAbstractItemView::currentChanged(QModelIndex const&, QModelIndex const&) 0x0000000103a655ef
    QTreeView::currentChanged(QModelIndex const&, QModelIndex const&) 0x0000000103ab32ab
    QMetaObject::activate(QObject*, int, int, void**) 0x000000010402adbb
    QItemSelectionModel::setCurrentIndex(QModelIndex const&, QFlags<QItemSelectionModel::SelectionFlag>) 0x0000000103fc7ea6
    QAbstractItemView::mousePressEvent(QMouseEvent*) 0x0000000103a5c0d4
    QTreeView::mousePressEvent(QMouseEvent*) 0x0000000103aabd89
    

    at this point it looks like the model is asking me for the parent of the item I deleted (0x61100018a298 at row 2). Sample code provided by Qt doesn’t even emit a layoutChanged() oddly enough, I just added mine after reading other answers on the subject. Qt redraws the sidebar correctly after deletion so I’m assuming it’s notified correctly but something still caches that invalid QModelIndex somehow? Any suggestions on how to figure out what that is?


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Aren't you missing the calls to beginRemoveRows and endRemoveRows ?



  • You are relying heavily on QModelIndex::child but that only works if parentIndex is valid. You are not checking that condition at all and it looks like it is violated:

    Before parent item 0x60d000078528 at row -1.

    Try replacing parentIndex.child(x,y) with index(x,y,parentIndex) everywhere and see if that does the trick



  • @sgaist said in QAbstractItemModel referencing invalid QModelIndex after row deletion.:

    Aren't you missing the calls to beginRemoveRows and endRemoveRows ?

    No, I believe they are in the removeRows() methods, around the actual action of removal.

    @vronin said in QAbstractItemModel referencing invalid QModelIndex after row deletion.:

    Try replacing parentIndex.child(x,y) with index(x,y,parentIndex) everywhere and see if that does the trick

    This got me on the right track and allowed me to figure out the culprit. My implementation of parent() in the model was returning an empty/invalid index (QModelIndex{ }) when the item being queried was a top level item. Problem was, it should have been returning the root item instead. My guess is this was causing the model internally to incorrectly/not remove the row and therefore still had an index pointing to it in its internals.

    Thanks for the help everyone.



  • My implementation of parent() in the model was returning an empty/invalid index (QModelIndex{ }) when the item being queried was a top level item

    This is correct

    it should have been returning the root item instead

    This is wrong. This is actually one of the items that will trip the model test which I can't recommend enough to everybody trying to subclass a custom model


Log in to reply