Coding with QML & Qt C++: The Epic Guide to Improve Your App Development

Christian Feldbacher
Level Up Coding
Published in
11 min readMar 29, 2020

--

Application Development with QML is simple and powerful. But Qt C++ can be more performant, offers many features, and is less error-prone. This post shows you how to create apps that take advantage of both languages.

How to Communicate between C++ and QML

It is important to choose the right language for different features of your app. Integrate C++ components with QML to take your mobile app development to the next level.

Advantages of Coding in QML

Felgo Engine for Qt-based mobile apps and games uses the power of Qt Quick (QML + Javascript). This declarative scripting language is so powerful that it saves up to 60% lines of code compared to other programming languages.

Coding in QML has several advantages over development with C++:

  • Coding with QML + JavaScript is very easy to learn and allows to reduce the required amount of code a lot.
  • Language concepts like states, signals or property bindings are a huge time-saver.
  • QML makes adding animations simple. You can animate every property of your QML types with simple Animation components.
  • QML is extensible and flexible. For example, you can extend objects with new properties and features in-line. No need to create a new re-usable type for small extensions.
  • The QML Rendering Engine offers great performance. The renderer uses C++ Qt and relies on a hardware accelerated scene graph. This makes it fast enough to power even high-performance games.

When to use C++ Instead

Qt app development with C++ has advantages as well. For some scenarios, you need features that are only available with Qt C++. Also, C++ is fast and type-safe. This allows to provide the best possible performance for long-running and data-intense calculations.

For these examples, you would choose C++ over QML:

  • Native C++ code is the right choice for data-intense operations. It will outperform interpreted QML/JavaScript code.
  • C++ code is type-safe and compiled into object code. For parts where stability and security are important, using C++ helps to make your app less error-prone.
  • The Qt C++ components offer different and in some cases more features than the QML types. For example, advanced networking features.
  • It is also possible to mix C++ with native code for Android (over JNI) or iOS (Obj-C or Swift). This allows to provide such native functionality for QML as well.

Felgo Engine extends Qt for mobile app and game development. It already covers tasks like accessing native device features — so you don’t have to worry about going deep into C++ or Java and Obj-C.

Still, to get the most out of your application you can use the advantages of both languages. The full example of this guide is also available on GitHub:

How to Access a C++ Object from QML

Before we go into any details, let us start by creating a simple Felgo Apps project with Qt Creator. If you are new to Felgo and don’t know how, please consider having a look at the Getting Started Tutorial or the Felgo Designer Tutorial Video.

To sign-up and install Felgo, see the download page of the Felgo website.

Note: Adding custom C++ code is not supported when testing with QML Live. Please build your project with the classic RUN button to test the examples below.

Create a C++ Class in your Felgo Project

1. After creating a new app project, first replace the code in Main.qml with this basic structure:

Run this code on your iOS or Android device now, with Live Code Reloading

It only includes the main App window and a Page within NavigationStack to show a navigation bar that holds the page title:

2. This is enough for our basic QML setup. Let’s go on by creating a new C++ class. First, right-click the C++ “Sources” folder of your project in Qt Creator, select “Add New…” and choose the “C++ Class” template in the C++ section:

3. Then set “ MyGlobalObject” as Class Name and select “Include QObject” to include the QObject type, as the main requirement to prepare our class for usage with QML is to derive from QObject.

After completing the wizard, your project contains the class definition myglobalobject.h in the “Headers” folder and the implementation myglobalobject.cpp in the “Sources” folder of the project.

Note that the *.pro configuration now also includes the new files in the HEADERS and SOURCES configuration.

Implement the C++ Class with Signals and Slots for Usage with QML

1. Open myglobalobject.h and add some code to derive from QObject — the required include statement is already in place:

Run this code on your iOS or Android device now, with Live Code Reloading

Do not forget to also add the Q_OBJECT preprocessor macro within the class definition.

2. Now that we have a new QObject, let’s add a simple method we will later call from QML. To make the method available in QML, it is required to mark it as a public slot:

Run this code on your iOS or Android device now, with Live Code Reloading

3. To complete our basic class, open myglobalobject.cpp and add the method implementation for doSomething(). We keep it simple and only print the given text to the debug output.

Run this code on your iOS or Android device now, with Live Code Reloading

