Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement QML ListModel like get method for an QAbstractListModel derived model

I want to use an QAbstractListModel derived model in QML. Binding the model to views already works great.

The next thing I want achieve is the ability to access specific items and their role like it is possible with a QML ListModel

grid.model.get(index).DisplayRole

But I have no idea how to implement this get method in my QAbstractListModel derived model.

Any hints?

like image 869
avb Avatar asked Mar 28 '14 11:03

avb


People also ask

What is a qabstractlistmodel?

NOTE: This is Part 2 of the Data Models Deconstructed series, see Part 1 here. QAbstractListModel is the tried and true way of exposing data from Qt C++ to QML. This abstract class provides an interface or contract that is adhered to by QtQuick elements such as ListView, PathView, GridView, and Repeater.

What is listmodel in Qt?

This element was introduced in Qt 4.7. The ListModel is a simple container of ListElement definitions, each containing data roles. The contents can be defined dynamically, or explicitly in QML. The number of elements in the model can be obtained from its count property.

Do you put data in your QML listmodels?

Yes I am. I put some data in the QML ListModels. This is data pertaining to the UI itself (screen location of the representation, size, etc.) "Real" data about the bottles (label, contents, fill level) is kept in a C++ object. This seemed like a natural breakdown for the data; is using two sources a bad idea?

What is a listmodel?

More... The ListModel is a simple container of ListElement definitions, each containing data roles. The contents can be defined dynamically, or explicitly in QML. The number of elements in the model can be obtained from its count property.


4 Answers

You can add an Q_INVOKABLE function to the QAbstractItemModel derived class like this:

...

Q_INVOKABLE QVariantMap get(int row);

...

QVariantMap get(int row) {
    QHash<int,QByteArray> names = roleNames();
    QHashIterator<int, QByteArray> i(names);
    QVariantMap res;
    while (i.hasNext()) {
        i.next();
        QModelIndex idx = index(row, 0);
        QVariant data = idx.data(i.key());
        res[i.value()] = data;
        //cout << i.key() << ": " << i.value() << endl;
    }
    return res;
}

This will return something like { "bookTitle" : QVariant("Bible"), "year" : QVariant(-2000) } so you could use .bookTitle on it

like image 140
labsin Avatar answered Oct 18 '22 02:10

labsin


if you want to use the classic approach for roles in list models you don't have to do anything special in the c++ side, you have your model like always and it should implement the data method:

QVariant QAbstractItemModel::data(const QModelIndex & index, int role = Qt::DisplayRole) const

to access the different roles from QML the model attached property can be used in your ListView delegate:

model.display // model.data(index, Qt::DisplayRole) in c++
model.decoration // Qt::DecorationRole
model.edit // Qt::EditRole
model.toolTip // Qt::ToolTipRole
// ... same for the other roles

I don't think that is anywhere documented in the Qt doc (yet), but to find out what properties you can access form QML just start the app in debug mode and put a breakpoint in the delegate or print all properties to the console. Btw the model property inside the delegate is of type QQmlDMAbstractItemModelData, so there is some "Qt magic" happening in the background, looks like some wrapper around the list model data, still I could not find anything official in the Qt documentation about that (I figured that out by myself with the QML debugger and stuff).

If you need to access the model data from outside of the delegate I don't think there is any build in functionality for that, so you have to do that yourself.

I did an example for a custom QAbstractListModel class which exposes a count property and get-function similar to the default QML ListModel:

mylistmodel.h

class MyListModel : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)

public:
    explicit MyListModel(QObject *parent = 0);

    int rowCount(const QModelIndex & = QModelIndex()) const override { return m_data.count(); }
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    Q_INVOKABLE int get(int index) const { return m_data.at(index); }

signals:
    void countChanged(int c);

private:
    QList<int> m_data;
};

mylistmodel.cpp

MyListModel::MyListModel(QObject *parent) :
    QAbstractListModel(parent)
{
    m_data << 1 << 2 << 3 << 4 << 5; // test data
    emit countChanged(rowCount());
}

QVariant MyListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.row() < 0 || index.row() >= rowCount())
        return QVariant();

    int val = m_data.at(index.row());

    switch (role) {
    case Qt::DisplayRole:
        return QString("data = %1").arg(val);
        break;
    case Qt::DecorationRole:
        return QColor(val & 0x1 ? Qt::red : Qt::green);
        break;
    case Qt::EditRole:
        return QString::number(val);
        break;
    default:
        return QVariant();
    }
}

Since it is pretty easy to expose properties and functions to QML I guess this a good way to do it for know.

For completeness here an example ListView using my custom model:

ListView {
    anchors.fill: parent
    model: MyListModel { id: myModel }
    delegate: Text {
        text: model.display
    }

    Component.onCompleted: {
        console.log(myModel.count) // 5
        console.log(myModel.get(0)) // 1
    }
}
like image 36
Xander Avatar answered Oct 18 '22 03:10

Xander


This took me a very long time to find, as there are many incorrect solutions on Stackoverflow.

I've posted the reply here:

How to access ListView's current item from qml

This works for all models, whether derived from QAbstractItemModel or built directly in QML, and even allows access to tree-shaped models.

like image 2
Richard1403832 Avatar answered Oct 18 '22 03:10

Richard1403832


An alternative approach to do this would be to directly use the built-in functions of QAbstractItemModel, e.g. via

grid.model.data(grid.model.index(index, 0), 0 /*== Qt::DisplayRole*/)

This technically works, but requires the user to know the numerical codes for the roles, instead of the designating strings. The main problem here is that there is at best only built-in functionality to determine roleNames(). For a proper mapping of the name strings to the according numerical values one would need to either implement an inverting function and expose it using Q_INVOKABLE or process the QHash resulting from roleNames() in QML manually.

like image 2
Sty Avatar answered Oct 18 '22 04:10

Sty