Jolla and SailfishOS
Linking C++ and QML code
While JavaScript (which QML is a version of) is a good tool for the presentation layer, I'm not eager to write all my business logic in it. There are many reasons why that is. But that is a discussion for some other time. Let's for now just stick with the argument, that there a many problems already solved in C/C++ libraries and there should be no reason to reinvent the wheel, just because you change the presentation layer.
There are two main reasons why I decided to have a look at Qt/QML (and hence Silica):
- Support for multiple platforms
- C/C++ binding
The multi-platform support I leave for some other project. For now, just look up the Internet if you are eager to learn more about it.
For D-Bus Inspector I was interested in how to make code written in C(++) available to a QML application.
It turns out that this not to hard to do.
C++ code
To test how to use C++ code from within QML we first need some C++ code.
Since this App is about accessing the D-Bus, I decided that the task of my first class should be to provide a list of the services available on the D-Session-Bus.
It is declared in dSessionBusServicesModel.h
- I base my service list on a
QAbstractListModel
, which provides the foundation that is needed to be able to use an object of the class as the list model for a QML widget (line 6). - For being able to use this class in the QML context, we have to use the
Q_OBJECT
macro (line 8). - Next we declare symbolic names for the fields (roles) indices of each entry in the list (line 10..12).
We only have one (user) field per entry:NameRole
- the name role = index of the field where we going to store the name of a service found. - Line 14 declares an overload-constructor.
- In line 16 we (in-line) overload the
rowCount(...)
method ofQAbstractListModel
, so it returns the number of entries in theQVector serviceNames
(see below). - Line 17 declares an overload for
QAbstractListModel
'sdata(...)
method (defined indSessionBusServicesModel.cpp
). - Line 19 declares an overload method for the
roleNames()
method ofQAbstractListModel
. - In line 21 we declare the new method
getServiceName(...)
and use the macroQ_INVOKEABLE
to make it callable from the QML context. - Finally in line 24 we define the private field
serviceNames
as aQVector
ofQStrings
, that will hold the names of the services found on the D-Session-Bus.
dSessionBusServicesModel.cpp
implements the DSessionBusServicesModel
methods declared in it's .h
file:
- We are going to access the D-Bus using the Qt library. So we import it's headers in line 1.
- Lines 5..15 hold the implementation of an overload constructor.
- In line 8 the registered service names of the D-Session-Bus are fetched, using a method of the
QDBusConnection
class. - The result of the call is assigned to the
reply
variable. - In line 9 we test if the call was successful, by calling the
isValid()
method of theQDBusReply
objectreply
. - The error handling in lines 10..11 has room for improvement, but for now we only log an error message to
qDebug
and kill the program with exit code 1. - If the call was successful we eventually reach line 13..14, in which the names of the services found are transferred from the (local)
reply.value()
QStringList
to our object'sQVector<QString> serviceNames
.
- In line 8 the registered service names of the D-Session-Bus are fetched, using a method of the
- Lines 17..21 override
QAbstractListModel
'sroleNames()
method to return aQHash
-table that describes our list model entries data structure.
Since we only have one (user) field in each entry - thename
field - our hash-table has only one entry, assigned in line 19. - Lines 23..31 override
QAbstractListModel
'sdata()
method that is used e.g. by widgets using the model to query a single field (int role
) of a specific list entry (&index
). - For a more convenient access to the service names, we declared the
getServiceName(...)
method, that is implemented in lines 33..38.
It returns aQString
with the name stored at indexi
inserviceNames
or an emptyQString
, ifi
is out of bounds ofserviceNames
.
QML type registering
So the model is pretty simple. We now just have to make it available in QML.
This is done by registering the type (class) with the QML system.
In the D-Bus Inspector project I do this in harbour-dbus-inspector.cpp
, which provide the main()
entry point for the application.
- In line 15 we include the header file of our class.
- In lines 21..24 we register the class with the QML system.
- Line 21 tells QML which class to register (must be
Q_OBJECT
). - Line 22 declares the package name and version.
Here you have to follow Jolla Harbour's naming rules for custom packages or the RPM validator will throw you out. - Line 23 declares the name you want to use in QML to access you C++ class.
- Line 21 tells QML which class to register (must be
QML type usage
All preparations down, we can now use our newly declared C++/QML type, like any native one.
- In line 4 we import the package.
- Since our class is a child of
QAbstractListModel
we can create an instance of it for themodel
property of e.g. aSilicaListView
, as done in line 60.
This creates a new instance of theDSessionBusServicesModel
class, calling the overload constructor, that in turn fills theQVector<QString> serviceNames
.
There is not much more to it. The rest is like using any other list model in QML:
delegate
in line 64 is called for each entry in the model to create a visual representation:- We can use
name
to access the value of this property of the current entry in the model (as done in line 70), since the list view has the hash-tableDSessionBusServicesModel::roleNames()
to find the index forname
, used inDSessionBusServicesModel::data(...)
to get the value (= service name).
- We can use
- When the user selects a
Label
of thelistView
,onClick()
is called (lines 75..79).
Theindex
of the label selected is available inonClick()
. We can use it to extract the name of the service associated with the label by callingDSessionBusServicesModel::getServiceName(index)
, as done e.g. in line 78.
Summary
Using:
- An arbitrary
Q_OBJECT
class as an "interface" class qmlRegisterType<...>(...)
- And than an instance of the registered type in QML
Once we have created an instance of our "interface" Q_OBJECT
, we are free to use it's Q_INVOKEABLE
methods to invoke from the QML context any C/C++ code we desire.