13

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?

  • did you try to create a mouse area in your MyListItem or override the onClick method? – Gabriel de Grimouard Jun 27 '17 at 11:22
  • @GabrieldeGrimouard I intend the `QList` element, not `myListItem` as ListView delegate – Zhigalin - Reinstate CMs Jun 27 '17 at 11:29
  • You can define a new role (`QAbstractItemModel::roleNames`) which returns that `MyListItem` and use it in the `onClick` event as described [here](http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass) – m7913d Jun 27 '17 at 14:39

3 Answers3

14

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;
            }
        }
    }
}
Thomas McGuire
  • 4,754
  • 21
  • 37
  • Thank you! I can't believe I had so much trouble finding this. Do you know where I can see the documentation for this? I was searching in the QT docs and didn't find it. – Curtwagner1984 Apr 01 '18 at 15:51
  • 2
    It might be that this isn't documented for QML, at least http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html doesn't mention it, and even incorrectly claims that you need to call setData() explicitly from QML. – Thomas McGuire Apr 02 '18 at 07:19
  • 1
    Please just add some qDebug() << "..." to setData() and try to change model.somthing from qml... Seems DelegateModel works with local copy of data, and newer call setData behind of scene... :( – Denys Rogovchenko Nov 17 '18 at 22:46
8

You can also use index property in the delegate to manipulate the data. You just need to transform the QML index into a QModelIndex using the index method on your model. Here's a simple example where we change the display value to the string "3" every time a list item gets clicked.

ListView {
    id: listView
    anchors.fill: parent
    model: my_model

    delegate: Rectangle {
        height: 50
        width: listView.width

        MouseArea {
            anchors.fill: parent
            onClicked: {
                // Column is always zero as it's a list
                var column_number = 0; 
                // get `QModelIndex`
                var q_model_index = my_model.index(index, column_number);

                // see for list of roles: 
                // http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames
                var role = 1

                var data_changed = my_model.setData(q_model_index, "3", role);

                console.log("data change successful?", data_changed);
            }
        }
    }
}

In addition to the index property in the delegates, all of the default role names are available in the delegates. So for example, I've used the decoration role to set the color property of my Rectangle delegate before. See this list for more.

ListView {
    delegate: Rectangle {
        // list items have access to all default `roleNames` 
        // in addition to the `index` property.
        // For example, using the decoration role, demo'd below
        color: decoration
    }
}

Please also see this link where Mitch Curtis recommends using qmlRegisterUncreatableType to register user enums.

Ben Hoff
  • 888
  • 9
  • 20
2

As setRoleNames() is absolute in QAbstractListModel. You can override roleNames() and add your roles explicitly. The simple implementation of inheriting QAbstractListModel is written below.

    class BaseListModel : public QAbstractListModel
    {
        Q_OBJECT
        Q_ENUMS(Roles) 
        public:
        enum Roles {
        Name = Qt::UserRole + 1
        };

        virtual QHash<int, QByteArray> roleNames() const;
        virtual int rowCount(const QModelIndex &parent) const;
        virtual QVariant data(const QModelIndex &index, int role) const override;
        virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
       private:
       QStringList _list;
    };

    // class 
    BaseListModel::BaseListModel(QObject *parent) :
        QAbstractListModel(parent)
    {
        QHash<int, QByteArray> h = RecipeListModel::roleNames();
    }

    QHash<int, QByteArray> BaseListModel::roleNames() const {
        return {
        { Name, "name" },
        };
    }

    int BaseListModel::rowCount(const QModelIndex &parent) const {
        if (parent.isValid())
        return 0;

        return _list.size();
    }

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

        return _list.at(index.row())->data(role);  
    }

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

        bool ret = _list.at(index.row())->setData(role, value);

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

        return ret;
    }

    QVariant BaseListModel::data(int role) const {
        switch(role) {
        case Name:
        return name();
        default:
        return QVariant();
        }
    }

    bool BaseListModel::setData(int role, const QVariant &value)
        switch(role) {
        case Name:
        setName(value.toString());
        return true;
        default:
        return false;
        }
    }
Mahi
  • 41
  • 2