14

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?

avb
  • 1,701
  • 5
  • 22
  • 37

5 Answers5

25

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

labsin
  • 365
  • 3
  • 5
17

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
    }
}
Xander
  • 687
  • 9
  • 15
  • 2
    I want to access a item at a specific index and a specific role in a JS function. e.g. `myString = grid.model.get(3).DisplayRole`. I don't see how your answer can help me achieving this. – avb Apr 15 '14 at 10:22
  • my answer was more general if you are inside of the delegate. It's rather rare to access a specific index from outside but it should just work the same way did you try `myString = grid.model.get(3).display` (not DisplayRole), if that doesn't work you can iterate through all the properties of the model data to see what properties are available because they are not documented I guess (I can't do that without an example app). – Xander Apr 15 '14 at 16:33
  • 1
    How can `myString = grid.model.get(3).display` just work? QAbstractListModel has no method called get. So I have to implement it by myself. And that is the question. How do I have to implement this method? – avb Apr 16 '14 at 05:27
  • I thought the QML get function would call the virtual "data" method of QAbstractListModel (which is the default way of getting the data). I may need to setup an example app and test it myself and come back here tomorrow. I did use QAbstractListModel in another project and it was quite easy. – Xander Apr 16 '14 at 22:08
  • I've updated my answer with a complete example I tested myself, hope that helps you now. – Xander Apr 17 '14 at 13:49
  • In your example `m_list` is type of `List` and your approach not works if I have a more complex class such as `List`. I suggest to use @labsin's approach. – S.M.Mousavi Aug 29 '16 at 08:06
  • 2
    +1000 -- Thanks for this. That `model` **really** needs to be documented! It still isn't (!?). – Russ Dec 01 '16 at 01:27
  • The model's items inside a delegate is encapsulated into QQmlDMAbstractItemModelData. It is actually a single element of the model's actual row the delegate responsible drawing. You can only get/set data of this type using the roles. – Ponzifex Jul 01 '19 at 18:35
2

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.

Community
  • 1
  • 1
Richard1403832
  • 673
  • 1
  • 9
  • 16
2

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.

Sty
  • 760
  • 1
  • 9
  • 30
0

My approach is to expose objects' properties directly to QML. Here is its implementation -https://stackoverflow.com/a/14424517/1059494

Community
  • 1
  • 1
Pavel Osipov
  • 2,067
  • 1
  • 19
  • 27
  • I don't really see how objects' properties are exposed. Wouldn't I have to see a Q_PROPERTIES macro somewhere? – avb Apr 15 '14 at 10:26
  • I expose the whole object. Then JS can lookup its particular properties which are declared with Q_PROPERTY macros. – Pavel Osipov Apr 18 '14 at 08:33