communicating from c++ to qml



  • Hi all
    I am new to qt qml i have tried communicating from qml to c++ by exposing a class using context or by qml registering .Now i have placed a button and i am able to communicate to the c++ .But i am unable to do the reverse that is communicating from c++ to qml.
    For example i am firing a QTimer in c++ say on every trigger a variable is incremented i want to display the same in qml how to proceed .Can anyone guide me with a simple example.Thank you


  • Lifetime Qt Champion

    Hi,

    You can use a Q_PROPERTY for that for example and each time your value is changed emit the corresponding signal. Then in qml you'll assign that property to what you want to use to show its value.

    Hope it helps



  • Hi @SGaist
    Thanks for you kind reply.I am actually using Q_PROPERTY in my c++ with read ,write and notify value but how to assign Q_PROPERTY in qml any small example will clear my doubt.
    Presently what i am doing is.

    //C++ class
    //MyTestClass
    Q_PROPERTY(int val READ val WRITE setValue NOTIFY valChanged)
    //In main
    
    //I am setting a root context and exposing this whole class.
    //in Qml
    //I have a button and in onClicked signal  i am able
    // to access the data in c++ class.
    

    But I want to trigger my signal from C++ to qml , trigger in the sense without any click event in qml.

    Thank you



  • You have two ways of doing that:

    • Get the QML item in your C++ code and change its properties/invoke its methods
    • Connect a C++ signal/slot to QML slot/signal

    Using properties and invoking methods

    QQuickView, QQuickWidget etc. offer the rootObject() method, which returns the QQuickItem that is the top-level item of your QML code. There are a couple of things you have to keep in mind:

    1. All involved have to have an objectName (not to be confused with id in QML, which is a totally different thing (very confusing omho)) including your widget (see 2 for C++ code):

      Rectangle {
        id: button // Used for accessing the item from QML
        objectName: "button" // Used for accessing the item from C++
      }
      
    2. You need to add the widget as a contextProperty to the QML
      Example:

      MyQQuickWidget::MyQQuickWidget(const QUrl &source, QWidget *parent) : QQuickWidget(parent) {
          // Add the widget to the root QML context as a context property
          this->setObjectName("quickwidget"); // quickwidget will be used to reference your widget within your QML code
          this->rootContext()->setContextProperty(this->objectName(), this);
          this->setSource(source);
          ...
      }
      

    After doing that you can simply retrieve the respective items in your C++ code. For example

    QQuickItem* button = this->rootObject()->findChild<QQuickItem*>("button");
    

    will retrieve the Rectangle I've used in 1. as an example. You should of course always check if the returned value is == Q_NULLPTR to avoid access violation if the child cannot be found and you try to use it anyway.

    After successfully retrieving your QML item you can access its properties or even invoke it's method. For example

    button->property("width").value<int>()
    

    returns the width of your item. In order to avoid a situation where you access a property that doesn't exists I strongly recommend to check the validity of the property like so

    int width = 0;
    if(button->property("width").isValid()) {
      width = button->property("width").value<int>();
      // Do something with width
      // For example increase it twice the length
       button->setProperty("width", width*2);
    }
    

    I just wrote a ListView with a ListModel where I'm controlling a lot of their aspects in my C++ code. ListModel for example has the move(int from, int to, int items) method. You can use QMetaObject::invokeMethod(...) to trigger some sort of a method that is part of the QML object. I'm quite new to QML so I don't know all the methods that are out there for Rectangle and whatnot but in the case of ListModel I did

    QObject* listModel = this->rootObject()->findChild<QObject*>("listModel"); // listModel is the objectName of my ListModel used by a ListView
    // Check if listModel retrieved correctly
    // Optional: check if "move" is a method available for the retrieved QObject - a little bit more complex then with checking if a property exists
    // ...
    
    int from = 0; // Or whatever
    int to = 10; // Or whatever
    int items = 1; // Move a single item
    
    // Use the listModel object to trigger its move(int from, int to, int items) method
    QMetaObject::invokeMethod(listModel, "move",
                              Q_ARG(int, from),
                              Q_ARG(int, to),
                              Q_ARG(int, items));
    

    Using Q_PROPERTY is also something that is used widely. If you want to expose C++ data to QML context you need to make that data a property of your C++ object etc. (you still have to add your C++ object to the QML context using its object name though).


    Using slots and signals

    I find the great difference between how slots and signals are declared in C++ and QML very annoying but since its a huge part of how Qt works one might also use this technique. ;)

    Here's an example how to:

    • Trigger a C++ slot using QML signal
    • Trigger a QML slot using a C++ signal

    main.qml (stored inside QRC file with the alias main)

    import QtQuick 2.0
    
    Rectangle {
        id: test
        width:  200
        height: 50
        x: 10
        y: 10
        signal handleText(string msg)
    
        Text {
            id: textItem
            objectName: "textItem"
            anchors.centerIn: test
            text: "Text set in QML"
    
            Connections {
                target: commObject
                onChangeText: {
                    textItem.text = newText;
                }
            }
        }
    
        MouseArea {
            hoverEnabled: false
            anchors.fill: parent
            onClicked: {
                test.handleText(textItem.text)
            }
        }
    }
    

    main.cpp

    #include <QtGui/QGuiApplication>
    #include <QtQuick/QQuickItem>
    #include <QtQuick/QQuickView>
    #include <QQmlContext>
    #include <QTimer>
    #include "CommObject.h"
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        // Create instance of object with slots and signals
        CommObject co;
        co.setObjectName("commObject");
    
        // Create view
        QQuickView view;
        // Add reference to object to view's rootContext as property
        view.rootContext()->setContextProperty("commObject", &co);
        // Load QML in view
        view.setSource(QUrl("qrc:/main"));
    
        // Retrieve top-level object which is a Rectangle
        QObject *rect = dynamic_cast<QObject*>(view.rootObject());
        // Connect QML signal to C++ slot
        QObject::connect(rect, SIGNAL(handleText(QString)),
                         &co, SLOT(slotRetrieveText(QString)));
    
        view.show();
    
        // Now lets trigger the emission of the C++ signal to the QML slot using a timer
        QTimer timer;
        timer.setInterval(5000);
        co.setText("Text changed from C++");
        QObject::connect(&timer, SIGNAL(timeout()), &co, SLOT(slotTriggerChangeText()));
        timer.start();
    
        return app.exec();
    }
    

    CommObject.h

    #ifndef COMMOBJECT_H
    #define COMMOBJECT_H
    
    #include <QObject>
    
    class CommObject : public QObject
    {
        Q_OBJECT
    public:
        explicit CommObject(QObject *parent = Q_NULLPTR);
        void setText(const QString &msg);
    
    signals:
        void changeText(const QString& newText);
    
    public slots:
        void slotTriggerChangeText();
        void slotRetrieveText(const QString &msg);
    
    private:
        QString text;
    };
    
    #endif // COMMOBJECT_H
    

    CommObject.cpp

    #include "CommObject.h"
    #include <QDebug>
    
    CommObject::CommObject(QObject *parent) :
        QObject(parent),
        text("")
    {
    }
    
    void CommObject::setText(const QString &msg)
    {
        this->text = msg;
    }
    
    void CommObject::slotTriggerChangeText()
    {
        qDebug() << "Triggering change text signal";
        emit changeText(this->text);
    }
    
    void CommObject::slotRetrieveText(const QString &msg)
    {
        qDebug() << "Received text from QML: \"" << msg << "\"";
    }
    

    Declaring signal handleText(string msg) (in QML) actually does two things - it creates a signal and a slot with the slot being automatically generated and named as onHandleText (notice the on and the uppercase first letter of your signal's name - if you miss these two things your slot will never get triggered). This applies even to signals coming from C++ context. In our case we have changeText(QString newText) so the QML slot is automatically generated as onChangeText. Another thing that is important to remember is that you HAVE TO have a name for the argument(s) the C++ signal carries and that name has to be exactly what you use inside your QML slot which receives the C++ signal. If you write

    Text {
      ...
      Connections {
                target: commObject
                onChangeText: {
                    textItem.text = msg;
                }
            }
    }
    

    it will throw a ReferenceError: msg is not defined since internally newText is what is used to access the string that the C++ signal carries. Last but not least (yet another annoying thing) - the target in the Connections item is for the source of the signal (which in our case is the C++ CommObject). The naming is extremely confusing and I hope that they change it in future releases (I'm currently using Qt 5.7).

    You can easily adapt the examples above to your scenario. I decided to write it in a more general way for future reference.



  • HI @Red-Baron

    Thank you for the detailed explanation and example I will try to implement and let you know .

    Thanks



  • @Shiv You're welcome. Do tell if you get stuck down the road. :)


Log in to reply
 

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