How to implement QML ListModel like get method for

2019-02-02 02:28发布

问题:

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?

回答1:

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



回答2:

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
    }
}


回答3:

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



回答4:

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.



回答5:

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.