http://developer.blackberry.com/cascades/reference/bb__cascades__abstractpane.html
QML is designed so that data can easily be passed to and from C++. You can use the classes in theQt Declarative module to load and modify QML documents from C++. In addition, QML and C++ objects can easily communicate using Qt signals and slots.
There are a number of reasons that you might want to mix QML and C++ in your application. For example, 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 anotherQt 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 asset that you want to load. To display the content of the QmlDocument, you retrieve the root node usingQmlDocument::createRootObject(), and display the content usingApplication::setScene().
The following example demonstrates how to create a Cascades app using QML for the UI:
int
main(
int
argc,
char
**argv)
{
Application app(argc, argv);
QmlDocument *qml = QmlDocument::create(
"asset:///main.qml"
).parent(&app);
AbstractPane *root = qml->createRootObject<AbstractPane>();
app.setScene(root);
return
Application::exec();
}
|
Page {
property alias labelText: label.text
Container {
Label {
id: label
text:
"Label"
}
Button {
objectName:
"button"
text:
"Button"
}
}
}
|
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 thelabelText property:
root->setProperty(
"labelText"
,
"New text"
);
|
In this example, we can change the text because we define an alias to the text property for theLabel at the root of the QML document. Without an alias to the label'stext property, the value is not accessible because it's not located at the root of the QML document.
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(
"asset:///main.qml"
);
Container *c = qml->createRootObject<Container>();
Application::instance()->setScene(Page::create().content(c));
|
Since QML documents are organized in a tree hierarchy, another option for accessing child properties and objects is to search for the object by using QObject::findChild(). In this example, we do a search for the button and change its text property:
QObject *newButton = root->findChild<QObject*>(
"button"
);
if
(newButton)
newButton->setProperty(
"text"
,
"New button text"
);
|
To be able to search for the object using findChild(), the button must have an objectName.
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 theQDeclarativePropertyMap class. This allows you to define key-value pairs in 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(
"asset:///main.qml"
);
QDeclarativePropertyMap* propertyMap =
new
QDeclarativePropertyMap;
propertyMap->insert(
"name"
, QVariant(QString(
"Wes Barichak"
)));
propertyMap->insert(
"phone"
, QVariant(QString(
"519-555-0199"
)));
qml->setContextProperty(
"propertyMap"
, propertyMap);
|
In your QML document, you can access the values by specifying the property name and the key that you define in C++.
Label {
text:
"User name: "
+ propertyMap.name
}
Label {
text:
"Phone number: "
+ propertyMap.phone
}
|
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 importantQt macros that you need to implement in the header file for the C++ class.
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 from QML using theQ_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
|
And this is how you could implement the resulting class.
#include "MyCppClass.h"
MyCppClass::MyCppClass()
{
m_iValue = 0;
}
MyCppClass::~MyCppClass()
{
}
int
MyCppClass::value()
{
return
m_iValue;
}
void
MyCppClass::setValue(
int
i)
{
m_iValue = i;
emit valueChanged(m_iValue);
}
void
MyCppClass::reset()
{
m_iValue = 0;
emit valueChanged(m_iValue);
}
|
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 callQ_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 some instances, instead of passing a C++ object or values to QML, you may want to use the C++ class in QML directly. In theCascades framework, there are a few ways that you can use a C++ class in QML directly, whether it's a class from the coreQt library, or a class that you define yourself:
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 has a visual representation. For example, if you want access to the functionality in theQTimer class, this is how you would register the class for use in QML:
qmlRegisterType<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, aQTimer object is attached to aLabel. When the page is created, the timer is started. 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 {
Label {
id: timerTestLabel
text:
"Hello world"
attachedObjects: [
QTimer {
id: timer
interval: 1000
onTimeout :{
timerTestLabel.text =
"Timer triggered"
}
}
]
}
onCreationCompleted: {
timer.start();
}
}
|
For another example demonstrating how to use the attachedObjects property in an application, see theSignals and slots tutorial.
By subclassing 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 calledTextControl 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>
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 bb.cascades 1.0
import my.library 1.0
Page {
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 this case, content is going to be added to a Container.
Page {
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.
QmlDocument *qml = QmlDocument::create(
"main.qml"
);
// Sets the context property that we want to use from within QML.
// Functions exposed via Q_INVOKABLE will be found with this
// property name and the name of the function.
qml->setContextProperty(
"injection"
,
this
);
// Creates the root using the page node
Page *appPage = qml->createRootNode<Page>();
// Retrieves the root container from the page
Container *container = appPage->findChild<Container*>(
"rootContainer"
);
|
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.
import bb.cascades 1.0
Page {
// Allows the user to scroll vertically
ScrollView {
scrollViewProperties {
scrollMode: ScrollMode.Vertical
}
// Root container that containers from C++ are added to
Container {
objectName:
"rootContainer"
layout: StackLayout {}
// 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();
}
}
}
}
}
|
#include "TestApp.hpp"
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
using
namespace
bb::cascades;
TestApp::TestApp(bb::cascades::Application *app)
: QObject(app)
{
QmlDocument *qml = QmlDocument::create(
"asset:///main.qml"
).parent(
this
);
qml->setContextProperty(
"injection"
,
this
);
AbstractPane *root = qml->createRootObject<AbstractPane>();
app->setScene(root);
}
void
TestApp::injectContainer()
{
// Creates the container and adds it to the root
// container in qml
mRootContainer->add(Container::create()
.background(Color::Red)
.preferredSize(200,200)
.bottomMargin(20)
.horizontal(HorizontalAlignment::Center));
}
|
#ifndef TestApp_HPP_
#define TestApp_HPP_
#include <QObject>
namespace
bb {
namespace
cascades {
class
Application; }}
class
TestApp :
public
QObject
{
Q_OBJECT
public
:
TestApp(bb::cascades::Application *app);
virtual
~Test() {}
// By using Q_INVOKABLE we can call it from qml
Q_INVOKABLE
void
injectContainer();
private
:
Page *appPage;
Container *mRootContainer;
};
#endif /* Test_Hpp_ */
|