-2

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?

TrebledJ
  • 8,713
  • 7
  • 26
  • 48
Sparkskie
  • 1
  • 1
  • 2
    please improve your example and provide a [mcve], there are many things that are not defined and that are possibly causing the error. On the other hand you are creating a new item with component.create(); that may differ from the MyItem created elsewhere, even with your update your code does not make sense. – eyllanesc Nov 15 '18 at 16:08
  • 1
    main.qml or MyItem.qml???? – eyllanesc Nov 15 '18 at 16:09
  • I see that you are creating a MyItem and instantly you are destroying it so it makes me presume that the visible MyItem is different and you have changed the text of another MyItem that you delete it instantly. every time you call component.create() you are creating another MyItem other than the one that is probably in your window. How do you create the window? – eyllanesc Nov 15 '18 at 16:12
  • On second thoughts, after posting my answer, sounds you're having an [XY problem](http://xyproblem.info/). @Sparkskie – TrebledJ Nov 16 '18 at 10:05

2 Answers2

0

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.

TrebledJ
  • 8,713
  • 7
  • 26
  • 48
0

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.

Sparkskie
  • 1
  • 1