When it comes to user interfaces Qt® software is good at it, and we love QtQuick and the QML language.
If you have developed UIs after the mobile boom (post 2005), you will have developed List Views. Every platform has them.
There are a fair view examples for Qt software List Views, both for PySide2/PyQt and C++, which point to using QAbstractListModel.
Many of them though point you at list views which show multiple string values. For example a list view of Users with first name and last name. The examples show how to extracts each of these strings in the QAbstractListModel using the Roles in the model.
Instead it would be nice to use a ‘variant’ to obtain a reference to your own class, which will have properties and methods which you can reference directly from your QML Qt software.
In Python this is reasonable straight forward because everything is a variant and the integration works ‘naturally’ as it were. So returning your class via the data() method of the QAbstractListModel works ‘out of the box’.
For C++ classes it is not as intuitive, primarily because to expose your class members and properties to QML, you need a QObject, as detailed in the guidelines provided in the Qt software documentation on integrating C++ with QML language. Take special note of the Q_OBJECT, Q_PROPERTY and Q_INVOKABLE macros.
Unfortunately, to return a custom class as a QVariant in the QAbstractListModel, we need it registered using meta data, which states:
Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.
from Detailed Description in qMetaType documentation.
Which a QObject does not fulfil as it does not have a copy constructor. So, we are between a rock and a hard place.
QAbstractListView is not the only container class we can use in a list view, but it is arguably the correct one. You could use say QList<Object *>, but then adding and removing items will not be correctly reflected.
The solution in C++ is to ensure that we return a pointer via the QVariant.
Lets look at an example. Firstly, we create our class, and we provide two strings store internally as mText
and mCommand
, and a submit method. mText
is exposed via a property we will call name
, and we expose the submit method using a Q_INVOKABLE macro. To provide an example of multiple ways to expose your members and methods, we also provide a public slot alternate_submit
, as they can be referenced from QML language directly. Which you will use will be a subjective choice.
#define YOURCLASS_H
#include <QObject>
#include <QMetaType>
class YourClass: public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ Text WRITE setText)
public:
YourClass();
YourClass(const QString& txt, const QString& command);
QString Text(void) const;
QString Command(void) const;
Q_INVOKABLE void submit(void);
signals:
public slots:
void alternate_submit(void);
private:
QString mText;
QString mCommand;
};
#endif // YOURCLASS_H
Next, we create our abstract list. Although we do not need to, we will create two roles, one called text
, the other item
, which will return the pointer to our whole class, rather than just a string. The key here is the static_cast
on returning our item.
QVariant YourListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if ( role == ETEXT)
{
return QVariant::fromValue(mDatas[index.row()]->Text());
}
else if (role == EITEM)
{
return QVariant::fromValue(static_cast<QObject *> mDatas[index.row()]));
}
return QVariant();
}
With that in place, your QML language can now reference your mText
, in two ways, via model.text
as provided by the the abstract list ETEXT
role, or model.item.name
as provided by the property of the EITEM
role. Useful, practically no, but as a demonstration, yes. The other duplicate we added was the submit()
and alternate_submit()
methods. These can both be used to call a function from your QML language. Lets exemplify with a QML language snippet.
import QtQuick 2.0
import QtQuick.Controls 2.0
Item {
width:parent.width
height: 40
clip: true
Button {
anchors.fill: parent
onClicked: model.item.alternate_submit() // or model.item.submit() or item.submit()
text: model.item.name // or model.text or item.name
}
}
A number of alternatives are given for obtaining the properties. This is because model
can be implicit to the QML file, so you can drop it. Adding it is arguably an easier read.
Errors you may hit
Undefined Reference in qmetatype.h
If you hit an undefined reference in the qmetatype.h, QMetaTypeFunctionHelper, as follows
...include\QtCore\qmetatype.h:766: error: undefined reference to `YourClass::YourClass()'
Then you have forgotten your default constructor. Referring to the documentation (which of course we read very carefully).
Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.
from Detailed Description in qmetatype documentation.
In YourClass.ccp simply add
YourClass::YourClass()
{}
TypeError: Property ‘submit’ of object QVariant(YourClass) is not a function
To be able to execute a function in a C++ class from the QML language you need to inherit your class from QObject, assign it the QOBJECT macro. Also the ‘function’ needs to be assigned the Q_INVOKABLE macro if it is a normal C++ method, or be a public slot.
In the YourClass example above, submit() and alternate_submit() can both be used within the QML language.
error: use of deleted function ‘QObject::QObject(const QObject&)’
error: use of deleted function ‘QObject::QObject(const QObject&)’
This will occur if you are trying to return a QVariant of a class inheriting a QObject. This can not be done as QObjects do not have a copy constructor. Instead cast and return the pointer to your object.
return QVariant::fromValue(static_cast<QObject *> mDatas[index.row()]));
depends on non-NOTIFYable properties
QQmlExpression: Expression qrc:/ui/YourClass.qml:10:15 depends on non-NOTIFYable properties:
This simply means you have not added a NOTIFY method for the QML language binding system to use. Refer The Property System | Qt Core 5.15.5, and in their example it is the priorityChanged method.
Qt is a registered trademark of The Qt Company Ltd. and its subsidiaries.
“Python” is a registered trademark of the Python Software Foundation.