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

// main.cpp

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();
}
// main.qml

import bb.cascades 1.2

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

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

Accessing child objects

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.

Exposing C++ values and objects to QML

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

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

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 with the property 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 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

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

Using C++ classes in QML

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:

In both cases, you must register the class for QML, by using qmlRegisterType().

Using the attachedObjects property

Diagram illustrating how to use the attachedObjects property.

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

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.

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.

#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"
    }
}

Injecting C++ objects into QML

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

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->createRootObject<Page>();

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

mRootContainer->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 *appPage = qml->createRootObject<AbstractPane>();

    mRootContainer = appPage->findChild<Container*>("
        rootContainer");
    app->setScene(appPage);
}
 
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_ */

Related resources

Web-based training

 
 

Last modified: 2013-12-21

comments powered by Disqus