In Qt6, the qt_add_qml_module CMake API integrates with C++ classes through specific macros to streamline the type registration process.
- QML_ELEMENT: Automatical registers the C++ class with the QML type system. This eliminates the need for manual registration calls like
qmlRegisterType. - QML_SINGLETON: Declares the class as a singleton. This ensures that all QML components share a single instance of the class. Without this macro, a new object is instantiated every time the type is referenced in QML.
- Q_GLOBAL_STATIC: A Qt macro used to create thread-safe, lazily initialized global static objects.
Standard Type Registration (Non-Singleton)
By default, a class marked with QML_ELEMENT behaves as a standard QML type. Each usage in QML creates a distinct instance.
1. C++ Class Definition
Define a class MessageHandler that inherits from QObject. Use the QML_ELEMENT macro to expose it to QML.
// messagehandler.h
#pragma once
#include <QObject>
#include <QtQml/qqml.h>
class MessageHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
explicit MessageHandler(QObject *parent = nullptr);
Q_INVOKABLE QString fetchStatus();
};
// messagehandler.cpp
#include "messagehandler.h"
MessageHandler::MessageHandler(QObject *parent) : QObject(parent) {}
QString MessageHandler::fetchStatus() {
return "System Initialized";
}
2. CMake Configuration
Add the class to the QML module definition in your CMakeLists.txt file.
qt_add_qml_module(your_application_target
URI CoreModule
VERSION 1.0
SOURCES
messagehandler.cpp messagehandler.h
)
3. QML Usage
Instantiate the component in QML to create a new object.
// main.qml
import QtQuick 2.15
import CoreModule 1.0
Item {
width: 400
height: 300
MessageHandler {
id: commHandler
}
Text {
anchors.centerIn: parent
text: commHandler.fetchStatus()
}
}
Singleton Pattern Implementation
Singletons ensure a single shared instance. There are two strategies for managing the instance lifecycle: sharing across all QML engines or scoping to a specific engine.
Shared Instance Across Engines
To share a single instance globally (even across multiple QML engines), you must implement a static factory functon with the signature create(QQmlEngine*, QJSEngine*). Use Q_GLOBAL_STATIC to manage the instance manually.
// appconfig.h
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QtQml>
class AppConfig : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
explicit AppConfig(QObject *parent = nullptr);
// Factory method required for shared singletons
static AppConfig *create(QQmlEngine *engine, QJSEngine *scriptEngine);
Q_INVOKABLE QString getApiKey();
private:
static QString m_apiKey;
};
// appconfig.cpp
#include "appconfig.h"
Q_GLOBAL_STATIC(AppConfig, globalAppConfig)
AppConfig::AppConfig(QObject *parent) : QObject(parent) {}
// Returns the shared instance managed by Q_GLOBAL_STATIC
AppConfig *AppConfig::create(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return globalAppConfig();
}
QString AppConfig::getApiKey() {
return "SHARED_KEY_123";
}
In QML, access the singleton directly using the type name without creating an object.
// main.qml
import QtQuick 2.15
import CoreModule 1.0
Item {
Text {
text: AppConfig.getApiKey() // Direct access
}
}
Per-Engine Singleton
If the static factory method is omitted, the QML angine assumes responsibility for the singleton's lifecycle. It will construct one instance per engine using the default constructor.
// engineconfig.h
class EngineConfig : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
explicit EngineConfig(QObject *parent = nullptr);
Q_INVOKABLE QString getSetting();
};
This approach provides a singleton within the scope of a specific QML engine but does not share the instance globally across different engine instances.