QML and C++ 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 QML objects from C++. In addition, QML and C++ objects can easily communicate using Qt signals and slots.

There are a few 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 another Qt class.

Loading a QML document from C++

Diagram illustrating how to load a QML document into C++.

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 using QmlDocument::createRootObject(), and display the content using Application::setScene().

The following example demonstrates how to create a Cascades app using QML for the UI:

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{

    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    // Create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    // Set created root object as the application scene
    Application::instance()->setScene(root);
}
// main.qml

import bb.cascades 1.3

Page {
    property alias labelText: label.text      
    Container {            
        Label {
            id: label
            text: "Label"
        }  
                       
        Button {
            objectName: "button"
            text: "Button"
        }                               
    }
}
// main.cpp

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);

    // Instatiate the class that creates and loads the QML file
    ApplicationUI appui;

    return Application::exec();
}

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));

Setting QML properties from C++

After your app loads a QML document, you can change the document's properties using QObject::setProperty(). The following example shows how you can change the text for the labelText property in the example above:

root->setProperty("labelText", "New text");

In this example, you can change the text because the app defines 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 available because it's not located at the root of the QML document.

Accessing child QML objects from 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(). The following code sample searches for the button and changes 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.

If possible, you should avoid using findChild() to access QML objects from your C++ code. This method breaks the model-view-controller pattern by tying your UI to your C++ code. Instead, you should always try to use a Q_PROPERTY instead so that you can keep all your UI logic in QML. To learn how to define a Q_PROPERTY, see Exposing C++ objects to QML.

Exposing C++ values to QML

Diagram illustrating how to embed C++ data in QML.

You can 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 technique allows you to define key-value pairs in C++ that you can bind to QML properties.

For example, you can use QDeclarativePropertyMap to pass a name and phone number to QML.

// Creates the QML document and grabs the root AbstractPane
QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
AbstractPane *root = qml->createRootObject<AbstractPane>();

// Creates the property map and then exposes it for use in QML
QDeclarativePropertyMap* propertyMap = new QDeclarativePropertyMap;
propertyMap->insert("name", QVariant(QString("Wes Barichak")));
propertyMap->insert("phone", QVariant(QString("519-555-0199"))); 
qml->setContextProperty("propertyMap", propertyMap);

// Sets the scene
Application::instance()->setScene(root);

In your QML document, you can access the values by specifying the property name and the key that you define in C++.

import bb.cascades 1.3 

Page {
    Container {
        Label {
            text: "Username: " + propertyMap.name
        }
        Label {
            text: "Phone number: " + propertyMap.number
        }
    }

}

Since QDeclarativePropertyMap accepts QVariant objects, you can pass a QObject to QML this way as well.

Exposing C++ objects to QML

Diagram illustrating how to share C++ objects.

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 when the property is changed.
  • Q_INVOKABLE exposes member functions so that they can be invoked from QML.

In the example header file 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 the Q_INVOKABLE macro. The header file also defines the signal that is emitted when the value property changes.

#ifndef MYCPPOBJECT_H_
#define MYCPPOBJECT_H_

#include <QObject>
#include <QMetaType>


class MyCppObject : public  QObject
{
	Q_OBJECT
	/*!
	 * The value of the object.
	 */
	Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)

public:
	/*!
	 * Constructs an instance of MyCppObject and sets the
	 * parent. When the parent object is destroyed,
	 * this object is as well.
	 */
	MyCppObject(QObject *parent = 0);
	virtual ~MyCppObject();

	/*!
	 * Resets the value to its default value.
	 */
	Q_INVOKABLE void reset();
	/*!
	 * Gets the current value.
	 */
	int value();
	/*!
	 * Sets the value.
	 */
	void setValue(int i);

signals:
    /*!
     * Emitted when the value changes.
     */
	void valueChanged(int);

private:
	int m_iValue;
};

#endif /* MYCPPOBJECT_H_ */

To expose MyCppClass to QML, you create an instance of the class and use setContextProperty() to expose it. The parent is set to the ApplicationUI instance using the this keyword so that when the app is destroyed, this object is as well.

MyCppObject *cppObject = new MyCppObject(this);
qml->setContextProperty("myCppObject", 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 MyCppObject: " + myCppObject.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 taps 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 MyCppObject::reset() to reset the value property.

Label {
    text: "Value of MyCppObject: " + myCppObject.value
}
Button {
    id: myButton
    text: "Increase the value"
    onClicked: {
        myCppObject.valueChanged.connect(myButton.onCppValueChanged);
        myCppObject.value = myCppObject.value + 1;
    }
    function onCppValueChanged(val) {
        myButton.text = "Set value to " + (val + 1);
    }
}
Button {
    text: "Reset the value"
    onClicked: myCppObject.reset()
}

Expand the sections below to see the full example.

#include "applicationui.hpp"

#include <bb/cascades/Application>

#include <QLocale>
#include <QTranslator>

#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);
    ApplicationUI appui;
    return Application::exec();
}
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>

