How does Qt convert roles into properties on ListView delegates?
-
Hello all, I'm wondering if someone who has some knowledge of the Qt source code might tell me where to look to achieve something similar to how QML adds model roles as "fake" properties of a ListView delegate.
For instance, when you have a C++ model MyDataModel extending QAbstractItemModel with some example roles in an enum, lets call them "displayRole", "colorRole", exposed via the "roleNames()", those roles are directly accessible via the delegate as if they were bound properties:
ListView { delegate: Rectangle { Label { text: displayRole // Magic? Since QAbstractItemModel never has any Q_PROPERTY("displayRole") color: colorRole } } }
Yet these roles are not directly properties (as in not a Q_PROPERTY on the model class) as they need to access the model via the "data()" method. Further, these properties can even set values back onto the model via the setData(index, value, role) method using an assignment such as:
displayRole = "foobar"
My inclination is that Qt makes a proxy object per role that sits between the QAbstractItemModel and the QML view, this object translates the get, set and notify of these properties into an an index, value and role for the QAbstractItemModel.
The reason I want to make one of these "fake" role properties is that I am trying to incorporate a QItemSelectionModel into a list, whereby I can use a QML behaviour on a "selectedRole" property to control a visual indicator of selection. My current way of solving this is to subclass QItemSelectionModel and add a pair of signals "rowSelected(int row)" and "rowDeselected(int row)" then have each QML list delegate do a test such as "if ( row == index )" to filter selections for the specific delegate, this adds more code clutter to the QML than building one of these "fake" role properties might though. I'd also hoped to make a more modular solution that would work for any other delegates that require selection in the future too that didn't require alot of glue code.
Does anyone know how these delegate properties are achieved? Thanks for reading, hopefully this wasn't too long winded.
-
I am not sure if I understand completely what you mean, but you could be looking for the
roleNames
property. (see http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames)e.q:
Implement the
roleNames()
method in your custom model:QHash<int, QByteArray> MyCustomModel::roleNames() const{ QHash<int, QByteArray> roles; roles[Qt::UserRole] = "myRole"; return roles; }
Add the role also to your
data()
method:QVariant MyCustomModel::data(const QModelIndex& index, int role) const{ if(index.isValid()){ if(role == Qt::UserRole){ return "Hello from my role"; } } }
And via QML you can access the role like that:
MyCustomModel{ id: myCustomModel } ListView{ model: myCustomModel delegate: MyCustomDelegate{ myCustomProperty: model.myRole } }
I just typed that by heart, so there might be compiling errors. ;-)
Is that what you are looking for? -
I am not sure if I understand completely what you mean, but you could be looking for the
roleNames
property. (see http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames)e.q:
Implement the
roleNames()
method in your custom model:QHash<int, QByteArray> MyCustomModel::roleNames() const{ QHash<int, QByteArray> roles; roles[Qt::UserRole] = "myRole"; return roles; }
Add the role also to your
data()
method:QVariant MyCustomModel::data(const QModelIndex& index, int role) const{ if(index.isValid()){ if(role == Qt::UserRole){ return "Hello from my role"; } } }
And via QML you can access the role like that:
MyCustomModel{ id: myCustomModel } ListView{ model: myCustomModel delegate: MyCustomDelegate{ myCustomProperty: model.myRole } }
I just typed that by heart, so there might be compiling errors. ;-)
Is that what you are looking for?@Schluchti Thank you for your reply, however that isn't what I was trying to figure out.
I'm aware of how to access roles from my data model in QML, my question was more geared towards how QML knows to call "data" and "setData" when those roles are not a declared Q_PROPERTY of the model its self, Qt must be doing some conversion in the background and I was curious about how that functioned.
As for my goal of making a "role" for a QItemSelectionModel I've ended up with the following solution which is quite compact I think, it uses a Property Value Source which can be found in the documentation here
itemselectionlistener.h (Note this needs to be passed to qmlRegisterType before it can be constructed in QML)
#ifndef ITEMSELECTIONLISTENER_H #define ITEMSELECTIONLISTENER_H #include <QObject> #include <QQmlPropertyValueSource> #include <QQmlProperty> #include <QItemSelectionModel> class ItemSelectionListener : public QObject, public QQmlPropertyValueSource { Q_OBJECT Q_INTERFACES(QQmlPropertyValueSource) Q_PROPERTY(QItemSelectionModel * model MEMBER mModel WRITE setModel) Q_PROPERTY( int rowIndex MEMBER mRowIndex WRITE setRowIndex NOTIFY rowIndexChanged ) QQmlProperty mTargetProperty; QItemSelectionModel *mModel; int mRowIndex; public: explicit ItemSelectionListener(QObject *parent = nullptr); void setModel(QItemSelectionModel *model); void setRowIndex(int rowIndex); void setTarget(const QQmlProperty &property) Q_DECL_OVERRIDE; private: bool updateTargetProperty(const QItemSelection &selection, bool selected); signals: void rowIndexChanged(); private slots: void onSelectionChanged (const QItemSelection &selected, const QItemSelection &deselected); }; #endif // ITEMSELECTIONLISTENER_H
itemselectionlistener.cpp
#include "itemselectionlistener.h" ItemSelectionListener::ItemSelectionListener(QObject *parent) : QObject(parent), mRowIndex(-1) {} void ItemSelectionListener::setModel(QItemSelectionModel *model) { mModel = model; connect( model, &QItemSelectionModel::selectionChanged, this, &ItemSelectionListener::onSelectionChanged ); } void ItemSelectionListener::setRowIndex(int index) { mRowIndex = index; } void ItemSelectionListener::setTarget(const QQmlProperty &property) { mTargetProperty = property; } void ItemSelectionListener::onSelectionChanged (const QItemSelection &selected, const QItemSelection &deselected) { // Index can't be selected and deselected at the same time. if ( !updateTargetProperty(selected, true) ) { updateTargetProperty(deselected, false); } } bool ItemSelectionListener::updateTargetProperty (const QItemSelection &selection, bool selected) { for ( const auto &index : selection.indexes() ) { if ( index.row() == mRowIndex ) { mTargetProperty.write(selected); return true; } } return false; }
Part of Qml ListView Delegate:
property bool selectionRole ItemSelectionListener on selectionRole { rowIndex: index model: somItemSelectionModel }