Skip to main content
Docs: template-qt6

QML Plugin

With this feature you can use a qml version of your interface as a [Qml plugin](https://doc.qt.io/qtcreator/creator-qml-modules-with-plugins.html. It provides qml -available version of you interfaces and factory implementation for interfaces and factories for structures. It creates a qml module for your interface.

File overview for module

With an example API

Hello World API (click to expand)
schema: apigear.module/1.0
name: io.world
version: "1.0.0"

interfaces:
- name: Hello
properties:
- { name: last, type: Message }
operations:
- name: say
params:
- { name: msg, type: Message }
- { name: when, type: When }
return:
type: int
signals:
- name: justSaid
params:
- { name: msg, type: Message }
enums:
- name: When
members:
- { name: Now, value: 0 }
- { name: Soon, value: 1 }
- { name: Never, value: 2 }
structs:
- name: Message
fields:
- { name: content, type: string }

the following file structure will be generated. The purpose and content of each file is explained below.

📂hello-world
┣ 📂apigear
┣ 📂qt_hello_world
┃ ┣ 📂apigear
┃ ┣ 📂examples
┃ ┣ 📂io_world
┃ ┃ ┣ 📂api
┃ ┃ ...
┃ ┃ ┣ 📂qmlplugin
┃ ┃ ┃ ┣ 📜apifactory.cpp
┃ ┃ ┃ ┣ 📜apifactory.h
┃ ┃ ┃ ┗ 📜CMakeLists.txt
┃ ┃ ┃ ┣ 📜qml_api.cpp
┃ ┃ ┃ ┣ 📜qml_api.h
┃ ┃ ┃ ┣ 📜qmlhello.cpp
┃ ┃ ┃ ┣ 📜qmlhello.h

QML API

To avoid possible namespace collision between different modules we use QML_NAMED_ELEMENT() with module name and element name for all registering objects, like e.g. QML_NAMED_ELEMENT(IoWorldWhen) for When enum.

Files 📜qml_api.h and 📜qml_api.cpp contain:

  • the enum wrapper for enum registration, which adds only QML macros for registering enum from api library.
struct ForeignWhen : public QObject
{
Q_OBJECT
QML_FOREIGN(When)
QML_NAMED_ELEMENT(IoWorldWhen)
QML_UNCREATABLE("This is enum class")
QML_ADDED_IN_VERSION(1,0)
};
  • the factory for uncreatable otherwise in QML structures
class IO_WORLD_QML_EXPORT MessageFactory : public QObject {
Q_OBJECT
QML_ADDED_IN_VERSION(1,0)
QML_NAMED_ELEMENT(IoWorldMessageFactory)
QML_SINGLETON
public:
Q_INVOKABLE io_world::Message create(QString in_content);
};

All factories are QML_SINGLETONS, so you don't have to create any instance of it, just call var myInstance = IoWorldMessageFactory.create(...) in your qml code. The create function requires input parameters for all members of the created structure.

note

The qml plugin uses the version information form helloworld.module.yaml to register types, both with the QML_ADDED_IN_VERSION and with qt_add_qml_module in CMakeLists.txt. Use this version when importing your module in qml, here import io_world 1.0

QML Wrappers

Files 📜qmlhello.h and 📜qmlhello.cpp contain a qml wrapper for the Hello interface.
The qml wrapper requires providing an implemented AbstractHello instance. For that we use the factory described in [api] module with 📜iapifactory.h and implemented in this module in (📜apifactory.h, 📜apifactory.cpp). The functionality is explained below.

The QML wrapper (simplified version)

qmlhello.h
class IO_WORLD_QML_EXPORT QMLHello : public AbstractHello
{
Q_OBJECT
QML_NAMED_ELEMENT(IoWorldHello)
...
Q_PROPERTY(Message last READ last WRITE setLast NOTIFY lastChanged)
...
Q_INVOKABLE int say(const Message& msg, When::WhenEnum when) override;
...
Q_SIGNALS:
void lastChanged(const Message& last);
}

The qml wrapper makes the interface usable from qml:
Line 6 shows exposed properties, with emitting signal on property changed from qml (NOTIFY lastChanged) - hence overriding signal for property changed (line 11).
Line 8 shows the function exposed as a callable from qml.
The wrapper forwards the calls to backend and all the signals from backend to qml wrapper.

caution

The signals defined in the api shall be emitted by the backend and handled in qml, not the other way around. When the signal is emitted in qml, it won't reach the backend implementation.

To use QMLHello you just need to create an instance of it in your QML code with a proper name (the one used in QML_NAMED_ELEMENT):

import QtQuick 2.15
import QtQuick.Layouts 1.2
import io_world 1.0

ApplicationWindow {
id: appWindow
visible: true
width: 300
height: 300

IoWorldHello { id: qmlIoWorldHello }

Providing backend to QML Wrapper.

The QmlHello is instantiated in qml without any handle to get or set the backend. Therefore, the global ApiFactory class of IApiFactory type is added to provide a flexible and extensible way to create it. The QmlHello class creates the Hello backend object by itself using the global ApiFactory. By providing your IApiFactory implementation, you will be able to use a Hello backend of your choice.

Here is an example code snippet that shows how to prepare your IApiFactory

io_world/customfactory.h
#pragma once

#include "io_world/api/iapifactory.h"
// #include "your CustomHello header file"
#include <QtCore>

class CustomFactory : public QObject, public IApiFactory
{
public:
CustomFactory(..., QObject *parent = nullptr); //provide any extra resources your factory needs.

std::shared_ptr<AbstractHello> createHello(QObject *parent = nullptr) override
{
return make_shared<CustomHello>(...); // provide any resource your CustomHello needs.
}
};

and set in code

main.cpp
#include "io_world/api/apifactory.h"
#include "io_world/customfactory.h"

#include <QtCore>
#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[]){

io_world::CustomFactory io_worldFactory();
// Setting the CustomFactory as a global factory. From now, each qml object will create backend with it.
io_world::ApiFactory::set(&io_worldFactory);

// Starting your application after setting the factory.
const QUrl url(QStringLiteral("qrc:/main.qml"));
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;

engine.load(url);
...

this way, on every QmlHello instantiation, your CustomFactory::createHello is called, and the returned object is used by QmlHello.

note

You can check the qml example or factories provided by other features. For instance olink.

caution

Your IApiFactory implementation needs to be set to ApiFactory once, before the QML file is loaded.

Paths

If you run your application outside of Qt Creator you need to add the import paths (paths where the plugins are) to runtime. You can either set the QML_IMPORT_PATH and QML2_IMPORT_PATH environment variables - for ad-hoc import paths (for debugging or testing) or use the QQmlEngine::addImportPath() function for fixed import paths that should always be available.

    QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
app.addLibraryPath(the-directory);

Usually the plugins are located in your CMAKE_BINARY_DIR in the target folder. In our example code that would be "your-build-dir/io/world/qmlplugin" folder.

Read more on import paths