Manipulate data in a QAbstractListModel from a QML ListView

Zhigalin - Reinstate CMs picture Zhigalin - Reinstate CMs · Jun 27, 2017 · Viewed 11.8k times · Source

I have a QML ListView which uses a QAbstractListModel subclass as a model.

ListView {
    id: myListView
    x: 208
    y: 19
    width: 110
    height: 160
    delegate: myListDelegate {}
    model: MyListModel
    opacity: 0
}

The model is a list of MyListItems.

class MyListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum MyRoles {
        HeadingRole = Qt::UserRole + 1,
        DescriptionRole,
        QuantityRole
    };

    explicit MyListModel(QObject *parent = 0);

    void addMyListItem(const MyListItem &item);
    int rowCount(const QModelIndex & parent = QModelIndex()) const;
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
    void dropList();

private:
    QList<MyListItem> m_list;

};

In the delegate I have a mousearea.

How I can intercept a click on the mousearea and pick that MyListItem from my QList model and send it somewhere inside the C++ part of the application?

Answer

Thomas McGuire picture Thomas McGuire · Jun 27, 2017

The comments mention returning a pointer to a MyListItem from data() to QML and accessing and modifying it in QML. That requires your MyListItem to inherit from QObject and adding one Q_PROPERTY for each member you want to access in QML. It also requires paying close attention to the object ownership (QQmlEngine::ObjectOwnership).

There is another way: Implement QAbstractListModel::setData() and QAbstractListModel::roleNames(), and the model content can be changed from QML, like model.roleName = foo.

Minimal working example below, which doubles the quantity each time the delegate is clicked:

C++:

struct MyListItem
{
    QString heading;
    QString description;
    int quantity;
};

class MyListModel : public QAbstractListModel
{
    Q_OBJECT
    Q_ENUMS(MyRoles)
public:
    enum MyRoles {
        HeadingRole = Qt::UserRole + 1,
        DescriptionRole,
        QuantityRole
    };

    using QAbstractListModel::QAbstractListModel;

    QHash<int,QByteArray> roleNames() const override {
        return { { HeadingRole, "heading" },
            { DescriptionRole, "description" },
            { QuantityRole, "quantity" },
        };
    }
    int rowCount(const QModelIndex & parent = QModelIndex()) const override {
        if (parent.isValid())
            return 0;
        return m_list.size();
    }

    bool setData(const QModelIndex &index, const QVariant &value, int role) override
    {
        if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
            return false;

        MyListItem &item = m_list[index.row()];
        if (role == DescriptionRole) item.description = value.toString();
        else if (role == HeadingRole) item.heading = value.toString();
        else if (role == QuantityRole) item.quantity = value.toInt();
        else return false;

        emit dataChanged(index, index, { role } );

        return true ;

    }

    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override {
        if (!hasIndex(index.row(), index.column(), index.parent()))
            return {};

        const MyListItem &item = m_list.at(index.row());
        if (role == DescriptionRole) return item.description;
        if (role == HeadingRole) return item.heading;
        if (role == QuantityRole) return item.quantity;

        return {};
    }

private:
    QVector<MyListItem> m_list = {
        { "heading 1", "description 1", 1 },
        { "heading 2", "description 2", 42 },
        { "heading 3", "description 3", 4711 }
    };
};

QML:

ListView {
    id: listView
    anchors.fill: parent
    model: MyListModel {}

    delegate: Item {
        implicitHeight: text.height
        width: listView.width
        Text {
            id: text
            text: model.heading + " " + model.description + " " + model.quantity
        }

        MouseArea {
            anchors.fill: text
            onClicked: {
                model.quantity *= 2;
            }
        }
    }
}