This is a text-only version of the following page on https://raymii.org:
---
Title       : 	QML Drag and Drop including reordering the C++ model
Author      : 	Remy van Elst
Date        : 	21-01-2022
URL         : 	https://raymii.org/s/tutorials/Qml_Drag_and_Drop_example_including_reordering_the_Cpp_Model.html
Format      : 	Markdown/HTML
---




This guide shows you how to implement drag and drop in Qml including how to reorder the backing C++ (`QAbstractListModel` derived) data model.
Most QML Drag and Drop examples you find online, including the Qt official example, use a `ListModel` in the same Qml file which has the data, but no example I found actually reordered a C++ model.
This example has a simple `MVVM (model-view-viewmodel)` C++ structure and a QML file with a drag and drop grid. The dragable example items come from the C++ model, which is derived from `QAbstractListModel`.

<p class="ad"> <b>Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:</b><br><br> <a href="https://leafnode.nl">I'm developing an open source monitoring app called  Leaf Node Monitoring, for windows, linux & android. Go check it out!</a><br><br> <a href="https://github.com/sponsors/RaymiiOrg/">Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.</a><br><br> <a href="https://www.digitalocean.com/?refcode=7435ae6b8212">You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $100 credit for 60 days. </a><br><br> </p>


This guide assumes you're familiair with Qml and have read through the 
[Drag][5] and [DropArea][6] documentation and the [official drag and drop][1] example.

### Drag and Drop in Qml

Qml has the concept of drag and drop built in, you define a `DropArea`
somewhere and make something `Drag`-able, that's basically it. Really neat
and quick to set up, including [official examples][1]

The second official example shows a grid of tiles which you can reorder by
dragging and dropping them. It uses a `DelegateModel`, a special Qml
repeater-like control which has both the model and the delegate, to move a
delegate item to the position of another item it is dragged over.

The example also states, quite clearly:

> The GridView Example adds drag and drop to a GridView, allowing you to
  visually reorder the delegates without changing the underlying ListModel.

In my case, also changing the underlying listmodel (and backing C++ model) is
exactly what I want to do. It turned out to be a bit convoluted, due to how a
`DelegateModel` acts as a proxy, it has a `ListModel` which you can
manipulate, but that is more like a copy of the original `model:`. You must
explicitly propagate changes back to your C++ code.


### Basic MVVM Qt setup

![drag and drop][0]

> A recording of the demo application

The example application follows an MVVM-like pattern. It has a C++ class
named `Thingie`. In this case a `Thingie` has two properties, a name and color,
but imagine it to be a more complex class, maybe an image of some kind.

There is a `ThingieListModel`, your basic Qt `QAbstractListModel` derived
list, with a backing `QList<Thingie>` and one extra special method (`move`). 

Finally there is a `ThingieModel`, the class that houses all business logic.
In an actual MVVM application there would also be a `ViewModel`, but for
this example that would be too much.

The `ThingieModel` is exposed to QML and constructs the list of `Thingies`,
which is also exposed to Qml as a property, via the model.

You can find the code [here on my Github][3], but for the sake of convinience,
the code is also at the bottom of this article.


### QML Drag & Drop 

My example has a grid of squares that you can drag and drop to re-order. The grid 
is in a sepeate file named `ThingGrid` and houses a `GridView` with a `DelegateModel`. 
The delegate of this model is another control, a `ThingTile`. This `ThingTile` has 
most of the `Drag` logic (rectangle with mousearea) and the tiles on the `ThingGrid`
have most of the `Drop` logic (`DropArea`). Inside the `ThingTile` you define your
own element, which in the case of the example is a `Text`, but could be anything.

Where my example differs from the Qt example is that my code has an explicit `MouseArea`
in the dragable tile, mostly to send signals back up above to the grid, the most 
important one being `parent.Drag.drop()`. If you're wondering why, well, let me explain.

The Drag 'thing', does not send a `drop` event / signal when you release it. It only generates
`entered` events when entering a DropArea. [You must explicitly call the `drop()`][6] method
on the `Drag` object.

The [example has a `DragHandler` and no `MouseArea`][7], so it took me a while to figure out
how to send that `drop()` event. 

But why do we need a `drop()` event? The official example already re-orders stuff once you
drop, you might ask.

