Solved Inserting/Deleting Items in a drag&drop QML listView with cpp model
-
Greetings,
I tried to add to a
ListView
inQML
ofN Items
a way to add and delete anew 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 myDataList
in mycpp model
, positions are correct, however,new or deleted items
won't beinserted
/deleted
at the right position.It seems that the error occurs when I insert a
new Item
, then Imove
it , and then I try todelete
thisItem
orinsert
anItem
next to this NewItem
.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 ! -
- Remove all the
+1
inBlockModel::insertBlock
- add calls to
beginMoveRows
andendMoveRows
inBlockModel::moveBlock
- Remove all the
-
@VRonin
Aboutindex+1
, I need to insert thenew Block
after thecurrentBlock
. But even if I writeindex
, it doesn't work.About
beginMoveRows
, I already tried that but if I addbeginMoveRows
, 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) } }
-
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(); } }
-
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 !
-
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() }
-
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 !