3

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.

Justin
  • 24,288
  • 12
  • 92
  • 142

1 Answers1

4

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].

Justin
  • 24,288
  • 12
  • 92
  • 142
  • 2
    The whatever reason being that QObject are supposed to be used by pointers. They have no copy constructor or assignment operators. Passing QObject by const ref is possible in general, but weird given the pointer semantic. Also when we are talking about signals and sots, if you pass a ref, Qt can make a copy behind the scene. One example of this is when the connection is queued. – Benjamin T Jul 11 '19 at 20:17
  • Why does it have to be a non-`const` pointer? Is there no way to tell QML it's allowed to convert an `Air*` to a `const Air*`? – Ben Nov 26 '19 at 15:58
  • This solution works, this should be explicitly stated in the Qt documentation... – quent Mar 18 '21 at 11:06