Expose an Object to QML as a Context Property

One possible way to work with a C++ object in QML is to add the object as a property to the root context of the QML tree. You can decide on a name for the property, which is then globally available in your QML code.

1. To create a new object of our class and add it as a property, we extend the main.cpp code:

Run this code on your iOS or Android device now, with Live Code Reloading

Note: It is possible to fully use the object also in C++. The above code example already includes a test-call to our doSomething method.

2. In the Main.qml of our project, we extend our Page with a Column and a first AppButton, which calls the doSomething() method when clicked:

Run this code on your iOS or Android device now, with Live Code Reloading

3. Let’s hit the green run button in Qt Creator to see how it works. The debug output shows the initial method call from main.cpp and with a click on the button another message appears:
MyGlobalObject doSomething called with “TEXT FROM QML”

That’s all we need to call methods of a C++ Object from QML. This already allows simple communication from QML to C++, but there’s even more we can do. QML supports many amazing concepts like value-changed listeners of properties and property bindings, which make development a lot easier. So let’s add a full-featured QML property to our C++ class!

Add a Class Property with Full QML Support

1. Open mylgobalobject.h and add a private counter property with a public getter and setter method.

Run this code on your iOS or Android device now, with Live Code Reloading

2. Implement the required methods and initialize the counter property in myglobalobject.cpp

Run this code on your iOS or Android device now, with Live Code Reloading

3. Similar to other properties in QML, we also want to be able to dynamically react to property changes in our QML code. In other words, we want to trigger functions in QML when the C++ property changes. Unlike the slots, which make C++ methods callable in QML, signals can be used to trigger QML code from C++. So the data flow looks like this:

Let’s add a signal counterChanged and trigger it in our setCounter implementation:

myglobalobject.h:

Run this code on your iOS or Android device now, with Live Code Reloading

myglobalobject.cpp:

Run this code on your iOS or Android device now, with Live Code Reloading

4. This simple change already allows us to add handler functions for the counterChanged() signal in QML. However, our counter property is still a normal C++ property with a getter and setter method. We can take care of that with an additional preprocessor macro:

Run this code on your iOS or Android device now, with Live Code Reloading

The Q_PROPERTY macro defines a property counter and configures the methods for reading and writing the property, as well as the signal that notifies property changes. This configuration is used by QML to work with the property.

5. Let’s extend our Main.qml and use our new counter property. The following snippet adds a new button to increase the counter and a text item to display the value:

Run this code on your iOS or Android device now, with Live Code Reloading

Our property is usable like any other property in QML. Thanks to the counterChanged we prepared, the text even updates automatically every time we change the counter.

This is how the final example looks like:

How to Register your C++ Class as a QML Type

The second possibility to use C++ components in QML is to register the class as a QML type. This allows to create objects (= instances) of your type directly in QML instead of C++. And the best thing is, the concepts with signals, slots and properties we used in the previous example still apply.

When to Use a Context Property and when a QML Object

If there’s only a single object instance you want to work within QML you can add the object as a context property. When there can be multiple instances of your class, register it as a QML type and create the objects directly in QML where you need it.

1. For this example, we will create a new type we can use in QML. Let’s start with adding a new C++ Class named MyQMLType

2. Replace the code in myqmltype.h with this implementation:

Run this code on your iOS or Android device now, with Live Code Reloading

Similar to the previous example, this type will have one public slot and a full-featured property with a getter method, a setter method and a property changed signal. The increment method increases a given integer value by one and the message property will store a string value.

3. To complete the class, add the following code for myqmltype.cpp:

Run this code on your iOS or Android device now, with Live Code Reloading

Which Parameters Can you Pass between C++ and QML

In contrast to the previous example, our new class also uses a return value for the increment slot. No further adjustments are required to receive the return value in QML. Qt automatically maps basic C++ types to QML types for all method parameters and return values.

For more information about available Qt types and corresponding QML types, please see Data Type Conversion Between QML and C++.

Register and Use your C++ QML Type

1. In your main.cpp, first add an include statement for the new class:

Run this code on your iOS or Android device now, with Live Code Reloading

2. Then use qmlRegisterType to add the class as a QML Type.

Run this code on your iOS or Android device now, with Live Code Reloading