The official example does not re-order when you drop, it re-orders when you `enter`. This
means, when you start dragging a square over another square (each square can be dragged, but
is also a drop area), it is already re-ordering the **visual model**. You can see this because
the animation starts (displacing the other square).

What we want to do however, is re-order the backing C++ model. Remember that the `DelegateModel`
acts as a kind of proxy between your actual `ListModel`. You're modifying the visual representation
of that model, not the actual model itself.

Inside our `DropArea` control in the `ThingGrid`, this is the code that handles the visual changes. 
Every square is both draggable as well as its own drop area, so when one square starts being dragged,
once it enters another square, this code triggers a visual change (and corresponding animation):

    onEntered: function (drag) {
        var from = (drag.source as Example.ThingTile).visualIndex
        var to = thingTile.visualIndex
        visualModel.items.move(from, to)
    }

Do note that this is all on the visual model side, not the actual model. Once you drop an item inside a
`DropArea`, the following code triggers, handling the actual backend model change:

    onDropped: function (drag) {
        var from = modelIndex
        var to = (drag.source as Example.ThingTile).visualIndex
        ThingModel.listOfThingies.move(from, to)
    }


The C++ `ThingModel` has a `Q_PROPERTY` names `listOfThingies`, which is the `QAbstractListModel` derived
class. QML calls the `move()` method directly on that listmodel. For the observant readers among you,
you might be wondering what `modelIndex` is in the latter method. The `DropArea` has a property `visualIndex`,
which is the actual index in the visual model:

    property int visualIndex: DelegateModel.itemsIndex

This property changes once we enter another droparea, via the `onEntered` method. But, we need to keep that
old index to move the C++ model. If we would use the visual index, then that would already be updated once
a drop occurs. Therefore I added a variable just below the `visualIndex`, named `modelIndex`. It is set once
you press the tile, but not via a property binding (otherwise it would update the same as the visualIndex), 
but via a JavaScript statement:

    Example.ThingTile {
      [...]
      onPressed: delegateRoot.modelIndex = visualIndex

This way, once you start dragging the square, the visual index updates and other squares are displaced. Only
when you drop, the actual C++ code is called with the old index and the new index. 


### Reordering the C++ model

The basic C++ (read only) listmodel derived from `QAbstractListModel` for your
own data structures must subclass `rowCount`, `data` and `roleNames`(last one
for QML). At work we have a few more convinience methods, for example to
update a listmodel from a vector. Most model data comes from the C++ backend
and the listmodels are only used to display stuff in QML. 

In this case, the data should also be re-ordered from QML. Most of the
[subclassing reference][8] documentation talks about removing data or adding
data from the model, not moving stuff around. There is the `beginMoveRows`
and `endMoveRows` method, but when I used that the visual model was not
ordered correctly and there were visual oddities when releasing an item. So,
in the end I went with a `beginResetModel` and `endResetModel`.

As you saw in the above Qml code, once the draggable tile is actually
released (dropped), a C++ method named `move()` is called. That method is
simple, in the backing `QList` it moves an item (not swapping) and emits the
correct signals to notify Qml that the model has changed:

    void ThingieListModel::move(int from, int to)
    {
        if(from >= 0 && from < rowCount() && to >= 0 && to < rowCount() && from != to) 
        {
            if(from == to - 1) 
            { // Allow item moving to the bottom
                to = from++;
            }

            beginResetModel();
            //beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
            _thingies.move(from, to); // update backing QList
            //endMoveRows();
            endResetModel();
        }
    }

I left the `moveRows` calls in there, if you can figure out why that doesn't work correctly, please 
let me know.

You could extend this method to emit another signal, which you can handle in the viewmodel
or actual model, for example, to send a call to a web api backend to also reorder the 
data.

### Code

The code is also [on my github][3] but since it's small, I've posted it here as well.

Generated with a bash loop to automatically intend for markdown:

    for i in *.h *.cpp *.qml; do 
      echo '**' $i '**'; 
      echo; 
      sed 's/^/    /' $i; 
      echo; 
      echo; 
    done 


**ThingModel.h**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    #ifndef THINGMODEL_H
    #define THINGMODEL_H
    
    #include <QObject>
    #include <ThingieListModel.h>
    
    class Thingie;
    class ThingModel : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(ThingieListModel* listOfThingies READ listOfThingies CONSTANT)
    public:
        ThingModel(QObject* parent = nullptr);
    
        Q_INVOKABLE QString printModel() { return _listOfThingies.print(); }
        ThingieListModel* listOfThingies() { return &_listOfThingies; }
    
    public slots:
    
    signals:
    
    private:
        ThingieListModel _listOfThingies;
    };
    
    #endif // THINGMODEL_H


