C and QML Integration
C and QML Integration
QML is designed so that data can easily be passed to and from C++. You can use the
classes in the Qt Declarative module to load and modify QML documents from C++. In
addition, QML and C++ objects can easily communicate using Qt signals and slots. To
learn more about the Qt Declarative module, see doc.qt.nokia.com/4.8/qtdeclarative.html.
doc.qt.nokia.com/4.8/qtdeclarative.html
There are a number of reasons that you might want to mix QML and C++ in your
application. For example,
e, you might have a data source for a list that you define in C++,
while the visuals for the list are in QML. Or you might define your UI in QML, but you
must access functionality in the Qt Declarative module or another Qt class.
Loading a QML document into C++ is a common way that Cascades applications are
created. You can define your UI in QML, and then load it into your C++ application to be
displayed for the user.
To load a QML document into an application, you create a QmlDocument object by calling
QmlDocument::create() and specifying the QML document that you want to load. To
display the content of the QmlDocument, you retrieve the root node using
QmlDocument::createRootNode() and display the content using Application::setScene().
QmlDocument::createRootNode(),
Application::setScene()
The following
owing example demonstrates how to create a Cascades app using QML for the UI:
main.cpp
int main(int argc, char **argv)
{
Application app(argc, argv);
QmlDocument *qml = QmlDocument::create("main.qml");
main.qml
Page {
property alias labelText: label.text
content: Container {
Label {
id: label
text: "Label"
}
Button {
objectName: "button"
text: "Button"
}
}
}
When you set the scene in an application, the root object must be a subclass of
AbstractPane. However, the root node of the QML document that you want to load doesn't
need to be an AbstractPane subclass. For example, if your main.qml file includes a
Container control at its root, you can load this document and create a scene to display it as
follows:
QmlDocument *qml = QmlDocument::create("main.qml");
Container *c = qml->createRootNode<Container>();
Application::setScene(Page::create().content(c));
After you load a QML document into C++, you can modify its properties using
QObject::setProperty(). The following example shows how you can change the text for the
labelText property:
root->setProperty("labelText", "New text");
In this example, we can change the text because we define an alias to the text property for
the Label at the root of the QML document. Without an alias to the label's text property, the
value is not accessible because it's not located at the root of the QML document.
if (newButton)
newButton->setProperty("text",
>setProperty("text", "New button text");
To be able to search for the object using findChild(), the button must have an objectName.
For more information on loading and manipulating QML elements from C++, see
doc.qt.nokia.com/4.8/qtbinding.html
There are often cases that require you to embed data from C++ into QML that you loaded
into your application. A convenient way of passing C++ data or objects to QML is by using
the QDeclarativePropertyMap class. This allows you to define key
key-value
value pairs in
i C++ that
you can bind to QML properties.
For example, this is how you could use QDeclarativePropertyMap to pass a name and
phone number to QML.
QmlDocument *qml = QmlDocument::create("main.qml");
QDeclarativePropertyMap* propertyMap = new QDeclarativePropertyMap;
ropertyMap;
propertyMap->insert("name",
>insert("name", QVariant(QString("Wes Barichak")));
propertyMap->insert("phone",
>insert("phone", QVariant(QString("519-555-0199")));
QVariant(QString("519
0199")));
qml->setContextProperty("propertyMap",
>setContextProperty("propertyMap", propertyMap);
Since QDeclarativePropertyMap accepts QVariant objects, you can pass a QObject to QML
this way as well.
In an application, it's possible to create a C++ object and expose it to QML. Before you can
pass a C++ object to QML, there are some important Qt macros that you need to implement
in the header file for the C++ class.
Q_OBJECT is a required macro for classes that use the signals and slots
mechanism.
Q_PROPERTY exposes a class property to QML. It also defines the read and write
values from and to the property, as well as the signal that is emitted with the
property
perty is changed.
Q_INVOKABLE exposes member functions so that they can be invoked from
QML.
In the example header below, the class has a Q_PROPERTY called value. It also has three
functions; value(), setValue(), and reset(). The reset() function is invokable
invoka from QML
using the Q_INVOKABLE macro. The header also defines the signal that is emitted when
the value property changes.
#ifndef MYCPPCLASS_H_
#define MYCPPCLASS_H_
#include <QObject>
#include <QMetaType>
class MyCppClass : public QObject {
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
MyCppClass();
virtual ~MyCppClass();
Q_INVOKABLE void reset();
int value();
void setValue(int i);
signals:
void valueChanged(int);
private:
int m_iValue;
};
#endif
To expose MyCppClass to QML, you create an instance of the class and use
setContextProperty() to expose it.
MyCppClass *cppObject = new MyCppClass();
qml->setContextProperty("cppObject", cppObject);
From QML, you can access the Q_PROPERTY by using the property name that is defined
in the header file. In the example below, the value property is displayed in the text property
of a label.
Label {
text: "Value of cppObject: " +
}
cppObject.value
From QML, you can also change the value of a Q_PROPERTY, connect slots to its signals,
and call Q_INVOKABLE functions. In the example below, a button is used to increase the
value property each time the user presses it. The button also defines a custom slot and
connects it to the property's valueChanged() signal. When the slot is invoked, the slot
changes the text on the button. A reset button is also used, which calls MyCppClass::reset()
to reset the value property.
Button {
id: increaseButton
text: "Increase the value"
onClicked: {
cppObject.valueChanged.connect
(increaseButton.onCppValueChanged);
cppObject.value = cppObject.value + 1;
}
function onCppValueChanged (val) {
increaseButton.text = "Set value to " + (val + 1);
}
}
Button {
id: resetButton
text: "Reset the value"
onClicked: {
cppObject.reset ()
}
}
Label {
id: valueLabel
text: "The value is " + cppObject.value
}
In both cases, you must register the class for QML, by using qmlRegisterType().
Since the attachedObjects property is a member of UIObject,, you can use it to attach a
QObject to almost any Cascades QML component that
that has a visual representation. For
example, if you want access to the functionality in the QTimer class, this is how you would
register the class for use in QML:
qmlRegisterType<QTimer>("my.library",
<QTimer>("my.library", 1, 0, "QTimer");
After you register the QTimer class, you can create and manipulate QTimer objects within
your QML document. In the following example, a QTimer object is attached to a Label.
When the page is created, the timer is started. When
When the timer is complete, the text for the
label is updated with new text.
import bb.cascades 1.0
import my.library 1.0
Page {
content: Label {
id: timerTestLabel
text: "Hello world"
textStyle {
base: SystemDefaults.TextStyles.BigText
}
attachedObjects: [
QTimer {
id: timer
interval: 1000
onTimeout :{
timerTestLabel.text = "Timer triggered"
}
}
]
}
onCreationCompleted: {
timer.start();
}
}
Subclassing CustomControl
By subclassing CustomControl,
CustomControl, you can use your C++ class in a QML document without
attaching it to another component using attachedObjects.. In the following header file, a
simple class called TextControl is declared. TextControl is composed of a text property,
functions to set and get the text property, and a signal that is emitted when the text changes.
#include <QObject>
#include <QString>
#include <bb/cascades/CustomControl>
<bb/cascades/CustomCo
class TextControl : public bb::cascades::CustomControl
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
QString text();
void setText(QString text);
signals:
void textChanged();
};
To use the class in QML, first you must register the class.
qmlRegisterType<TextControl>("my.library", 1, 0, "TextControl");
After you register the class, you can import its library into a QML document, and use it the
way you use any other QML component:
import
ort bb.cascades 1.0
import my.library 1.0
Page {
content: TextControl {
text: "Custom text control"
}
}
In previous sections, we've looked at how to define values and objects in C++ and use them
in QML. We've also looked at how to expose C++ classes to QML as attached objects or
custom controls. In this section, we'll see how to create objects in C++ and inject them
dynamically into QML.
Being able to inject C++ content into to QML is useful if you have a UI that is dynamically
generated. For example, you might want the ability to add or remove containers from your
screen during run time.
The first step is to provide an objectName for the QML component that you want to add
content to (or remove from). In tthis
his case, content is going to be added to a Container.
Page {
content: Container {
objectName: "rootContainer"
}
}
Next, you load the QML file into C++ and create a Page object using the root from the
QML file. Once you have the root of the QML file, you use findChild() to search for the
Container with the specified object name.
Now that you have the container you want to add content to, it's just a matter of adding
your content. Here's how to add another container as the last child in the parent.
container->add(Container::create()
.background(image)
.preferredSize(200,200));
You aren't restricted to just adding content though. If you wanted, you could remove
components, replace components, or insert components at a specific index.
Here's an example that demonstrates how to create containers in C++ and inject them into
QML. The QML file contains a button that when clicked, calls a function in C++ that
creates a new container and adds it to QML.
main.qml
import bb.cascades 1.0
Page {
// Allows the user to scroll vertically
content: ScrollView {
scrollViewProperties {
scrollMode: ScrollMode.Vertical
}
// Root container that containers from C++ are added to
Container {
objectName: "rootContainer"
layout: StackLayout {
leftPadding: 50
rightPadding: 50
topPadding: 50
}
// Button that calls the C++ function to add a
// new container. The selectedIndex from the drop down
// is passed to C++.
Button {
text: "Add container"
onClicked: {
injection.injectContainer();
}
}
}
}
}
app.cpp
#include "app.hpp"
#include
#include
#include
#include
#include
<bb/cascades/Application>
<bb/cascades/QmlDocument>
<bb/cascades/Color>
<bb/cascades/StackLayoutProperties>
<bb/cascades/HorizontalAlignment>
app.hpp
#ifndef APP_H
#define APP_H
#include <QObject>
#include <bb/cascades/Page>
#include <bb/cascades/Container>
using namespace bb::cascades;
class App : public QObject
{
Q_OBJECT
public:
App();
// By using Q_INVOKABLE we can call it from qml
Q_INVOKABLE void injectContainer();
private:
Page *appPage;
Container *mRootContainer;
};
#endif // ifndef APP_H