class MyCppObject;

class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
};

#endif /* ApplicationUI_HPP_ */
#include <bb/cascades/AbstractPane>
#include <bb/cascades/LocaleHandler>

using namespace bb::cascades;

#include <QtDeclarative/QDeclarativeEngine>
#include <QtDeclarative/QDeclarativeComponent>

ApplicationUI::ApplicationUI() :
        QObject()
{
    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    // Create the instance of your custom object and set the parent
    // to the root of the scene. When the scene gets deleted, so does this object.
    MyCppObject *cppObject = new MyCppObject(this);
    qml->setContextProperty("myCppObject", cppObject);

    // Create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    // Set created root object as the application scene
    Application::instance()->setScene(root);
}
#ifndef MYCPPOBJECT_H_
#define MYCPPOBJECT_H_

#include <QObject>
#include <QMetaType>


class MyCppObject : public  QObject
{
	Q_OBJECT
	/*!
	 * The value of the object.
	 */
	Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)

public:
	/*!
	 * Constructs an instance of MyCppObject and sets the
	 * parent. When the parent object is destroyed,
	 * this object is as well.
	 */
	MyCppObject(QObject *parent = 0);
	virtual ~MyCppObject();

	/*!
	 * Resets the value to its default value.
	 */
	Q_INVOKABLE void reset();
	/*!
	 * Gets the current value.
	 */
	int value();
	/*!
	 * Sets the value.
	 */
	void setValue(int i);

signals:
    /*!
     * Emitted when the value changes.
     */
	void valueChanged(int);

private:
	int m_iValue;
};

#endif /* MYCPPOBJECT_H_ */
#include "MyCppObject.h"
#include <QtCore/qobject.h>

MyCppObject::MyCppObject(QObject *parent)
    : QObject(parent)
{
	m_iValue = 0;
}

MyCppObject::~MyCppObject()
{
}


int MyCppObject::value()
{
	return m_iValue;
}

void MyCppObject::setValue(int i)
{
	m_iValue = i;
	emit valueChanged(m_iValue);
}

void MyCppObject::reset()
{
	m_iValue = 0;
	emit valueChanged(m_iValue);
}
import bb.cascades 1.3 

Page {
    Container {
        Label {
            text: "Value of MyCppObject: " + myCppObject.value
        }
        Button {
            id: myButton
            text: "Increase the value"
            onClicked: {
                myCppObject.valueChanged.connect(myButton.onCppValueChanged);
                myCppObject.value = myCppObject.value + 1;
            }
            function onCppValueChanged(val) {
                myButton.text = "Set value to " + (val + 1);
            }
        }
        Button {
            text: "Reset the value"
            onClicked: myCppObject.reset()
        }
    }
}

Using C++ classes in QML

Diagram illustrating how to use the attachedObjects property.

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 the Cascades framework, there are a few ways that you can use a C++ class in QML directly, whether it's a class from the core Qt library, or a class that you define yourself:

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 the QTimer class, this is how you would register the class for use in QML:

qmlRegisterType<QTimer>("my.library", 1, 0, "QTimer");

// Create an load the scene
QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
AbstractPane *root = qml->createRootObject<AbstractPane>();
Application::instance()->setScene(root);

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 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 the Signals and slots tutorial.

Best practices for using attachedObjects

For objects that you include in QML using attachedObjects, ensure that their constructors don't perform any long-running tasks. When you create and load a QML file any objects defined in attachedObjects are also created, so if their constructors perform any long-running tasks, this can block the UI thread. Make sure that these long-running tasks get initiated only after the UI is created and loaded.

Subclassing CustomControl

Diagram illustrating how to subclass a Cascades custom control.

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 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.

#ifndef CUSTOMTEXTCONTROL_H_
#define CUSTOMTEXTCONTROL_H_

#include <QObject>
#include <QString>
#include <bb/cascades/CustomControl>
#include <bb/cascades/TextField>

using namespace bb::cascades;

class CustomTextControl: public bb::cascades::CustomControl {
	Q_OBJECT
	Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

public:
	/*!
	 * Constructs the text control. This is where you'd instantiate its
	 * visual components.
	 */
	CustomTextControl();
	virtual ~CustomTextControl();

