Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QAbstractItemModel referencing invalid QModelIndex after row deletion.

QAbstractItemModel referencing invalid QModelIndex after row deletion.

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 3 Posters 1.1k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    Didier M.
    wrote on last edited by Didier M.
    #1

    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?

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by SGaist
      #2

      Hi and welcome to devnet,

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

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      D 1 Reply Last reply
      1
      • VRoninV Offline
        VRoninV Offline
        VRonin
        wrote on last edited by VRonin
        #3

        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

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        1 Reply Last reply
        1
        • SGaistS SGaist

          Hi and welcome to devnet,

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

          D Offline
          D Offline
          Didier M.
          wrote on last edited by
          #4

          @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.

          VRoninV 1 Reply Last reply
          0
          • D Didier M.

            @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.

            VRoninV Offline
            VRoninV Offline
            VRonin
            wrote on last edited by
            #5

            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

            "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
            ~Napoleon Bonaparte

            On a crusade to banish setIndexWidget() from the holy land of Qt

            1 Reply Last reply
            2

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved