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. Inserting/Deleting Items in a drag&drop QML listView with cpp model
Forum Updated to NodeBB v4.3 + New Features

Inserting/Deleting Items in a drag&drop QML listView with cpp model

Scheduled Pinned Locked Moved Solved QML and Qt Quick
7 Posts 3 Posters 1.9k 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.
  • F Offline
    F Offline
    Fheanor
    wrote on last edited by Fheanor
    #1

    Greetings,

    I tried to add to a ListView in QML of N Items a way to add and delete a new Item at a given index.

    I did the following example, but the problem is that when I move some Items, when I try to insert a new one, the position might be incorrect and I have no clue why. When I check my DataList in my cpp model, positions are correct, however, new or deleted items won't be inserted/deleted at the right position.

    It seems that the error occurs when I insert a new Item, then I move it , and then I try to delete this Item or insert an Item next to this New Item.

    Here is a simple example (you can run it if you need). I called my Items Data : Blocks

    #include "mainwindow.h"
    #include <QApplication>
    #include <QtQml>
    #include <QQuickView>
    #include <QQuickWidget>
    #include <QQmlApplicationEngine>
    int main(int argc, char *argv[])
    {
    
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
    
        return a.exec();
    }
    

    main.cpp

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include "model.h"
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        void addItem(int index);
        ~MainWindow();
    
    private slots:
    
    
    private:
        QList<QObject*> dataList;
        Ui::MainWindow *ui;
        BlockModel model;
        int cpt = 0;
    };
    
    #endif // MAINWINDOW_H
    

    mainwindow.h

    #include <QtQml>
    #include <QQuickView>
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "QQuickWidget"
    #include <QStringList>
    
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
    
         int nbItems = 5;
    
         for(; cpt < nbItems; cpt ++) {
             Block a = Block(QString("Item ")+QString::number(cpt));
             model.addBlock(a);
         }
    
        ui->setupUi(this);
    
        QQuickWidget *view = new QQuickWidget;
        QQmlContext *ctxt = view->rootContext();
    
        ctxt->setContextProperty("myModel", &model);
        view->setSource(QUrl::fromLocalFile("main.qml"));
        view->setGeometry(0, 200, 600, 400);
        view->setResizeMode(QQuickWidget::SizeRootObjectToView);
        ui->dockWidget_3->setWidget(view);
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    

    mainwindow.cpp

    #include <QAbstractListModel>
    #include <QStringList>
    #include <qqmlcontext.h>
    #include <QDebug>
    #include <QStringList>
    
    //![0]
    class Block
    {
    public:
        Block(){
        }
    
        Block(const QString &name);
    
        QString nameBlock() const;
    
        void setName(QString n) {
            m_name = n;
        }
    
    private:
        QString m_name;
    };
    
    class BlockModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
    
        Block* getBlock(QString name);
    
        Q_INVOKABLE void moveBlock(int from,int to);
        Q_INVOKABLE void insertBlock(int index);
        Q_INVOKABLE void deleteBlock(int index);
    
        enum BlockRoles {
            nameRole = Qt::UserRole + 1,
        };
    
        BlockModel(QObject *parent = 0);
    
        void setContext(QQmlContext *ctx) {
            m_ctx = ctx;
        }
    
        void setName(const QString &name);
    
        void addBlock(const Block &Block);
    
        int rowCount(const QModelIndex & parent = QModelIndex()) const;
    
        QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
        QHash<int, QByteArray> roleNames() const;
    
    private:
        QList<Block> m_blocks;
        QQmlContext*  m_ctx;
        int cpt = 0;
    };
    

    mode.h

    #include "model.h"
    #include "qDebug"
    Block::Block(const QString &name)
        : m_name(name)
    {
    }
    
    
    
    QString Block::nameBlock() const
    {
        return m_name;
    }
    
    
    BlockModel::BlockModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    void BlockModel::addBlock(const Block &Block)
    {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_blocks << Block;
        endInsertRows();
    }
    
    
    
    int BlockModel::rowCount(const QModelIndex & parent) const {
        Q_UNUSED(parent);
        return m_blocks.count();
    }
    
    void BlockModel::moveBlock(int from, int to) {
         m_blocks.move(from,to);
    }
    
    void BlockModel::insertBlock(int index) {
        Block b =(Block(QString("New Item ")+QString::number(cpt)));
        beginInsertRows(QModelIndex(),index+1,index+1);
        m_blocks.insert(index+1,b);
        endInsertRows();
        cpt++;
    }
    
    void BlockModel::deleteBlock(int index) {
        beginRemoveRows(QModelIndex(),index,index);
        m_blocks.removeAt(index);
        endRemoveRows();
    }
    
    QVariant BlockModel::data(const QModelIndex & index, int role) const {
        if (index.row() < 0 || index.row() >= m_blocks.count())
            return QVariant();
    
        const Block &Block = m_blocks[index.row()];
        if (role == nameRole)
            return Block.nameBlock();
    
        return QVariant();
    }
    
    //![0]
    QHash<int, QByteArray> BlockModel::roleNames() const {
        QHash<int, QByteArray> roles;
    
        roles[nameRole] = "nameBlock";
    
        return roles;
    }
    

    model.cpp

    import QtQuick 2.7
    import QtQuick.Controls 1.4
    import QtQuick.Window 2.2
    import QtQuick.Dialogs 1.2
    import QtQuick.Layouts 1.2
    import QtQml.Models 2.2
    import QtQuick.Controls.Styles 1.4
    
    Rectangle {
        id : rootRectangle
        visible: true
        ScrollView {
            anchors.fill:parent
            ListView{
                id: root
                width: parent.width; height: parent.height
                property int visualIndex: -1
    
                displaced: Transition {
                    NumberAnimation { properties: "y"; easing.type: Easing.OutQuad }
                }
    
                model: DelegateModel {
    
                    id: visualModel
                    model: myModel
                    delegate: Component {
                        MouseArea {
    
                            id: delegateRoot
    
                            property int visualIndex: DelegateModel.itemsIndex
                            cursorShape: Qt.PointingHandCursor
                            width: root.width; height: 100
    
                            drag.target:  icon
                            drag.axis: Drag.YAxis
    
                            Behavior on height {
                                PropertyAnimation { duration: 100 }
                            }
    
                            Rectangle {
                                anchors.top:  delegateRoot.top
                                anchors.left: delegateRoot.left
                                id: icon
                                objectName: nameBlock
                                width: root.width-5; height: 100
                                color:  "skyblue"
    
                                radius: 3
                                Text {
                                    objectName: "rect"
                                    id: title
                                    anchors.fill: parent
                                    anchors.margins: 10
                                    horizontalAlignment: Text.AlignLeft
                                    verticalAlignment: Text.AlignVCenter
                                    text: nameBlock
                                }
    
                                Drag.active: delegateRoot.drag.active
                                Drag.source: delegateRoot
                                Drag.hotSpot.x: 36
                                Drag.hotSpot.y: 36
    
                                    Button {
                                        id : buttonAdd
                                        text: "Add Block"
    
                                        anchors{
                                            right: parent.right
                                            top: parent.top
                                            bottom: parent.bottom
                                            margins: 30
                                        }
    
    
                                        onClicked: {
                                            myModel.insertBlock(visualIndex)
                                        }
                                    }
    
                                    Button {
                                        id : buttonDelete
                                        text: "Delete Block"
                                        anchors{
                                            right: buttonAdd.left
                                            top: parent.top
                                            bottom: parent.bottom
                                            margins: 30
                                        }
                                        onClicked: {
                                            myModel.deleteBlock(visualIndex)
                                        }
                                    }
    
    
                                states: [
                                    State {
                                        when: icon.Drag.active
                                        ParentChange {
                                            target: icon
                                            parent: root
                                        }
                                        AnchorChanges {
                                            target: icon;
                                            anchors.horizontalCenter: undefined;
                                            anchors.verticalCenter: undefined
                                        }
                                    }
                                ]
    
                                transitions: Transition {
                                    // Make the state changes smooth
                                    ParallelAnimation {
                                        ColorAnimation { property: "color"; duration: 500 }
                                        NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width,font.pixelSize,font.bold,visible" }
                                    }
                                }
                            }
    
                            DropArea {
                                anchors { fill: parent; margins: 15 }
                                onEntered: {
                                    visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
                                    myModel.moveBlock(drag.source.visualIndex,delegateRoot.visualIndex)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    main.qml

    Do you have any idea of what I am doing wrong ?
    Thanks a lot and have a good day !

    1 Reply Last reply
    0
    • VRoninV Offline
      VRoninV Offline
      VRonin
      wrote on last edited by
      #2
      • Remove all the +1 in BlockModel::insertBlock
      • add calls to beginMoveRows and endMoveRows in BlockModel::moveBlock

      "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

      F 1 Reply Last reply
      0
      • VRoninV VRonin
        • Remove all the +1 in BlockModel::insertBlock
        • add calls to beginMoveRows and endMoveRows in BlockModel::moveBlock
        F Offline
        F Offline
        Fheanor
        wrote on last edited by
        #3

        @VRonin
        About index+1, I need to insert the new Block after the currentBlock. But even if I write index, it doesn't work.

        About beginMoveRows, I already tried that but if I add beginMoveRows, it will cause the application to crash.

        void BlockModel::moveBlock(int from, int to) {
                 beginMoveRows(QModelIndex(),from,from,QModelIndex(),to);
                 m_blocks.move(from,to);
                 endMoveRows();
        }
        

        I think it is because I move my blocks from the QML because I need to use some move animations.
        Here it is :

        DropArea {
             anchors { fill: parent; margins: 15 }
             onEntered: {
                    visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
                    myModel.moveBlock(drag.source.visualIndex,delegateRoot.visualIndex)
             }
         }
        
        1 Reply Last reply
        0
        • R Offline
          R Offline
          Ray Gray
          wrote on last edited by Ray Gray
          #4

          The problem is that you move items twice (in ListView's visual model - visualModel.items.move- and data model - myModel.moveBlock) and the state eventually becomes inconsistent.

          I suggest the following solution:

          In the main.qml remove the visualModel.items.move:

          DragArea  {
             ...
             onEntered: {
                myModel.moveBlock(drag.source.visualIndex, delegateRoot.visualIndex)
             }
          }
          

          In the model.cpp:

          void BlockModel::moveBlock(int from, int to) {
              if (from != to)
              {
                   // read beginMoveRows documentation why (from < to) ? to + 1 : to
                   beginMoveRows(QModelIndex(), from, from, QModelIndex(), (from < to) ? to + 1 : to);
                   m_blocks.move(from, to);
                   endMoveRows();
              }
          }
          
          1 Reply Last reply
          0
          • F Offline
            F Offline
            Fheanor
            wrote on last edited by
            #5

            Hello,

            I just read what you said in the documentation, for me it is strange way to implement the "move feature", I don't understand why they made this choice.

            But now it works fine thanks to you, have a good day !

            1 Reply Last reply
            0
            • R Offline
              R Offline
              Ray Gray
              wrote on last edited by
              #6

              Fheanor, just to make things clear. Logically, there is nothing wrong with your original code at all! It's only the visual representation that gets messed up (BlockModel::moveBlock() doesn't inform visual engine that items have been swapped and engine uses old/wrong - cached - names). The underlying data model is perfectly consistent all the time. For example, when you move and delete some recently added item, it only seems like the wrong item has been deleted.

              You can check this by yourself - the following code will force repaint on every drop and now you can see the actual model data:

              //--- model.h ---
              class BlockModel : public QAbstractListModel
              {
              public:
                 ...
                 Q_INVOKABLE void forceRepaint();
                 ... 
              
              
              //--- model.cpp ---
              void BlockModel::forceRepaint() {
                  layoutAboutToBeChanged();
                  layoutChanged();
              }
              
              
              //--- main.qml ---
              ...
                  delegate: Component {
                      MouseArea {
                          ...
                          onReleased: {
                              // force repaint
                              myModel.forceRepaint()
                         }
              
              1 Reply Last reply
              2
              • F Offline
                F Offline
                Fheanor
                wrote on last edited by
                #7

                Hello Ray.
                Sorry for the late answer, but you are right. It was the visual representation that was wrong. I should have been more careful when reading the doc :-)
                Now I fixed my problem thanks to you, have a good day !

                1 Reply Last reply
                0

                • Login

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