**Thingie.h**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    #ifndef THINGIE_H
    #define THINGIE_H
    
    #include <QObject>
    #include <QColor>
    
    class Thingie : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
        Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    
    public:
        Thingie(const QString& name, QObject* parent = nullptr);
        const QString &name() const;
        const QColor &color() const;
    
    public slots:
        void setName(const QString &newName);
        void setColor(const QColor &newColor);
    
    signals:
        void nameChanged(const QString &name);
        void colorChanged(const QColor &color);
    
    private:
        QString _name;
        QColor _color = randomColor();
    
        QColor randomColor();
        QString randomHexString(unsigned int length);
    };
    
    #endif // THINGIE_H


**ThingieListModel.h**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    #ifndef ThingieLISTMODEL_H
    #define ThingieLISTMODEL_H
    
    #include "Thingie.h"
    
    #include <QAbstractListModel>
    
    class ThingieListModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        enum ThingieRoles
        {
            NameRole = Qt::UserRole + 1,
            ColorRole,
            ModelIndexRole,
        };
        ThingieListModel(QObject *parent = nullptr);
    
        void updateFromVector(std::vector<Thingie*> newThingies);
        QHash<int, QByteArray> roleNames() const override;
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    
        Q_INVOKABLE void move(int from, int to);
        Q_INVOKABLE QString print();
    
    private:
        QList<Thingie*> _thingies;
    };
    
    #endif // ThingieLISTMODEL_H


**ThingModel.cpp**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    #include "ThingModel.h"
    #include "Thingie.h"
    
    ThingModel::ThingModel(QObject* parent) : QObject(parent)
    {    
        std::vector<Thingie*> tmpV;
        tmpV.push_back(new Thingie("Coffee Bean", this));
        tmpV.push_back(new Thingie("Small Cup", this));
        tmpV.push_back(new Thingie("Remy van Elst", this));
        tmpV.push_back(new Thingie("Fire information", this));
        tmpV.push_back(new Thingie("New Products", this));
        tmpV.push_back(new Thingie("New Videos", this));
        tmpV.push_back(new Thingie("Corona Info", this));
        _listOfThingies.updateFromVector(tmpV);
    }
    
    
    
**Thingie.cpp**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    #include "Thingie.h"
    
    #include <random>
    
    Thingie::Thingie(const QString& name, QObject* parent) : QObject(parent), _name(name)
    {
    
    }
    
    const QString &Thingie::name() const
    {
        return _name;
    }
    
    void Thingie::setName(const QString &newName)
    {
        if (_name == newName)
            return;
        _name = newName;
        emit nameChanged(_name);
    }
    
    const QColor &Thingie::color() const
    {
        return _color;
    }
    
    void Thingie::setColor(const QColor &newColor)
    {
        if (_color == newColor)
            return;
        _color = newColor;
        emit colorChanged(_color);
    }
    
    
    QString Thingie::randomHexString(unsigned int length)
    {
        QString result;
        static std::mt19937 generator {std::random_device {}()};
        std::string hex_characters = "0123456789abcdef";
        std::uniform_int_distribution<int> dist(0, hex_characters.length() - 1);
        for (unsigned int i = 0; i < length; i++)
        {
            result += hex_characters[dist(generator)];
        }
        return result;
    }
    
    
    QColor Thingie::randomColor()
    {
        QString result = "#";
        result.append(randomHexString(6));
        return QColor(result);
    }


