Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Qml Treeview: unexpected view output after specific sequence of deleting row then moving row
Forum Updated to NodeBB v4.3 + New Features

Qml Treeview: unexpected view output after specific sequence of deleting row then moving row

Scheduled Pinned Locked Moved Solved QML and Qt Quick
9 Posts 4 Posters 605 Views 1 Watching
  • 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.
  • A Offline
    A Offline
    ashinjo
    wrote on last edited by ashinjo
    #1

    Hello, I'm working with QML Treeview, but getting unexpected view output after doing a specific sequence of deleting a row then moving a row in the TreeView. I'm still a bit new to C++ and Qt so I may be doing something wrong. I was wondering if anyone might know what's wrong. Also, I tested this on Qt 6.8.1 and Qt 6.8.2 on Linux.

    To reproduce the error, I have a Treeview with the following rows:

    • Row A (Depth 0)
      • Row B (Depth 1)
    • Row C (Depth 0)
    1. Delete Row B. Now, Row A has no children.
    2. Move Row C to be a child of Row A.
    3. Row C should be indented underneath Row A, but it's now above row A visually.

    To be specific, this issue appears when I have a parent row with exactly one child. I delete that child, resulting in the parent row having no children. Then, I try to move a row to become a new child.

    My project's code is a bit messy so I modified the "tableofcontents" example code provided by Qt Creator to demonstrate the issue. I'll show screenshots below:

    a57d9f01-6cda-4028-9a9e-6bae1169b341-image.png

    First, click delete on the "Block 1.1" that's nested under the "Block 1".

    4da58b1c-7403-4e42-8b51-40421817cd16-image.png

    Second, click indent on the "Block 2".

    0288ec16-19e8-43ea-a50a-577707784b35-image.png

    Block 2 now appears in the wrong position. When I toggle Block 1's expander on and off, Block 2 appears twice.

    3b11c1a2-17b6-44bc-a995-6195adcc4cf9-image.png

    This is the relevant C++ code.

    bool TreeModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
    {
        TreeItem *oldParent;
    
        if(sourceParent == QModelIndex())
        {
            oldParent = rootItem.get();
        }
        else
        {
            oldParent = static_cast<TreeItem*>(sourceParent.internalPointer());
        }
    
        TreeItem *newParent = static_cast<TreeItem*>(destinationParent.internalPointer());
    
        beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
    
        for(int i = 0; i < count; i++)
        {
            std::unique_ptr<TreeItem> itemToMove = oldParent->uniqueChild(sourceRow+i);
            oldParent->removeChild(sourceRow + i);
            itemToMove.get()->setParent(newParent);
            newParent->appendChild(std::move(itemToMove));
        }
    
        endMoveRows();
        return true;
    }
    
    bool TreeModel::removeRows(int row, int count, const QModelIndex &parent)
    {
        beginRemoveRows(parent, row, row+count-1);
        TreeItem *parentItem = (parent == QModelIndex()) ? rootItem.get() : static_cast<TreeItem*>(parent.internalPointer());
        for(int i = 0; i < count; i++)
        {
            parentItem->removeChild(row);
        }
        endRemoveRows();
    
        return true;
    }
    
    void TreeModel::indent(const QModelIndex &sourceIndex)
    {
        if(sourceIndex.row() == 0)
        {
            return;
        }
    
        QModelIndex parentIndex = parent(sourceIndex);
        QModelIndex previousSiblingIndex = index(sourceIndex.row() - 1, 0, parentIndex);
        TreeItem *previousSiblingItem = static_cast<TreeItem*>(previousSiblingIndex.internalPointer());
        moveRows(parentIndex, sourceIndex.row(), 1, previousSiblingIndex, previousSiblingItem->childCount());
    }
    
    void TreeModel::deleteRow(const QModelIndex &sourceIndex)
    {
        QModelIndex parentIndex = parent(sourceIndex);
        removeRows(sourceIndex.row(), 1, parentIndex);
    }
    

    This is the relevant QML code:

    contentItem: Row {
                    x: viewDelegate._padding + (viewDelegate.depth + 1 * viewDelegate.indentation)
                    width: parent.width - viewDelegate._padding - x
    
                    Text
                    {
                        text: "Depth: " + viewDelegate.depth + ", " + viewDelegate.model.display
                        elide: Text.ElideRight
                    }
    
                    Button
                    {
                        text: "Indent"
    
                        onClicked:
                        {
                            const index = viewDelegate.treeView.index(viewDelegate.model.row, 0);
                            viewDelegate.treeView.model.indent(index);
                        }
                    }
    
                    Button
                    {
                        text: "Delete"
    
                        onClicked:
                        {
                            const index = viewDelegate.treeView.index(viewDelegate.model.row, 0);
                            viewDelegate.treeView.model.deleteRow(index);
                        }
                    }
                }
    

    This is a link to the demo project that demonstrates the error:
    https://drive.google.com/file/d/1zcQEb-cOmiw1wtLgqRWDJmg9npOZLnNx/view?usp=sharing

    1 Reply Last reply
    0
    • B Offline
      B Offline
      Bob64
      wrote on last edited by
      #2

      Probably either the modifications aren't being done correctly in the underlying tree model, or the arguments to the beginMoveRows/beginRemoveRows are not correct. I haven't had enough coffee yet to be sure whether the latter are correct but they look as if they are at least along the right lines.

      I would start off - if you haven't done so already - by checking that the state of the underlying tree is exactly as expected after each change.

      A 1 Reply Last reply
      1
      • B Bob64

        Probably either the modifications aren't being done correctly in the underlying tree model, or the arguments to the beginMoveRows/beginRemoveRows are not correct. I haven't had enough coffee yet to be sure whether the latter are correct but they look as if they are at least along the right lines.

        I would start off - if you haven't done so already - by checking that the state of the underlying tree is exactly as expected after each change.

        A Offline
        A Offline
        ashinjo
        wrote on last edited by
        #3

        @Bob64 Hi Bob. Thanks for the reply. I'm starting to wonder if this might be a bug?

        Sorry for the long post. Most of it is screenshots. I just wanted to share what I found:

        1. The underlying NodeModel seems to be correct. I added a print model function to show me the contents of the model at each step and it's always as expected.
        2. So the error happens when a parent row deletes its only child. Then, I give the parent row a new child. However, if I unindent the child row then re-indent again, the issue disappears.
        3. I implemented a "Move to previous parent sibling" function which moves a child row to it's parent's previous sibling in order to test if the move row is working as expected. It seems to move correctly until the child is added to a parent that deleted its own child row previously.

        In summary, it seems like if i have a parent row that deletes its own child row, the parent row becomes "corrupted" and when I try to insert a new child to it, the view is messed up.

        My issue + Underlying model

        View:
        6e78698e-2160-462a-a5c6-b663b6f2ab07-image.png

        Print model:
        f6e10193-28b0-4a28-9b36-54069358af23-image.png

        Action: Delete Block 1.1

        View:
        552a8e9e-9b69-4e64-ac3b-765aeb51dade-image.png

        Print model:
        59058881-4806-48f0-9197-d4af595738dd-image.png

        Action: Indent Block 2

        View:
        a215fd09-eb8a-4d43-8e16-b1ced751892f-image.png

        Print model:
        594d0cd2-e07c-4328-8797-acfb06599832-image.png

        Unindent then reindent fixes the view

        At this point, Block 2 is visually misplaced. If I unindent Block 2 then re-indent Block 2, it goes back to normal.

        Action: Unindent Block 2

        View:
        d7d2959f-044b-4f69-8bb0-ac22d0bf36a3-image.png

        Print model:
        56fbf54b-db40-46d1-8a04-689bb191b35f-image.png

        Action: Indent Block 2 again

        View:
        cbd30202-1470-4103-bed9-4f1b0d9b57f8-image.png

        Print model:
        eb421588-b8a5-4d71-8491-c1c22ff7da5d-image.png

        Move Row works until child is added to parent row that deleted its own child.

        View:
        4bd65277-e176-4b60-b796-e24ad74215ba-image.png

        Print model:
        3ae8d511-7bb2-4518-9041-7e1d32d3a950-image.png

        Action: Delete Block 1.1

        View:
        e7c8c5be-ff39-4dc8-a8ab-68f87a0d58f4-image.png

        Print model:
        ef31b986-1447-41f2-940d-09bea7c213d3-image.png

        Action: Indent Block 4 to be child of Block 3

        View:
        15c0de2d-18ec-4dbd-8219-0943b5c11140-image.png

        Print Model:
        fdc2f531-e226-4916-9a96-2dc3feb76d1d-image.png

        Action: Move Block 4 to be child of Block 2

        View:
        f1ef8a7f-d744-407b-8725-eb0e7ad15c46-image.png

        Print Model:
        d43384da-5184-421f-bb04-2543591ee325-image.png

        Action: Move Block 4 to be child of Block 1. Wrong view.

        View:
        2cd62e53-d356-460c-8062-ec78c06ae4be-image.png

        Print Model:
        517037f5-de0d-4236-b450-caf8202aa98d-image.png

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

          Hi,

          While the state might be correct, did you ensure that you are emitting the adequate signals from your model ? Especially when you start moving elements from one row of a branch to a different row of a different branch ?

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

          A 1 Reply Last reply
          1
          • SGaistS SGaist

            Hi,

            While the state might be correct, did you ensure that you are emitting the adequate signals from your model ? Especially when you start moving elements from one row of a branch to a different row of a different branch ?

            A Offline
            A Offline
            ashinjo
            wrote on last edited by
            #5

            @SGaist Hi SGaist, thank you for the help.

            So the indent, unindent, and moveToPreviousSibling functions all call moveRows() function and the moveRows function calls beginMoveRows() and endMoveRows() inside of it. From what I understand (I could be wrong), I only need to call beginMoveRows and endMoveRows.

            I attached a code snippet of moveRows at the bottom of this message.

            bool TreeModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
            {
                TreeItem *oldParent;
            
                if(sourceParent == QModelIndex())
                {
                    oldParent = rootItem.get();
                }
                else
                {
                    oldParent = static_cast<TreeItem*>(sourceParent.internalPointer());
                }
            
                TreeItem *newParent =  destinationParent == QModelIndex() ? rootItem.get() : static_cast<TreeItem*>(destinationParent.internalPointer());
            
                beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
            
                for(int i = 0; i < count; i++)
                {
                    std::unique_ptr<TreeItem> itemToMove = oldParent->uniqueChild(sourceRow+i);
                    oldParent->removeChild(sourceRow + i);
                    itemToMove.get()->setParent(newParent);
                    newParent->insertChild(std::move(itemToMove), destinationChild+i);
                }
            
                endMoveRows();
                return true;
            }
            
            1 Reply Last reply
            0
            • B Offline
              B Offline
              Bob64
              wrote on last edited by
              #6

              I have never used the move operations when working with tree models so don't take this as gospel, but from reading the documentation I believe you are correct that the begin and end calls are all that is needed here. That is consistent with other parts of the API that I am more familiar with.

              I've stared at your code, and I can't see anything obviously wrong with your implementation. But at the same time it seems unlikely that this is a Qt bug. It is a mature Qt component that no doubt is widely used, so I would have thought any issues would have been shaken out by now.

              I know it's not ideal, but if it were me doing this and I had run out of things to try, I would probably try an alternative implementation using remove and insert.

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

                I don't see anything that would be wrong.
                Something you could do is compare your implementation with the one from the model used in QTreeWidget, it might give you some hints.

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

                1 Reply Last reply
                0
                • JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by
                  #8

                  You should also test your model with QAbstractItemModelTester Class. Just in case that reveals anything basic, though the way you describe your move issue may not be tested to any depth by its tests, I don't know.

                  1 Reply Last reply
                  0
                  • A Offline
                    A Offline
                    ashinjo
                    wrote on last edited by
                    #9

                    I decided to follow Bob's suggestion to replace moveRows with removeRows followed by insertRows and it seemed to work out for me. Thank you all for the suggestions.

                    1 Reply Last reply
                    0
                    • A ashinjo has marked this topic as solved on

                    • Login

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