Search code examples
c++qtqmlqtquick2qt5.12

Arguments to C++ signal show up as `undefined` inside QML when a QObject is passed by reference


I have a C++ object with a signal that I connected to a QML handler. However, even though I am passing arguments to the signal, the QML arguments show up as undefined.

With Balloon and Air as custom QObjects, I'm connecting Balloon's signal void popped(Air const& air, int volume, QString message); to a QML handler:

Balloon.qml:

import QtQuick 2.12
import QtQuick.Controls 2.12

import ReproImpl 0.1 as Impl

Item {
    id: root
    width: 500
    height: 500

    property int sharpness: 50

    readonly property Impl.Balloon impl: Impl.Balloon {
        onPopped: {
            // Output: {}
            console.log(JSON.stringify(arguments));
            // TypeError: cannot read property 'type' of undefined
            console.log("Bang!", air.type, volume, message);
        }
    }

    Button {
        anchors.centerIn: parent
        text: "Click me"

        onClicked: {
            console.log("Clicked!");
            impl.prick(sharpness)
        }
    }
}

main.cpp:

#include <QGuiApplication>
#include <QObject>
#include <QQmlEngine>
#include <QQuickView>

namespace my::ns {
    // Used as a parameter of the signal
    class Air : public QObject {
        Q_OBJECT
        Q_PROPERTY(QString type READ type)

    public:
        Air(QString type, QObject* parent = nullptr)
            : QObject{parent}
            , type_{type}
        {}

        Air(QObject* parent = nullptr)
            : Air{"", parent}
        {}

        QString type() const { return type_; }

    private:
        QString type_;
    };


    class Balloon : public QObject {
        Q_OBJECT

    public:
        Balloon(int toughness, QObject* parent = nullptr)
            : QObject{parent}
            , toughness{toughness}
        {}

        Balloon(QObject* parent = nullptr)
            : Balloon{10, parent}
        {}

    Q_SIGNALS:
        void popped(Air const& air, int volume, QString message);

    public Q_SLOTS:
        void prick(int sharpness)
        {
            if (sharpness > toughness) {
                Air air{"Hello"};
                Q_EMIT popped(air, 10, "POP!");
            }
        }

    private:
        int toughness;
    };

    void registerModule()
    {
        qmlRegisterModule("ReproImpl", 0, 1);
        qmlRegisterType<Air>("ReproImpl", 0, 1, "Air");
        qmlRegisterType<Balloon>("ReproImpl", 0, 1, "Balloon");
        qmlProtectModule("ReproImpl", 0);
    }
}

int main(int argc, char** argv)
{
    QGuiApplication app(argc, argv);

    my::ns::registerModule();

    QQuickView window(QUrl("Balloon.qml"));
    window.show();

    return app.exec();
}

#include "main.moc"

What can I do to fix this?

This occurs in Qt 5.12.0 and 5.13.0.


Solution

  • For whatever reason, QML doesn't support reference signal parameters. You are passing the air by const&:

    void popped(Air const& air, int volume, QString message);
    

    To get this working, you need to pass a non-const pointer:

    void popped(Air* air, int volume, QString message);
    
    // ...
    
    void prick(int sharpness)
    {
        if (sharpness > toughness) {
            Air air{"Hello"};
            Q_EMIT popped(&air, 10, "POP!");
        }
    }
    

    Note that it is safe to pass a pointer to the stack allocated air; QML does not take ownership of it:

    When data is transferred from C++ to QML, the ownership of the data always remains with C++ [unless a QObject is returned from an explicit C++ method call].