C
Integrating C++ code with QML
Overview
The Qt Quick Ultralite applications use C++ code for business logic, interfacing with hardware, and working with other software modules. The C++ part of the application interacts with the QML presentation layer. Therefore the C++ classes as well as their functions, properties, and signals are exposed to QML code.
Interface headers
An application can contain interface headers that describe the components that shall be exposed to QML. These are normal C++ header files that are registered in QmlProject using the InterfaceFiles.files property.
When you register a header with the macro, it has two effects on the build:
- the
qmlinterfacegenerator
tool will be run on the header, creating one or more QML interface files - the generated files will be fed to
qmltocpp
when translating the project's QML code
The steps enable QML code to make use of the C++ interfaces.
Exporting C++ APIs
Components
Class and struct declarations are the building blocks for exposing C++ functionality to QML. Each exported C++ class declaration defines a QML component of the same name.
The following example demonstrates how to use components:
#include <qul/object.h> #include <qul/property.h> #include <qul/signal.h> struct BigState : public Qul::Object { Qul::Property<int> bigness; int feed(int amount); Qul::Signal<void()> gotFood; };
Item { BigState { id: bigState bigness: 61 onGotFood: bigness = 99 } Component.onCompleted: bigState.feed(3) }
In order to be picked up by qmlinterfacegenerator, the declared class
- must at least indirectly inherit from Qul::Object
- must be default constructible
Some base classes have special meaning. For more detailed information, see Singletons and Models
Singletons
If the class derives directly from Qul::Singleton<Itself>, it is marked with pragma Singleton and an instance is available globally.
The following example demonstrates how to use Singletons:
#include <qul/singleton.h> struct MyApi : public Qul::Singleton<MyApi> { void start(); };
Item { Component.onCompleted: MyApi.start() }
Models
The class can directly derive from from Qul::ListModel<T> to export a list model. See the Qul::ListModel documentation for details.
Functions
All public non-overloaded member functions in an exported class declaration are available in QML. The function's parameter types and return type are mapped to their QML counterparts.
The following example demonstrates how to use functions:
struct DirLookup : public Qul::Object { Qul::Property<std::string> basePath; int lookup(const std::string &path); }; struct Simulator : public Qul::Singleton<Simulator> { void run(DirLookup *lookup); };
Item { DirLookup { id: look basePath: "/objects" } Component.onCompleted: Simulator.run(look) }
Properties
Public fields in exported class declarations become component properties if the field has the type Qul::Property<T>. Here T describes the property's C++ type and is mapped to a corresponding QML type.
Note: Every type T that do not have built-in comparison operator must be provided with a user-defined operator==.
The properties defined in this way behave like built-in properties. In particular they can be assigned bindings in QML and can be used as a data source in other bindings.
The following example demonstrates how to use properties. Define a property in C++:
struct MyData : public Qul::Object { Qul::Property<int> val; void update(int x) { // can get and set property values from C++ val.setValue(x); } };
And use it in QML:
Item { Item { // can bind QML property to exported property x: mydata_x.val color: "red" width: 50 height: 50 } MyData { id: mydata_x val: 100 } MyData { id: mydata_width // can bind exported property val: parent.width } Component.onCompleted: { mydata_x.update(200); console.log(mydata_width.val); } }
Note: Calling Qul::Property::setValue does not request Qt Quick Ultralite engine update. Event queue is needed if there are no QML animations or timers running. Repainting can be triggered by using an event queue as shown below.
struct MySingleton : public Qul::Singleton<MySingleton> { Qul::Property<int> val; // function that is called only from the C++ code void update(int value); }; class MyEventQueue : public Qul::EventQueue<int> { void onEvent(const int &value) override { // set property value in the event handler MySingleton::instance().val.setValue(value); } }; void MySingleton::update(int value) { static MyEventQueue myEventQueue; myEventQueue.postEvent(value); }
Grouped properties
Properties can be grouped together as follows:
struct MyObject : public Qul::Object { struct Grouped { Qul::Property<int> val1; Qul::Property<int> val2; Qul::ListProperty<int> list1; }; Grouped group; };
Then they can be used in QML like this:
Item { MyObject { group.val1: 42 group.val2: 43 } }
The grouping happens by placing the properties inside a struct or class S and then having a field of type S inside the exported class. The type S must not itself be derived from Qul::Object. Only its public fields of type Qul::Property are exposed as properties within the group. Groups are only for properties and can not have signals, enums or functions.
List Properties
Public members of an exported class become component list properties, if they are of Qul::ListProperty<T> type. See the Qul::ListProperty documentation for details.
Signals
Public fields of type Qul::Signal<Fn>
are translated to signals on the QML component.
The template argument Fn
must be a function type that describes the signal's parameter types. As usual, these types are mapped to the matching QML types. Similarly, the parameter names used in Fn
becomes a QML signal's parameter names.
The following code demonstrates how use signals:
struct MyItem : public Qul::Object { Qul::Signal<void(int sigValue)> triggered; void callTriggered() { triggered(42); } };
Item { MyItem { id: myitem onTriggered: console.log(sigValue); } Component.onCompleted: myitem.callTriggered() }
Enums
Public enum declarations in an exported class declaration are translated to QML enums.
The following code demonstrates how use public enums:
struct MyStates : public Qul::Singleton<MyStates> { enum State { On, Off, Broken }; };
Item { property MyStates.State state: MyStates.On }
C++ to QML type mapping
C++ type | QML type |
---|---|
bool | bool |
integral (char, short, int, long, ...) | int |
floating point (float, double) | real |
std::string (assumed UTF-8 encoding) | string |
T* where T derives from Qul::Object | matching component type |
enum in exported class | matching enum type |
ListProperty<T*> if T derives from Qul::Object | list<T> |
ListProperty<T> if T is present in QML Basic Types | list |
Note: Conversion to std::string implies dynamic memory allocation.
Transferring data from Interrupt Handlers to QML
The following technique combines several APIs and mechanisms existing in Qt Quick Ultralite. In result, a straight forward and efficient data transfer from any part of the application (including interrupt handlers) is possible.
This example covers data transfer using the QML singleton pattern to expose data to C++, but the same technique is applicable for Objects and Models.
Note: interrupt_handler
example demonstrates the techniques described here. You can find it under examples
directory.
What's needed?
Start with defining your event type that will be used as a data payload.
struct HMIInputEvent { enum Type { KeyPress, KeyRelease }; int keyCode; Type type; };
Next, implement a C++ to QML interface (for example Qul::Singleton). It can be combined with the Qul::EventQueue to change a property value or emit a signal when an event is received.
Example:
#include <qul/singleton.h> #include <qul/eventqueue.h> struct HMIInput : Qul::Singleton<HMIInput>, Qul::EventQueue<HMIInputEvent> { Qul::Signal<void(int key)> pressed; Qul::Signal<void(int key)> released; void onEvent(const HMIInputEvent &inputEvent) override { if (inputEvent.type == HMIInputEvent::KeyPress) { pressed(inputEvent.keyCode); } else if (inputEvent.type == HMIInputEvent::KeyRelease) { released(inputEvent.keyCode); } } };
This creates a Singleton object, which emits pressed
and released
signals on receiving the HMIInputEvent
Now, make use of the Singleton object in QML
import QtQuick 2.15 Rectangle { Row { spacing: 10 Text { id: operation } Text { id: keyCode } } HMIInput.onPressed: { operation.text = "pressed" keyCode.text = key } HMIInput.onReleased: { operation.text = "released" keyCode.text = key } }
At this point, the implementation is ready for transferring data by posting events to the EventQueue, for example from an interrupt handler.
Example:
static void keyEventInterruptHandler() { static HMI_StateTypeDef keyBuffer; if (BSP_HMI_GetState(&keyBuffer) != HMI_OK) { return; } HMIInput::instance().postEventFromInterrupt( HMIInputEvent{keyBuffer.code, keyBuffer.pressed ? HMIInputEvent::KeyPress : HMIInputEvent::KeyRelease}); }
Note: The default queue implementation for bare metal platforms can only have a single writer posting to the event queue. See Qul::EventQueue description for more information.
Now, every time keyEventInterruptHandler()
is called, a corresponding text in QML will change.
Available under certain Qt licenses.
Find out more.