Calling a QML item method from C++

This is another one of those “it’s all in the documentation”, but for whatever reason, I had a heck of a time finding the info when I needed it. (The Qt documentation is generally great — I’m not beefing about that, but rather my personal inability to remember bits of it and not have enough remembered that I can search on things efficiently).

There’s times where you want to have something in C++ call a method defined by a QML item. In my case, I had a ListView that I wanted to manually reposition when its model (a C++ object derived from QStandardItemModel) changed under certain circumstances. I began with a ListView that looked something like this:

    ListView {
        id: events
        objectName: "events"

        property bool moved: false

        model: appmodel
        delegate: MyItemDelegate {}
        Component.onCompleted: positionViewAtIndex(count - 1, ListView.End)
        onMovementStarted: moved = true
        function positionListAtEnd() {
            if (!moved) events.positionViewAtIndex(events.count - 1, ListView.End)
        }
    }

Here, I’m going to use the moved property to track whether the user’s interacted with the list view at all; if she’s moved the list view, I don’t want to start yanking it around when the model updates, but if it’s just sat unused and the model updates, I want the list to always show the last item in the model. When the list view first loads, I do that manually using the onCompleted signal handler; I also want to do it when the model changes, by having the code that updates the model invoke the ListView‘s positionListAtEnd method.

The trick is actually getting a reference to the QObject that corresponds to my ListView. In QML, items are referenced by their id property; in Qt’s meta-object system, they’re referenced by object name—denoted by the objectName property, a string bearing the object’s name.

Don’t confuse the two! You can’t link QML things using object names, and, as near as I can tell, the Qt meta-object system’s C++ implementation doesn’t care one whit about QML ids.

So I give my ListView an objectName property, which for sanity’s sake is the same as the id I’ve assigned: "events". It’s a string, not a name, so those quotes in the QML are important. Once I’ve done that, I can find this object by name in my QML hierarchy using the following bit of C++:

    QObject *object = mView.rootObject();
    if (!object) return;

    QObject* listView = object->findChild<QObject *>("events");
    QVariant returnedValue;
    QMetaObject::invokeMethod(listView, "positionListAtEnd",
            Q_RETURN_ARG(QVariant, returnedValue));

The mView field is just the QDeclarativeView that renders my QML; you can see how I originally linked my C++ and QML in my previous post, Adding C++ Objects to your QML. It has a root object, a QObject descendant (really, a QGraphicsObject that corresponds to the top level of the QML object hierarchy being displayed). To invoke my QML method, I use Qt’s meta-object protocol to first find the child named "events" and then invoke its positionListAtEnd method, discarding the value it returns.

There’s no magic here—as long as you remember that QML items can have an objectName property, and that property is used internally by Qt’s object hierarchy as the object’s name.

Leave a Reply