The method takes several parameters: The module identifier and version define the required QML import to use the type. The last parameter holds the name of the QML type, which can be different from the actual C++ class name.

3. Add the import which matches the used configuration of qmlRegisterType to your Main.qml:

Run this code on your iOS or Android device now, with Live Code Reloading

4. For an example usage of our new QML Type, add the following snippet below the first example:

Run this code on your iOS or Android device now, with Live Code Reloading

The code shows that we can now use MyQMLType like any other QML item. The message property is initialized inline with a property binding, that shows the integer result of dividing myGlobalObject.counter by two. Whenever the counter changes, this expression is re-evaluated automatically.

In addition, when in turn the message changes (every 2 counter steps), we use the onMessageChanged signal to display the new message in the log output.

Similar to other QML Items, the Component.onCompleted signal is available to perform initialization steps when the QML engine creates the object. In this example, we use the increment slot to increase the counter by 1.

The AppText at the bottom simply displays the message property:

Use a Property, Signal or Slot?

As we’ve already seen in the previous examples, properties, signals and slots offer different types of communication between C++ and QML:

  • Slots allow communication from QML to C++: Slots are used to trigger C++ code from QML. You can use parameters and return values to pass data to and from C++.
  • Signals allow communication from C++ to QML: Signals are used to run QML code when certain events occur C++. You can pass parameters from C++ to QML. However, you can not return data from QML.
    In contrast to slots, signals may be handled by none, one or many components. There is no guarantee that triggering a signal in C++ will actually run QML code, unless there’s a handler defined.

Properties work both ways: Properties are read- and write-able from both C++ and QML. To support property bindings in QML, make sure to add a changed-signal for the property and do not forget to trigger the signal in C++ whenever the value changes.

How to Start Long-running C++ Operations from QML

The above example already fully covers slots and properties, but only uses a signal as part of the property configuration. To complete the example, let’s add a new slot startCppTask(), a new method doCppTask() and a new signal cppTaskFinished() to myqmltype.h:

Run this code on your iOS or Android device now, with Live Code Reloading

We will later call the slot startCppTask() from QML, which executes the internal doCppTask() method. You can e.g. run calculations in another thread at this point to avoid blocking the QML UI while performing the task. This is useful for any cpu-intense or long-lasting operation you want to handle in C++. By adding the implementation for the methods to myqmltype.cpp, we are fnished with the C++ part.

Run this code on your iOS or Android device now, with Live Code Reloading

As everything is prepared now, we can add another AppButton that starts our C++ task:

Run this code on your iOS or Android device now, with Live Code Reloading

The onCppTaskFinished() signal will notify us when the C++ part has finished calculations:

Run this code on your iOS or Android device now, with Live Code Reloading

In this example, we simply reset our global counter to zero when the signal fires, which will also update the message property of MyQMLType.

This is how the final example looks like after executing the cpp task:

Note: To handle custom signals in QML when using a context property, use the Connections QML Type. The following snippet adds a handler to the counterChanged() signal of myGlobalObject:

Run this code on your iOS or Android device now, with Live Code Reloading

When to Derive from QQuickItem instead of QObject

In all used examples, we created a C++ Class which extends QObject. However, there are some limitations to QObjects: QObjects do not have a visual representation. This means they can not hold any child items and properties regarding visual features like size, position, visibility are not available.

A QObject only holds data and logic you can use in QML as properties, signals and slots. When registering a QObject class as a type for QML, keep this restriction in mind. To create a QML Item with C++ which should support a visual representation with all default properties, derive from QQuickItem instead.

As this short introduction does not cover implementing QQuickItems, please see the Qt documentation for more information. The overview page about Integrating QML and C++ is found here.

The full source code of the project created in this guide can be found on GitHub:

More Posts Like This

Add Chat Service and Cross-Platform Leaderboard with User Profiles to Your iOS or Android App

Release 2.14.1: Update to Qt 5.9.3 | Use Live Code Reloading on macOS and Linux

How to Make Cross-Platform Mobile Apps with Qt — Felgo Apps

Originally published at https://felgo.com on March 29, 2020.

--

--

I’ve been developing native iOS & Android apps since 2007. Built games since 2002. This led to founding Felgo Framework, to make app & game development faster.