Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Threaded qmlRegisterType signal not emitted



  • G'day,

    I'm in the process of expanding my understanding regarding c++/QML integration beyond Singletons, which I've used extensively in the past. At the moment I'm trying to implement a basic threaded QUdpSocket class, which updates a QML label with a received value. This is just an example/proof of concept so I can better understand the mechanism. The issue I'm having is that despite the QThread setup working, and the readyRead() signal being called when a UDP packet is ready, the corresponding c++ property signal isn't emitted, so the QML label is not updated.

    Below is some example code.

    someObject.h:

    #include <QObject>
    #include <QUdpSocket>
    
    class someObject : public QObject
    {
        Q_OBJECT
    
        //Some metric
        Q_PROPERTY(int metric1 READ metric1 NOTIFY metric1Changed)
    
    public:
        explicit someObject(QObject *parent = nullptr);
    
        int metric1(){return this->m_metric1;}
    
    private slots:
        void readDatagram();
    
    private:
        QUdpSocket *udpSocket = nullptr;
    
    signals:
        void metric1Changed();
    
    public slots:
        void setupSocket();
    
    public:
        int m_metric1;
    };
    

    someObject.cpp:

    #include "someObject.h"
    #include <QDebug>
    #include <QNetworkDatagram>
    #include <string>
    #include <iostream>
    #include <QString>
    
    someObject::someObject(QObject *parent) : QObject(parent), m_metric1(0)
    {
    
    }
    
    void someObject::setupSocket()
    {
        someSocket = new QUdpSocket(this);
    
        bool bindAttempt = someSocket->bind(QHostAddress::LocalHost, 5000, QAbstractSocket::BindFlag::ShareAddress);
    
        if (bindAttempt) {
            qDebug("Success");
        } else {
            qDebug("Fail");
        }
    
        connect(someSocket, &QUdpSocket::readyRead, this, &someObject::readDatagram);
    }
    
    void someObject::readDatagram()
    {
        while (someSocket->hasPendingDatagrams()) {
                QNetworkDatagram datagram = someSocket->receiveDatagram();
                qDebug(datagram.data());
                bool ok;
                m_metric1 = datagram.data().toHex().toInt(&ok, 16);
                emit metric1Changed();
        }
    }
    

    main.cpp:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QThread>
    
    #include "someObject.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
            qmlRegisterType<someObject>("SomeObject", 1, 0, "QMLsomeObject");
    
            someObject * someObjectObj = new someObject;
            QThread* someObjectThread = new QThread;
            someObjectObj->moveToThread(someObjectThread);
            QObject::connect(someObjectThread, SIGNAL(started()), someObjectObj, SLOT(setupSocket()));
            someObjectThread->start();
            engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        }
    
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    
    

    main.qml:

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.VirtualKeyboard 2.4
    
    import SomeObject 1.0
    
    import QtQuick.Layouts 1.3
    import QtQuick.Extras 1.4
    import QtQuick.Controls 2.4
    import QtGraphicalEffects 1.0
    import QtQuick.Controls.Styles 1.4
    
    Window {
        id: mainWindow
        visible: true
        width: 800
        height: 480
    
       QMLsomeObject {
            id: someObject
        }
    
        Text {
            id: labelMetric1
            width: 100
            height: 20
            color: "#ffffff"
            text: someObject.metric1 ? someObject.metric1 : "0.0"
            horizontalAlignment: Text.AlignHCenter
        }
    }
    

    I am able to successfully connect to the UDP server, and see UDP data in the console via the qDebug(). As such, I know that the data is received, and have validated that m_metric1 is successfully updated in c++, however no signal is received by QML.

    What am I missing here? Do I need to setContextProperty or is it enough to use the qmlRegisterType? I would rather manage this code in someObject.h/cpp rather than in main.cpp if at all possible.



  • Your QMLsomeObject created at QML is not the same as someObjectObj created at main. They are two different instances. To pass someObjectObj into QML you should set it as global context property:

    ...
    engige.rootContext()->setContextProperty("someObject", someObjectObj);
    ...
    engine.load(...);
    

    Then in QML you can use Connections to your object and catch signals. If it is not your way, you can also read about qmlRegisterSingletonType.



  • Thanks for that, that's the part I was missing. I'm moving away from Singletons, so haven't used the Connections approach before.

    I've implemented the recommended changes, but am now having issues with threading:

    QQmlEngine: Illegal attempt to connect to someObject(0x559022c47d20) that is in a different thread than the QML engine QQmlApplicationEngine(0x7ffe1d7503f0.
    

    I understand this; GUI changes can't be made from outside the main thread. In my mind, I need to have the threaded signal received by an object in the main thread, and have the QML update from this main thread object. Is that correct?

    If so, what's the best way to go about it? I figure I have two options:

    • Keep someObject in the main thread and have a worker in a separate thread. The worker then emits signals to the main thread, where the QML is then updated.
    • Also keep someObject in the main thread and have a worker in a separate thread, but this time register the worker directly with QML (via setContextProperty).

    Option 1 seems to have more overhead involved, as all interaction between the worker and QML (and vice versa) needs to go through the someObject main thread, but perhaps this is a good design for scaleability and mutexing?



  • I've gone with option 1 for the time being (an external worker class which emits a signal to someObject in the main thread, which in turn emits a signal which is received by QML). It's all working as intended :)


Log in to reply