**ThingieListModel.cpp**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */
    #include "ThingieListModel.h"
    
    #include <QDebug>
    
    ThingieListModel::ThingieListModel(QObject *parent) :
        QAbstractListModel(parent)
    {
    }
    
    void ThingieListModel::updateFromVector(std::vector<Thingie*> newThingies)
    {
        beginResetModel();
        _thingies.clear();
        for (const auto &item : newThingies)
        {
            _thingies << item;
        }
        endResetModel();
    }
    
    QHash<int, QByteArray> ThingieListModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[NameRole] = "name";
        roles[ColorRole] = "color";
        roles[ModelIndexRole] = "modelIndex";
        return roles;
    }
    
    QVariant ThingieListModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid())
        {
            return QVariant();
        }
    
        const Thingie *thingie = _thingies[index.row()];
        switch (role)
        {
        case NameRole:
            return thingie->name();
    
        case ColorRole:
            return thingie->color();
    
        case ModelIndexRole:
            if (std::find(_thingies.begin(), _thingies.end(), thingie) != _thingies.end()) {
              return std::distance(_thingies.begin(), std::find(_thingies.begin(), _thingies.end(), thingie));
            } else {
              return -1;
            }
    
        default:
            return QVariant();
        }
    }
    
    int ThingieListModel::rowCount(const QModelIndex &) const
    {
        return _thingies.count();
    }
    
    
    void ThingieListModel::move(int from, int to)
    {
        if(from >= 0 && from < rowCount() && to >= 0 && to < rowCount() && from != to) {
            if(from == to - 1) { // Allow item moving to the bottom
                to = from++;
            }
    
            beginResetModel();
    //        beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
            qInfo() << "model move from: " << from << " to: " << to;
            _thingies.move(from, to);
    //        endMoveRows();
            endResetModel();
    
        }
    }
    
    QString ThingieListModel::print()
    {
        QString tmp;
        for(int i = 0; i < _thingies.size(); ++i) {
            tmp.append(QString::number(i));
            tmp.append(": ");
            tmp.append(_thingies.at(i)->name());
            tmp.append("; ");
        }
        return tmp;
    }


**main.cpp**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */
    
    #include "ThingModel.h"
    #include "Thingie.h"
    
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    
    int main(int argc, char *argv[])
    {
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
    
        QGuiApplication app(argc, argv);
    
        qRegisterMetaType<std::vector<Thingie*>>("std::vector<Thingie*>");
        ThingModel* thingModel = new ThingModel;
        qmlRegisterSingletonInstance<ThingModel>("org.raymii.ThingModel", 1, 0, "ThingModel", thingModel);
    
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }


**ThingGrid.qml**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    import QtQuick 2.14
    import QtQml.Models 2.15
    
    import org.raymii.ThingModel 1.0
    
    import "./" as Example
    
    GridView {
        id: root
        width: 600
        height: 600
    
        cellWidth: 250
        cellHeight: 250
    
        displaced: Transition {
            NumberAnimation {
                properties: "x,y"
                easing.type: Easing.OutQuad
            }
        }
    
        model: DelegateModel {
            id: visualModel
            model: ThingModel.listOfThingies
    
            // each square is both a drag-able item as well as a droparea (to drop items in).
            delegate: DropArea {
                id: delegateRoot
                required property color color
                required property string name
    
                property int modelIndex
    
                width: root.cellWidth
                height: root.cellHeight
    
                onEntered: function (drag) {
                    var from = (drag.source as Example.ThingTile).visualIndex
                    var to = thingTile.visualIndex
                    visualModel.items.move(from, to)
                }
    
                onDropped: function (drag) {
                    var from = modelIndex
                    var to = (drag.source as Example.ThingTile).visualIndex
                    ThingModel.listOfThingies.move(from, to)
                }
    
                property int visualIndex: DelegateModel.itemsIndex
    
                Example.ThingTile {
                    id: thingTile
                    width: root.cellWidth * 0.8
                    height: root.cellHeight * 0.8
                    dragParent: root
                    visualIndex: delegateRoot.visualIndex
                    color: delegateRoot.color
                    onPressed: delegateRoot.modelIndex = visualIndex
    
                    // content of the draggable square
                    Text {
                        anchors.fill: parent
                        anchors.centerIn: parent
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color: "white"
                        anchors.margins: 5
                        fontSizeMode: Text.Fit
                        minimumPixelSize: 10
                        font.pixelSize: 30
                        text: delegateRoot.name
                    }
                }
            }
        }
    }


