简体   繁体   中英

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

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.visualInde)
                            }
                        }
                    }
                }
            }
        }
    }
}

main.qml

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

There are two bugs when moving items. In DropArea.onEntered , if you print out both drag.source.visualIndex and delegateRoot.visualIndex before and after visualModel.items.move , you'll see that values are modified after moving. That means you are moving wrong rows when calling myModel.moveBlock . To fix the problem, save the value before moving items:

DropArea {
    anchors { fill: parent; margins: 15 }
    onEntered: {
        var from = drag.source.visualIndex;
        var to = delegateRoot.visualIndex;
        myModel.moveBlock(from, to);
    }
}

When moving items in C++ model, QAbstractItemModel::beginMoveRows should be called just like insert/remove items. Otherwise the QML DelegateModel cannot correctly display your model. Remember that when implementing BlockModel::moveBlock , the destination row for the model is different from the one for your source list m_blocks . See the last example in QAbstractItemModel::beginMoveRows documentation for detail.

void BlockModel::moveBlock(int from, int to) {
    if (from == to)
        return;
    auto modelFrom = from;
    auto modelTo = to + (from < to ? 1 : 0);

    beginMoveRows(QModelIndex(), modelFrom, modelFrom, QModelIndex(), modelTo);
    m_blocks.move(from,to);
    endMoveRows();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM