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?
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.
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.
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?
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.
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
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
}
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With