Development for Sailfish OS: Creating Custom QML Components in C ++
- Tutorial
Motivation
To begin with, it is worth identifying cases in which using components in C ++ makes sense. The built-in types of Qt Quick certainly offer some tools for working with sensors, with databases and notifications, which we already talked about earlier here and here , with various system capabilities such as network or Bluetooth, D-Bus. However, the capabilities of these tools are quite limited and not suitable for creating more complex applications. In this case, C ++ just allows you to implement functionality beyond what Qt Quick provides.
Another argument in favor of C ++ is the ability to use a variety of low-level libraries. In addition, complex logic is simply more correctly allocated to C ++ classes. That is what we were guided by when creating a component for displaying data on a time axis in the form of a graph.
Component Description
The appearance of the component is a coordinate plane on which various graphs are depicted. Moving on the schedule to the right and left is carried out using the buttons under the component. Data on the plane can be displayed for different time periods: for a week, month, quarter or year. Moreover, when you select one of the last three periods, the data is compressed. For example, for a month, the graph displays the average values for each day of the range, and for the quarterly and annual range, the average values for each 3 and 12 days, respectively. You can select the desired period using the buttons that are located above the coordinate plane:

Component implementation
It is worth noting that the process of creating components does not differ from that on Qt, however, there are still some specific features for Sailfish OS. They will be discussed below.
First you need to create a C ++ class inherited from QObject or its descendants and containing the Q_OBJECT macro :
#include
#include
class PlotView : public QQuickPaintedItem {
Q_OBJECT
public:
explicit PlotView(QQuickItem* parent = NULL);
void paint(QPainter* painter);
}; Our class is inherited from QQuickPaintedItem to be able to override the QQuickPaintedItem :: paint () method , which implements the process of plotting the graph using the QPainter interface . To implement visual components without drawing, you can use the QQuickItem class .
We show how to create properties that will be visible in QML, and how to work with them using the example of the periodIndex property, which is responsible for the index of the currently selected period. To declare a property, you must use the Q_PROPERTY macro :
class PlotView : public QQuickPaintedItem {
//...
Q_PROPERTY(int periodIndex READ periodIndex WRITE setPeriodIndex NOTIFY periodIndexChanged)
private:
int periodIndexValue;
//...
public:
void setPeriodIndex(int periodIndex);
int periodIndex() const;
//...
signals:
void periodIndexChanged();
};As you can see from the example, for a property you must specify its name, type and READ function to get the value of the property. You can also specify a WRITE function for assigning a property a new value. A complete list of available options can be found here . In addition, inside the class, it is best to declare a variable to store the property value (which we did in the example).
Also, to use the binding mechanism inside QML, using the NOTIFY parameter of the periodIndex property , a signal about the change in its value is determined. The call of the corresponding signal must be placed inside the PlotView :: setPeriodIndex () method :
void PlotView::setPeriodIndex(int periodIndex) {
periodIndexValue = periodIndex;
//...
emit periodIndexChanged();
//...
}For all signals declared inside our component, you can create appropriate handlers when declaring a component in QML. So, the signal handler for changing the index of the current period will look like this:
PlotView {
onPeriodIndexChanged: {}
//...
}
To create a method that will be available for calling inside QML, you must add the Q_INVOKABLE macro when declaring it . Declare a method that updates data for graphs and redraws the component:
class PlotView : public QQuickPaintedItem {
//...
public:
Q_INVOKABLE void drawPlot();
//...
};
Now we can add a call to this method to the onPeriodIndexChanged () handler
PlotView {
onPeriodIndexChanged: drawPlot()
}
Thus, when the periodIndex property changes, the coordinate plane and graphs are automatically updated.
It is worth noting that instead of using Q_INVOKABLE, the method can be declared as a slot, which will also make it visible inside QML.
The component itself is drawn, as mentioned above, using the standard classes provided by the Qt library. An example of drawing horizontal lines of the coordinate plane is as follows:
void PlotView::drawVerticalScaleLines(QPainter* painter) {
painter->setPen(QPen(QBrush(Qt::white), 1));
for (float i = minValue; i < maxValue; i += step) {
int y = calculatePlotYCoordinate(i);
painter->drawLine(0, y, width(), y);
}
}
So, to make our component available inside QML, you need to register it inside the application. Here it is worth noting several features specific to Sailfish applications, namely with their publication in the Harbor store:
- The name of your application should begin with harbor and be written with a "-". For example, harbor-plot-app
- It is necessary that the URI on which you register the components begins with the name of the application, in which "-" are replaced by ".". In our case, it will look like harbor.plot.app.plotview
A complete list of name requirements for publishing an application in the store can be found here .
Registration is performed using the qmlRegisterType () method and looks like this:
#include
#include
#include "plotview.h"
//...
int main(int argc, char *argv[]) {
QGuiApplication* app = SailfishApp::application(argc, argv);
//...
qmlRegisterType("harbour.plotapp.plotview", 1, 0, "PlotView");
//...
return app->exec();
}
Thus, using this component in QML will look something like this:
import QtQuick 2.0
import Sailfish.Silica 1.0
import harbour.plotapp.plotview 1.0
//...
PlotView {
id: plotView
periodIndex: 0
onPeriodIndexChanged: drawPlot();
//...
}
//...
It is important to note that within our components you can use classes and properties from the Sailfish Silica library , thereby creating components that satisfy the Sailfish OS UI. A good example of this is the Theme class, which provides access to standard Sailfish OS style features, such as color fonts and padding:
PlotView {
//...
width: parent.width - Theme.horizontalPageMargin * 2
anchors.horizontalCenter: parent.horizontalCenter
//...
}
Conclusion
As a result, the basic steps for creating your own components were shown. Similar steps can be used to create non-visual components for your application. More information on this topic can be found here and here . The source code for a small application to demonstrate how our component works is available on Bitbucket .
Technical issues can also be discussed on the channel of the Russian-speaking community Sailfish OS in Telegram or the VKontakte group .
Author: Daria Roychikova