QML function invoked from C++ not able to update element

343 views Asked by At

I am invoking a QML function from C++. Issue is the QML function cannot update a QML element when invoked from C++. below is code:

In main.qml:

import QtQuick 2.0

function myQmlFunction(msg) {
    console.log("Got message:", msg)
    textbox.text = msg
    return "some return value"
}

Text {
    id: textbox
    text: "nothing"
}

In main.cpp:

QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml");
QObject *object = component.create();

QVariant returnedValue;
QVariant msg = "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction",
    Q_RETURN_ARG(QVariant, returnedValue),
    Q_ARG(QVariant, msg));

qDebug() << "QML function returned:" << returnedValue.toString();
delete object;

The textbox element is just a regular text, and the text inside it remains "nothing", instead of the expected "Hello from C++".

Any ideas on how to solve this issue or in successfully passing arguments from C++ to QML?

2

There are 2 answers

0
TrebledJ On

Lé Code

Qml

I'll assume that the qml code given actually belongs to MyItem.qml instead of main.qml.

Your Qml file generated an compile-time error. Functions should be placed inside an object, like so

// MyItem.qml
import QtQuick 2.0

Text {
    id: textbox
    text: "nothing"

    function myQmlFunction(msg) {
        console.log("Got message:", msg)
        textbox.text = msg
        return "some return value"
    }
}

I'm not sure how you were able to compile your project without generating an error, but I'm guessing either

  1. Your QtCreator/Qt version is not the same as mine (highly unlikely the cause); or
  2. You were try to making your code minimal and originally had a parent.

I'm sure you have a sufficient understanding about Qml so I'm not going to go deep into this.

C++

On the C++ side, I had to fiddle around with debug output to see what's wrong. Here's my main.cpp:

// main.cpp
#include <QApplication>
#include <QDebug>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQuickItem>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);  // Qt requires an instance of QApplication


    QQmlEngine *engine = new QQmlEngine;

    QString projectPath = "/Users/user/full/path/to/your/project";  // I'm on a Mac, but replace
                                                    // with the appropriate path to your project

    //  QQmlComponent component(engine, "MyItem.qml");   // this didn't work for me and
                                      // set component.status() to QQmlComponent::Error

    QQmlComponent component(engine, projectPath + "/qml/MyItem.qml");  // use full path

    qDebug() << "Status:" << component.status();
    if (component.status() == QQmlComponent::Error)
        qDebug() << "Errors:" << component.errors();
    else if (component.status() != QQmlComponent::Ready)
    {
        qDebug() << "Component is not ready!";
        return 0;
    }

    QObject *object = component.create();
    if (!object) { qDebug() << "Object creation failed!"; return 0; }

    QQuickItem *item = qobject_cast<QQuickItem*>(object);   // adding this didn't change much
                                                     // but this could be crucial

    QVariant returnedValue;
    QVariant msg = "Hello from C++";

    bool success = QMetaObject::invokeMethod(item, "myQmlFunction",   // replace `object` with `item`
                                             Q_RETURN_ARG(QVariant, returnedValue),
                                             Q_ARG(QVariant, msg));

    if (success)
        qDebug() << "QML function returned:" << returnedValue.toString();
    else
        qDebug() << "QMetaObject::invokeMethod returned false";

    delete object;

    return 0;
}

Output

The output I received on a successful build, with a successful object creation was

Status: QQmlComponent::Status(Ready)
Object: MyItem_QMLTYPE_0(0x7f8d4ae8b640)
qml: Got message: Hello from C++
QML function returned: "some return value"

I haven't yet checked whether the text changed in your Qml textbox. (Didn't bother to. It'll require more changes to the C++ code and this answer is already long enough. I was also confident that nothing'll go wrong, so ¯\_(ツ)_/¯).


Lé Non-Code

What if I don't want to use a raw file path?

If you're meh about using a raw file path (e.g. /Users/whoami/ugly/looking/path) in

QString projectPath = "/Users/user/full/path/to/your/project";

You can add this to your .pro file:

DEFINES += SOURCE_PATH=$$PWD

and set projectPath to

QString projectPath = QT_STRINGIFY(SOURCE_PATH);

This idea was borrowed from a forum thread.


Assumptions

Throughout my answer, I have assumed that your project hierarchy resembles

/./
 |- myProject.pro
 |- main.cpp
 |- qml/
    |- MyItem.qml

The essential thing is that you use your full path to your qml item. If you do find another to reference it (maybe using QUrl?) then do post a comment about it.


Further Reading

Check out the details section of the QQmlComponent class and QQmlComponent::create member function. Reading these led me to know which values to debug and what to look out for.

0
Sparkskie On

Thanks for helping out, I debugged it as well and the textbox.text was being overwritten with "Hello from C++" without the text in the window to be updated.

like eyllanesc suggested, I was creating a new engine object other than the already displayed window. (created elsewhere in the code)

after referencing the same object, the problem was solved.