Passing multi-dimensional/stacked data models from Qt C++ to QML

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*>.

Extending to stacked data models

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

« Übersicht
Datenschutzerklärung Impressum