Unsolved 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 -
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 therootObject()
method, which returns theQQuickItem
that is the top-level item of your QML code. There are a couple of things you have to keep in mind:-
All involved have to have an
objectName
(not to be confused withid
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++ }
-
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 soint 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 aListModel
where I'm controlling a lot of their aspects in my C++ code.ListModel
for example has themove(int from, int to, int items)
method. You can useQMetaObject::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 forRectangle
and whatnot but in the case ofListModel
I didQObject* 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 aproperty
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 aliasmain
)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 asonHandleText
(notice theon
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 havechangeText(QString newText)
so the QML slot is automatically generated asonChangeText
. 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 writeText { ... Connections { target: commObject onChangeText: { textItem.text = msg; } } }
it will throw a
ReferenceError: msg is not defined
since internallynewText
is what is used to access the string that the C++ signal carries. Last but not least (yet another annoying thing) - thetarget
in theConnections
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. :)