	/*!
	 * Getter and setter for the text for the control
	 */
	QString text();
	void setText(QString text);

signals:
	/*!
	 * Emitted when the text changes
	 */
	void textChanged();

private:
    TextField *mTextField;
};

#endif / CUSTOMTEXTCONTROL_H_ /;

To use the class in QML, first you must register the class.

qmlRegisterType<CustomTextControl>("custom.control.lib", 1, 0, "CustomTextControl");

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:

Page {
    Container {
        CustomTextControl {
            text: "My custom text"
        }
    }
}

Expand the sections below to see the full example.

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);
    ApplicationUI appui;
    return Application::exec();
}
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>

class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
};

#endif /* ApplicationUI_HPP_ */
#include "applicationui.hpp"
#include "CustomTextControl.h"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    qmlRegisterType<CustomTextControl>("custom.control.lib", 1,
         0, "CustomTextControl");

    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    AbstractPane *root = qml->createRootObject<AbstractPane>();
    Application::instance()->setScene(root);
}
#ifndef CUSTOMTEXTCONTROL_H_
#define CUSTOMTEXTCONTROL_H_

#include <QObject>
#include <QString>
#include <bb/cascades/CustomControl>
#include <bb/cascades/TextField>

using namespace bb::cascades;

class CustomTextControl: public bb::cascades::CustomControl {
	Q_OBJECT
	Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

public:
	/*!
	 * Constructs the text control. This is where you'd instantiate its
	 * visual components.
	 */
	CustomTextControl();
	virtual ~CustomTextControl();
	/*!
	 * Getter and setter for the text for the control
	 */
	QString text();
	void setText(QString text);

signals:
	/*!
	 * Emitted when the text changes
	 */
	void textChanged();

private:
    TextField *mTextField;
};

#endif / CUSTOMTEXTCONTROL_H_ /
#include "CustomTextControl.h"
#include <bb/cascades/Container>
#include <bb/cascades/Color>
#include <bb/cascades/TextField>
#include <bb/cascades/DockLayout>

using namespace bb::cascades;

CustomTextControl::CustomTextControl()
{
	Container *rootContainer = Container::create()
		.background(Color::Blue)
		.layout(DockLayout::create());

	mTextField = TextField::create()
		.text("Put some text here.")
		.horizontal(HorizontalAlignment::Center)
		.vertical(VerticalAlignment::Center)
		.preferredWidth(350);

	rootContainer->add(mTextField);

	setRoot(rootContainer);
}

CustomTextControl::~CustomTextControl() {
}

QString CustomTextControl::text()
{
	return mTextField->text();
}

void CustomTextControl::setText(QString pText)
{
	mTextField->setText(pText);
}
import bb.cascades 1.3
import custom.control.lib 1.0

Page {
    Container {
        CustomTextControl {
            text: "My custom text"
        }
    }
}

Injecting C++ objects into QML

Diagram illustrating how to inject C++ objects into QML.

In previous sections, you learned how to define values and objects in C++ and use them in QML. You've also seen how to expose C++ classes to QML as attached objects or custom controls. In this section, you learn 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 runtime.

The first step is to provide an objectName for the QML component that you want to add content to (or remove content from). In this case, content is going to be added to a  Container.

Container {
    objectName: "rootContainer"
}

Next, you load the QML file into C++ and create a Page object using the root from the QML file. When 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("asset:///main.qml").parent(this);

    // 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);

    // Create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    // Retrieves the root container from the page
    mRootContainer = root->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.

mRootContainer->add(Container::create()
        .background(Color::Red)
        .preferredSize(200,200)
        .bottomMargin(20));

You aren't restricted to just adding content though. If you want, you can 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 tapped, calls a function in C++ that creates a container and adds it to QML.

#include "applicationui.hpp"
#include <bb/cascades/Application>
#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);
    ApplicationUI appui;
    return Application::exec();
}
#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/Color>
#include <bb/cascades/Container>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    // 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);

    // Create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    // Retrieves the root container from the page
    mRootContainer = root->findChild<Container*>("rootContainer");

    Application::instance()->setScene(root);
}

void ApplicationUI::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));
}
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>

namespace bb
{
    namespace cascades
    {
        class Page;
        class Container;
    }
}
/*!
 * @brief Application UI object
 *
 * Use this object to create and init app UI, to create context objects, to register the new meta types etc.
 */
class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
    Q_INVOKABLE void injectContainer();

private:
    bb::cascades::Page *appPage;
    bb::cascades::Container *mRootContainer;
};

#endif /* ApplicationUI_HPP_ */
import bb.cascades 1.3
 
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();        
                }
            }
        }
    }
}

Related resources

Web-based training

 
 

Last modified: 2014-09-29



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus