Recently, I found myself develop an app in Qt again. A multi-layered data model had to be passed to QML and displayed there in a nested ListView.
But instead of writing a QAbstractListModel Class holding all the values and making them accessible, I went for a simpler approach which does not require any custom Classes.
But at first let us have a look at a simple ListView and how I learned to pass "1-D" shaped custom structured data to it:
class SimpleData: public QObject
{
Q_OBJECT
Q_PROPERTY(QString labeltext READ getLabelText NOTIFY ...)
public:
explicit SimpleData();
QString getLabelText() const;
private:
QString label_text;
}
Suppose we have a main.qml
looking like this:
ListView{
model: datamodel
delegate: Rectangle{
Label{
text: labeltext
}
}
}
And a main.cpp
including the following lines:
int main(int argc, char *argv[]){
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QList<QObject*> model;
model.append(new SimpleData(...)); // Fill with elements, set a String for element...
engine.rootContext()->setContextProperty("mainmodel", QVariant::fromValue(model));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
}
Alright. I implemented a class SimpleData
which has a property labeltext which I make accessible through the Q_PROPERTY(...)
directive. Accessing the property labeltext
from within the ListView calls getLabelText()
and the String stored in the instance is returned. If I now want to pass a List of elements to my ListView
, I create a List of QObject*
. Note that any class deriving from QObject can be cast into a QObject pointer, which is essential for this to work.
setContextProperty
provides our model to QML. It sets the property mainmodel
as a QVariant object. QML knows how to handle List-Shaped QVariants, but only if they are of a certain type, e.g. QList<QObject*>
.
For multi-dimensional models, I thought a list of QObject derived classes (i.e. SimpleData
) containing QLists of QObjects would do. I could access my labeltext
property within the nested Repeater (or ListView).
The class containing the nested data model looks like this:
class StackedData: public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> submodel READ getSubModel NOTIFY ...)
public:
explicit SimpleData();
QList<Qobject*> getSubModel() const;
private:
QList<QObject*> model_data;
}
I then modified my main.qml
like this:
ListView{
model: datamodel
delegate: Rectangle{
Repeater{
model: submodel
delegate: Rectangle{
Label{
text: labeltext
}
}
}
}
}
But, all I get is an error from QML saying that within the repeater, my labeltext
property is undefined:
ReferenceError: labeltext is not defined
I found out that within the label, you have to write modelData.labeltext
to access the data properly. The Qt Documentation "Models and Views in QtQuick" has some interesting sections about various ways of passing data to QML, including the modelData
property for Repeaters which have to deal with string lists.
For reference, here is the working main.qml
file:
ListView{
model: datamodel
delegate: Rectangle{
Repeater{
model: submodel
delegate: Rectangle{
Label{
text: modelData.labeltext
}
}
}
}
}
Reading the documentation page above, I also found out that ListView
has a section
property, used for grouping similar elements together and display them stacked. However, this looks quite limited and not flexible to me.
Concerning performance, I did not notice any major deterioration in UI interaction.
Questions? E-Mail me: afk @ daichronos.net