Signalling between QML and the C++ back-end

Now we know how the signals and slots are used in the Qt programming domain. Next step is to connect the QML front-end to the C++ back-end so that we can do our heavy number crunching in the C++ side.

The communication should be bidirectional so that we can pass data from the QML side to the C++ back-end and vice versa.

In the C++ classes we can add slots and signals as described in my previous post. If we create a context property for each object we can then access the slots and signals from the QML elements.

Let’s create a simple counter application with a QML user interface. The application structure is depicted in the figure below. In the C++ side we will create one simple class (MyClass) that contains 3 slots (setValue, increment, decrement) and one signal (valueChanged). In the QML side we will add 3 buttons that will call the slot methods when clicked. In addition we will add one label that will display the counter value (stored in the myValue variable in the MyClass object).

MyClass-05

Here is the MyClass declaration (in myclass.h). Note that the signal valueChanged has one named argument (int value). If we want to pass some data with the emitted signal to the QML side then we must name the argument in the signal prototype declaration (this was not needed when connecting to another C++ class instance). In the QML element we must refer to this argument name when reading the data from the signal.

#include <QObject>

class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass();

signals:
    void valueChanged(int value);

public slots:
    void setValue(int v);
    void increment();
    void decrement();

private:
    int myValue;
};

#endif // MYCLASS_H

The MyClass implementation (myclass.cpp) is shown below. Here we will just define the slot methods (+ the class constructor). Each slot method will emit the valueChanged signal to display the counter value.

#include "myclass.h"

MyClass::MyClass()
{
    myValue = -1;
}

void MyClass::setValue(int v)
{
    if (v == myValue)
        return;
    myValue = v;
    emit valueChanged(myValue);
}

void MyClass::increment()
{
    emit valueChanged(++myValue);
}

void MyClass::decrement()
{
    emit valueChanged(--myValue);
}

The main program is pretty basic. We will instantiate MyClass and then add the object pointer (counter) to the root context (naming the property also to “counter”). Finally we will call the setValue method to initialize the counter to 0 (the constructor set the value to -1). This ensures that we will get the first signal emitted at this point and the display will be initialized, too.

#include <QtGui/QApplication>
#include <QtDeclarative/QDeclarativeContext>
#include "qmlapplicationviewer.h"
#include "myclass.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> app(createApplication(argc, argv));
    MyClass *counter;

    QmlApplicationViewer viewer;

    counter = new MyClass;
    viewer.rootContext()->setContextProperty("counter",counter);

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

    counter->setValue(0);
    return app->exec();
}

Ok, next the QML code.

The main.qml file is just the normal template given by the Qt Quick Application project.

import QtQuick 1.1
import com.nokia.meego 1.0

PageStackWindow {
    id: appWindow

    initialPage: mainPage

    MainPage {
        id: mainPage
    }

    ToolBarLayout {
        id: commonTools
        visible: true
        ToolIcon {
            platformIconId: "toolbar-view-menu"
            anchors.right: (parent === undefined) ? undefined : parent.right
            onClicked: (myMenu.status === DialogStatus.Closed) ? myMenu.open() : myMenu.close()
        }
    }

    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem { text: qsTr("Sample menu item") }
        }
    }
}

The MainPage.qml file contains the buttons and labels (and a gradient background rectangle copied from my earlier project). The label named MyLabel will display the counter value. To connect to the valueChanged signal we need to add a Connections block to the label definition. The Connections block needs two properties:

  1. target = the signal source object (the context property “counter” in this case).
  2. event name = on<SignalName> (“onValueChanged” in this case).

Note that we must capitalize the first letter of the signal name when adding the “on” prefix. And also note that we can refer to the signal parameter by using the formal parameter name (“value”) defined in the signal prototype in myclass.h.

import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    tools: commonTools
    Rectangle {
        id: wallpaper
        gradient: Gradient {
            GradientStop {
                position: 0.000
                color: "gainsboro"
            }
            GradientStop {
                position: 1.000
                color: "slategray"
            }
        }
        anchors.fill: parent
    }
    Label {
        color: "white"
        font.pixelSize: 46
        text : "TestApp9"
        anchors.top: parent.top
    }
    Rectangle {
        id: myRectangle
        color: "slategray"
        width: parent.width
        height: 100
        anchors.centerIn: parent
    }
    Label {
        id: myLabel
        color: "red"
        font.pixelSize: 92
        text : ""
        anchors.centerIn: myRectangle
        Connections {
            target: counter
            onValueChanged: {
                myLabel.text = value; // signal formal parameter name
            }
        }
    }
    Button{
        id: resetButton
        anchors.horizontalCenter: parent.horizontalCenter
        y: parent.height*3/4
        text: qsTr("Reset")
        onClicked: {
            counter.setValue(0)
        }
    }
    Button{
        id: incrementButton
        anchors.horizontalCenter: parent.horizontalCenter
        y: parent.height*5/8
        text: qsTr("Increment")
        onClicked: {
            counter.increment()
        }
    }
    Button{
        id: decrementButton
        anchors.horizontalCenter: parent.horizontalCenter
        y: parent.height*7/8
        text: qsTr("Decrement")
        onClicked: {
            counter.decrement()
        }
    }
}

Let’s compile and run the application. The counter will start at zero. By clicking the “Increment” and “Decrement” buttons the counter value can be changed. By clicking the “Reset” button the value will be set back to zero.

MyClass-06MyClass-07MyClass-08

The project is available for download from:

https://github.com/n9dyfi/TestApp9.git

Leave a Reply

Your email address will not be published.