0
votes

My original goal was to make the Name of an enum available in QML. The name can be provided via QMetaEnum and QVariant's toString(), neither of which are available in QML.

Articles here on Stack Overflow show how to add a Q_INVOKABLE to a QObject; this method can implement either of the above methods to solve the problem (and this method would be a handy solution these enums were not declared inside an Object). However, these enums are defined using Q_ENUM_NS, which has no place/support for Q_INVOKABLE AFAIK.

So the question became: how to make a C-function or 'static method' available to QML, in the lightest way possible?

I would like to avoid a Q_OBJECT-based solution, since there is no need for signals or a lot of the overhead provided by QObject. From reading in doc.qt.io and here, it seemed that the answer would be a Q_INVOKABLE within a Q_GADGET. Reading here Q_GADGET states:

Q_GADGETs can have Q_ENUM, Q_PROPERTY and Q_INVOKABLE, but they cannot have signals or slots.

How to create new instance of a Q_GADGET struct in QML? Shows a Struct/Q_GADGET, but uses an intermediary object.

QML - Q_INVOKABLE functions Shows Q_INVOKABLE within objects, but not gadgets.

QML can see my Q_GADGET but not Q_OBJECT Comes close, shows code defining multiple Q_PROPERTY within a Struct. It also mentions the code works fine, but doesn't show the QML or main.cpp code. Note: Not the author's fault since his aim was to get the QObject to work, not the Struct.

I could go on. I was unable to find an article showing a Q_INVOKABLE within Q_GADGET exposed directly into QML without relying on a QObject. It is possible to do so? If so, would you use a struct similar to the one below?

    struct Foo
    {
        Q_GADGET
        public:
        Q_INVOKABLE static QString bar() { return QString("invoked"); }
    };

In response to @JarMan, I have added an MRE with main.cpp and main.qml. I have tried various qmlRegister... methods. It is not clear to me which of the qmlRegister... methods needs to be used to register the Struct (or if they need to be used at all). engine.rootContext()->setContextProperty(...) works well with an object instance, but generates an error for struct Foo.
foo.h

#include <QQmlContext>

struct Foo
{
    Q_GADGET
    public:
    Q_INVOKABLE static QString bar() { return QString("Invoked"); }
};

main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>


int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    Foo foo;
    qDebug() << "in main.cpp" << foo.bar();                  // works

    qmlRegisterType<Foo>("Foo", 1, 0, "Foo");                // error

    QQmlApplicationEngine engine;

  /*engine.rootContext()->setContextProperty("Foo", &foo);*/ // error

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);    

    return app.exec();
}

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

import Foo 1.0

ApplicationWindow {
    visible: true
    height: 400
    width:  400
    
    Component.onCompleted: {
        console.log("In main.qml: "+Foo.bar());
    }
}

SOLUTION FOR setContextProperty

Thanks to @JarMan for providing the solution for setContextProperty: setContextProperty now works (as described below) - it works great.

    Foo foo;
    qDebug() << "in main.cpp" << foo.bar();                  // works

  //qmlRegisterType<Foo>("Foo", 1, 0, "Foo");                // error

    QQmlApplicationEngine engine;

    // works
    engine.rootContext()->setContextProperty("Foo", QVariant::fromValue<Foo>(foo));

The changes (above) to main.cpp, commenting import Foo in main.qml, and placing struct in a dedicated header file (shown at top) allowed the MRE to compile, run and produce the expected output. So one of the two methods works: I can expose the Q_INVOKABLE via setContextProperty.
Lessons learned:

  1. Q_GADGET [and Q_OBJECT, for that matter] tend to generate errors if they don't have dedicated header files, and
  2. Send the struct in a QVariant!

The combination of qmlRegisterType and import Foo 1.0 is not working. After researching some more, I got similar errors even with a Q_OBJECT, which tells me that my error has nothing to do with Q_GADGET. I will look more into it.

Note: it seems that Q_GADGET [and Q_ENUM, for that matter] is registered automatically and does not need the Q_DECLARE_METATYPE macro, per this Q_DECLARE_METATYPE(Type) link.

1
It looks like you have something you would like to try, but it's not clear to me that you have tried it. So I'm not sure what part you're having trouble with?JarMan
@JarMan I originally had more text, then trimmed it down to try to make it more clear. After defining the Struct above, I do not know what to do so that I can access the method 'bar' in QML. I will update the question with an MRE. I have tried various qmlRegister... and setContextProperty("foo",&foo), but all variations I have tried result in errors.Marcelo

1 Answers

1
votes

I think you're really close. There were just a couple things missing:

  1. Use Q_DECLARE_METATYPE to allow QVariant to hold a Foo object:
struct Foo
{
    Q_GADGET
public:
    Q_INVOKABLE static QString bar() { return QString("Invoked"); }
};
Q_DECLARE_METATYPE(Foo)
  1. Send your gadget to QML as a QVariant:
    Foo foo;
    engine.rootContext()->setContextProperty("Foo", QVariant::fromValue<Foo>(foo));

Then in the QML, calling Foo.bar() works fine.