How to catch the signal emitted from C++ global function in QML



  • Hi all,

    I am facing problem in catching a signal emitted from a cpp class global function in QML. I have registered the class using qmlRegisterType.
    further guidance is required in this matter.
    Thanks.

    my .h file
    the code is ...

    #ifndef GLOBALTEST_H
    #define GLOBALTEST_H
    
    #include <QObject>
    #include <QDebug>
    
    int output(int ,int);
    class Globaltest : public QObject
    {
        Q_OBJECT
    public:
        explicit Globaltest(QObject *parent = 0);
    
    signals:
        void testSignal();
    
    public slots:
        Q_INVOKABLE void test();
    };
    
    #endif // GLOBALTEST_H
    
    

    my .cpp class

    #include "globaltest.h"
    
    Globaltest::Globaltest(QObject *parent) : QObject(parent)
    {
    }
    
    void Globaltest::test()
    {
        qDebug()<<"test called"<<endl;
        int x= output(20,20);
        qDebug()<<x<<endl;
    }
    
    int output(int a, int b)
    {
        qDebug()<<"output function called"<<endl;
        return(a+b);
        Globaltest t;
        t.testSignal();
    }
    

    my main.cpp

    #include <QGuiApplication>
    #include <QApplication>
    #include <QQmlApplicationEngine>
    
    #include "globaltest.h"
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        qmlRegisterType<Globaltest>("com.globalCpp",1,0,"Globaltest");
    
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    

    my main.qml

    import QtQuick 2.7
    import QtQuick.Window 2.2
    import com.globalCpp 1.0
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        Globaltest{
            id: global
            onTestSignal: {
                console.log("signal catched");
            }
        }
    
        MouseArea{
            id: mouse
            anchors.fill: parent
            onClicked: {
                global.test()
            }
        }
    }
    

  • Moderators

    @Naveen_D said in How to catch the signal emitted from C++ global function in QML:

    the problem is you are creating 2 different instances, the first one here

    int output(int a, int b)
    {
    qDebug()<<"output function called"<<endl;
    return(a+b);
    Globaltest t;
    t.testSignal();
    }

    and the a compleltey separate one in QML here:

    Globaltest{
    id: global
    onTestSignal: {
    console.log("signal catched");
    }
    }

    There are multiple ways to solve this.
    You need to create a global instance of your object.
    Then you can set this global object as QML context property, or you can create a second wrapper class which operates (forwards it's calls and signals) on the global instance. This wrapper class is then registered to QML.



  • @raven-worx
    Thank you for your reply...i am new to this
    can you please explain me what changes i have to make in the above example so that i can get an idea. thanks


  • Moderators

    int output(int a, int b)
    {
    qDebug()<<"output function called"<<endl;
    return(a+b);
    Globaltest t;
    t.testSignal();
    }
    

    You are creating Globaltest instance after return statement - it will never be called.

    As raven-workx mentioned, there are several ways to make it work.

    • make Globaltest a singleton (discouraged - there is no reason to do it in this case)
    • make one instance of Globaltest in your main.cpp and use it in both QML and C++. Make it available in QML using engine->rootContext()->setContextProperty(). You can respond to signals from that object in QML by using Connections QML type
    • use Globaltest as a QML singleton
    • etc.

    All that is described in great detail in Qt documentation: here and here.



  • @sierdzio i am posting the updated code here...please correct me if i am wrong somewhere...thanks
    .cpp

    #include "globaltest.h"
    
    Globaltest testobj;
    
    Globaltest::Globaltest(QObject *parent) : QObject(parent)
    {
    }
    
    void Globaltest::test()
    {
        qDebug()<<"test called"<<endl;
        int x= output(20,20);
        qDebug()<<x<<endl;
    }
    
    int output(int a, int b)
    {
        qDebug()<<"output function called"<<endl;
        testobj.testSignal();
        return(a+b);
    }
    

    main.cpp

    #include <QGuiApplication>
    #include <QApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include "globaltest.h"
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        Globaltest testobj;
    
        qmlRegisterType<Globaltest>("com.globalCpp",1,0,"Globaltest");
    
    
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("TestObject",&testobj);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    

    main.qml

    import QtQuick 2.7
    import QtQuick.Window 2.2
    import com.globalCpp 1.0
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        Globaltest{
            id: global
            onTestSignal: {
                console.log("signal catched");
            }
        }
    
        MouseArea{
            id: mouse
            anchors.fill: parent
            onClicked: {
                global.test()
            }
        }
    }
    

  • Moderators

    Now you have 3 separate instances of Globaltest, and you want a single one :-)

    Try this:

    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        Globaltest testobj;
    
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("TestObject",&testobj);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        testobj.test(); // This emits the signal. Not the nicest solution, but should work for now.
    
        return app.exec();
    }
    
    // QML
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        Connections {
          target: TestObject
          onTestSignal: console.log("Signal caught")
       }
    
        MouseArea{
            id: mouse
            anchors.fill: parent
            onClicked: {
                TestObject.test() // Emits from QML
            }
        }
    }
    


  • only using the global variable, is it possible to make signal slot communication from cpp to Qml?


  • Moderators

    @Naveen_D
    @sierdzio already showed you how to

    Connections {
    target: TestObject
    onTestSignal: console.log("Signal caught")
    }



  • @raven-worx i tried as said by @sierdzio but i am not able to catch the signal.
    here is the code.
    .cpp

    #include "globaltest.h"
    
    Globaltest testobj;
    Globaltest::Globaltest(QObject *parent) : QObject(parent)
    {
    }
    
    void Globaltest::test()
    {
        qDebug()<<"test called"<<endl;
        output();
    }
    
    void output()
    {
        qDebug()<<"output function called"<<endl;
        testobj.testSignal();
    }
    
    

    main.cpp

    #include <QGuiApplication>
    #include <QApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include "globaltest.h"
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        Globaltest testobj;
    
        qmlRegisterType<Globaltest>("com.globalCpp",1,0,"Globaltest");
    
    
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("TestObject",&testobj);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    
    

    main.qml

    import QtQuick 2.7
    import QtQuick.Window 2.2
    import com.globalCpp 1.0
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        Connections {
              target: TestObject
              onTestSignal: console.log("Signal caught")
           }
        MouseArea{
                id: mouse
                anchors.fill: parent
                onClicked: {
                    TestObject.test()
                }
            }
    }
    

  • Moderators

    @Naveen_D
    you are creating two different instances of your your object. One in the main() and one in the cpp file.
    You need to ensure that you operate on the same instance. Take a look at the Singleton pattern:

    class Globaltest {
    public:
        static Globaltest* Globaltest::Instance() {  //static method
              static Globaltest* instance = new Globaltest;
              return instance;
        }
    private:
         Globaltest();  //hidden constructor --> only allow to use our static Instance() method
    };
    
    


  • @raven-worx without using singleton...it is not possible? becoz i have a global object of that class...


  • Moderators

    @Naveen_D
    sure, but you need to make sure you are using the right object instance. The singleton is an easy and understandable mechanism, which prevents some possible poitfalls.
    As i said in your posted example you are using 2 different instances. Define the global object as global static object in the header file instead of the source file.



  • @raven-worx
    if i define the global object as global static object in the header file instead of the source file. it is not necessary to again create an object in main.cpp and use context property?..without this i can directly catch the signal using signal handler in qml ?


  • Moderators

    @Naveen_D said in How to catch the signal emitted from C++ global function in QML:

    it is not necessary to again create an object in main.cpp and use context property?

    the context property of course is still necessary. But not the duplicate object creation. Instead reference the static global one from the header file wherever you need it.



  • @raven-worx i want to use this global obj in global function to emit the signal from that function.
    the way i am declaring the global function is right ?

    #ifndef GLOBALTEST_H
    #define GLOBALTEST_H
    
    #include <QObject>
    #include <QDebug>
    
    void output();
    class Globaltest : public QObject
    {
        Q_OBJECT
        static Globaltest *s_instance;
    public:
        explicit Globaltest(QObject *parent = 0);
    
    signals:
        void testSignal();
    
    
    public slots:
        Q_INVOKABLE void test();
    };
    
    #endif // GLOBALTEST_H
    


  • Hi, as you said i have only one instance of the object in the below code but when i use this object in set context property i am getting QVariant error...the error is

    /home/ubuntu/Qt5.7.0/5.7/gcc_64/include/QtCore/qvariant.h:471: error: 'QVariant::QVariant(void)' is private
    inline QVariant(void ) Q_DECL_EQ_DELETE;

    and if i remove the '&' and run the code i get this qml error
    QML debugging is enabled. Only use this in a safe environment.
    qrc:/main.qml:20: TypeError: Cannot call method 'test' of null

    can anyone tell what is wrong in the code and what i need to change
    Thanks

    .h

    #ifndef GLOBALTEST_H
    #define GLOBALTEST_H
    
    #include <QObject>
    #include <QDebug>
    
    void output();
    class Globaltest : public QObject
    {
        Q_OBJECT
    
    public:
        explicit Globaltest(QObject *parent = 0);
    
    signals:
        void testSignal();
    
    
    public slots:
        Q_INVOKABLE void test();
    };
    extern Globaltest *MySender;
    #endif // GLOBALTEST_H
    
    

    .cpp

    #include "globaltest.h"
    
    Globaltest *MySender;
    Globaltest::Globaltest(QObject *parent) : QObject(parent)
    {
    }
    
    //Globaltest *Globaltest::s_instance = 0;
    
    void Globaltest::test()
    {
        qDebug()<<"test called"<<endl;
        output();
    }
    
    void output()
    {
        qDebug()<<"output function called"<<endl;
        MySender= new Globaltest;
        MySender->testSignal();
    }
    
    

    main.cpp

    #include <QGuiApplication>
    #include <QApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include "globaltest.h"
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
    
        qmlRegisterType<Globaltest>("com.globalCpp",1,0,"Globaltest");
    
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("TestObject",&MySender); //Getting error here //
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    
    

    main.qml

    import QtQuick 2.7
    import QtQuick.Window 2.2
    import com.globalCpp 1.0
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        Connections {
              target: TestObject
              onTestSignal: console.log("Signal caught")
           }
    
        MouseArea{
                id: mouse
                anchors.fill: parent
                onClicked: {
                    TestObject.test()
                }
            }
    }
    
    

  • Moderators

    You are declaring MySender to be a pointer, so using '&' in setContextProperty() is wrong. '&' is a way to extract a pointer from non-pointer variable. Here you already have a pointer, so no need for '&'.

    You declare MySender variable, but you never set it to any value (meaning: you never create an object of type Globaltest). So QML engine - rightfully - complains that the object is null (== not set).

    You need to set the value of MySender somewhere in your code (before call to setContextProperty), in other words:

    MySender = new GlobalTest;
    


  • @sierdzio ya that i came to know since i am using pointer var no need of '&' but in cpp before emitting a signal i am allocating memory for that global object...


  • Moderators

    @Naveen_D
    to add up to @sierdzio
    also remove the line MySender= new Globaltest; from your output() method.
    Instead call it once before you set it as context property.


  • Moderators

    @raven-worx said in How to catch the signal emitted from C++ global function in QML:

    also remove the line MySender= new Globaltest; from your output() method.

    Doh, I did not even look at that method when writing my reply. Good hint, thanks for being so attentive @raven-worx :-)



  • @raven-worx @sierdzio
    yes when i allocate memory before setcontext property it worked...
    thanks alot for your help...:-)


  • Moderators

    Cool, good to know :-) Happy coding!


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.