Solved 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 alayoutChanged()
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? -
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 ifparentIndex
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)
withindex(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)
withindex(x,y,parentIndex)
everywhere and see if that does the trickThis 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