**ThingTile.qml**

    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */

    import QtQuick 2.14
    
    Rectangle {
        id: root
        required property Item dragParent
        signal pressed
        signal released
        signal clicked
    
        property int visualIndex: 0
    
        anchors {
            horizontalCenter: parent.horizontalCenter
            verticalCenter: parent.verticalCenter
        }
        radius: 3
    
        MouseArea {
            id: mouseArea
            anchors.fill: parent
            drag.target: root
            onClicked: root.clicked()
            onPressed: root.pressed()
            onReleased: {
                parent.Drag.drop()
                root.released()
            }
        }
    
        Drag.active: mouseArea.drag.active
        Drag.source: root
        Drag.hotSpot.x: root.width / 2
        Drag.hotSpot.y: root.height / 2
    
        states: [
            State {
                when: mouseArea.drag.active
                ParentChange {
                    target: root
                    parent: root.dragParent
                }
    
                AnchorChanges {
                    target: root
                    anchors.horizontalCenter: undefined
                    anchors.verticalCenter: undefined
                }
            }
        ]
    }


**main.qml**


    /* Author: Remy van Elst, https://raymii.org
     * License: GNU AGPLv3
     */
    
    import QtQuick 2.15
    import QtQuick.Layouts 1.12
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    
    import org.raymii.ThingModel 1.0
    
    import "./" as Example
    
    Window {
        width: 800
        height: 800
        visible: true
        title: qsTr("Drag & Drop")
    
        Text {
            id: infoText
            anchors.top: parent.top
            anchors.left: parent.left
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignTop
            color: "black"
            anchors.margins: 5
            fontSizeMode: Text.Fit
            minimumPixelSize: 10
            font.pixelSize: 30
            height: 40
            text: "Drag and drop images below to reorder them"
        }
    
        Button {
            anchors.top: infoText.bottom
            anchors.left: parent.left
            anchors.leftMargin: 5
            id: printButton
            text: "Log C++ Model"
            onClicked: {
                modeltext.text = ThingModel.printModel()
            }
        }
        Text {
            id: modeltext
            anchors.top: printButton.bottom
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.margins: 5
            text: ""
            font.pixelSize: 20
            height: 40
            fontSizeMode: Text.Fit
            wrapMode: Text.WordWrap
            minimumPixelSize: 10
        }
    
        Example.ThingGrid {
            id: g
            anchors.top: modeltext.bottom
            anchors.margins: 5
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
        }
    }







[0]: /s/inc/img/listmodel.gif
[1]: https://web.archive.org/web/20220115175823/https://doc.qt.io/qt-5/qtquick-draganddrop-example.html
[2]: /s/inc/img/draganddrop.png
[3]: https://github.com/RaymiiOrg/qml-drag-and-drop-reorder-cpp-model-example
[4]: https://web.archive.org/web/20220117055723/https://doc.qt.io/qt-5/qml-qtquick-droparea.html#dropped-signal
[5]: https://web.archive.org/web/20220117060027/https://doc.qt.io/qt-5/qml-qtquick-drag.html
[6]: https://web.archive.org/web/20161215174157/http://askubuntu.com/questions/301145/how-to-emit-ondropped-in-qml-drag-n-drop-example
[7]: https://web.archive.org/web/20220117064303/https://code.qt.io/cgit/qt/qtdeclarative.git/tree/examples/quick/draganddrop/views/Icon.qml?h=5.15#n71
[8]: https://web.archive.org/web/20220121064951/https://doc.qt.io/qt-5/model-view-programming.html#model-subclassing-reference
[9]: https://web.archive.org/web/20200923015835/https://doc.qt.io/qt-5.12/qabstractitemmodel.html#beginMoveRows

---

License:
All the text on this website is free as in freedom unless stated otherwise. 
This means you can use it in any way you want, you can copy it, change it 
the way you like and republish it, as long as you release the (modified) 
content under the same license to give others the same freedoms you've got 
and place my name and a link to this site with the article as source.

This site uses Google Analytics for statistics and Google Adwords for 
advertisements. You are tracked and Google knows everything about you. 
Use an adblocker like ublock-origin if you don't want it.

All the code on this website is licensed under the GNU GPL v3 license 
unless already licensed under a license which does not allows this form 
of licensing or if another license is stated on that page / in that software:

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Just to be clear, the information on this website is for meant for educational 
purposes and you use it at your own risk. I do not take responsibility if you 
screw something up. Use common sense, do not 'rm -rf /' as root for example. 
If you have any questions then do not hesitate to contact me.

See https://raymii.org/s/static/About.html for details.