Wednesday, March 28, 2012

Connecting QML to C++, First Attempt

It took me some time to figure it out all the way, so I'm recording steps to create a minimal application that will have QML UI talking to the C++ code and receiving some data. I was using Qt Creator 2.2.1, and some syntax appear to have changed in further versions, which gave me some additional headaches while trying to figure out the examples. There are some "gotchas" on the way, but eventually it will look quite simple.

In Qt Creator, select File -> New File or Project and choose Qt Quick Project -> Qt Quick Application. Give it a name, i.e. SimpleCPP. Accept the defaults. Let's first add the C++ code. Right-click the project and select Add New -> C++ -> C++ Class. Give it a name too, i.e. "test". This will add two files to the project: test.h under Headers and test.cpp under Sources. Here is the full header file:

#ifndef TEST_H
#define TEST_H

#include <QObject>

class test : public QObject
{
Q_OBJECT

public:
explicit test(QObject *parent = 0);

Q_INVOKABLE QString getString() const;
};

#endif // TEST_H

The class test inherits from QObject and exposes getString function, which is marked as Q_INVOKABLE. This registers the function with the meta-object system and, whatever that means, will allow the function to be called from QML.

Here is the test.cpp listing, and not much to say about it - it's only purpose is to return a string so we could actually verify that the C++ code gets executed.

#include "test.h"

test::test(QObject *parent): QObject(parent)
{

}

QString test::getString() const
{
QString str = "string from cpp code";
return str;
}

Next step is to register the C++ class with the main.cpp which was automatically created by the project. Add a couple of includes on the top

#include <QDeclarativeContext>
#include <QtGui/QGraphicsObject>
#include "test.h"

And these two lines after the definition of the QApplicationViewer:

test dummy;
viewer.rootContext()->setContextProperty("Dummy", &dummy);

The instance of the test class will now be known to the QML file as "Dummy". See later.

Now to modify the main.qml so it will be able to receive something from C++. In the main.qml which was automatically generated, I give the Text element the objectName property of "textObject". This is how it will be referenced by C++.

Text {
objectName: "textObject"
text: "Hello World"
anchors.centerIn: parent
}

So, here's one way to modify the text on the screen: add the following two lines to main.cpp

QObject* testText = viewer.rootObject()->findChild<QObject*>("textObject");
if(testText) testText->setProperty("text", dummy.getString());

This code finds a child object in the QML file which has an objectName of "textObject" and, if found, sets its text property to the string returned by the test C++ class. Build and run the application and verify that the "string from cpp code" is shown in the middle of the screen.

Another way to access the C++ code is to call the function right from the QML file. I can modify the main.qml this way:

Rectangle {
width: 360
height: 360
Text {
id: textQML
objectName: "textObject"
text: "Hello World"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
textQML.text = Dummy.getString();
//Qt.quit();
}
}
}

Now, when the Rectangle is clicked, the QML file calls Dummy, which is how the test class is known to the QML file. See above. Remove or comment out the last two added lines from the main.cpp and run the application again. At first nothing happens, but when you click anywhere in the window, the text changes to "string from cpp code".

The full main.cpp now looks like that:

#include <QtGui/QApplication>
#include "qmlapplicationviewer.h"
#include <QDeclarativeContext>
#include <QtGui/QGraphicsObject>
#include "test.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QmlApplicationViewer viewer;

test dummy;
viewer.rootContext()->setContextProperty("Dummy", &dummy);

viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/SimpleCPP/main.qml"));
viewer.showExpanded();

/* comment these two lines if you don't want to display the string on start up */
QObject* testText = viewer.rootObject()->findChild<QObject*>("textObject");
if(testText) testText->setProperty("text", dummy.getString());

return app.exec();
}

References

QObject Class Reference
qt, access c++ function from qml
Communication between C++ and QML by . Also posted on my website

No comments: