
As soon as a QML UI needs to use a Qt module, like for example a custom bluetooth interface, or just interfere with some embedded device, then those C++ classes and/or modules need to be integrated to QML so that the UI knows about them and is able to use the functionality they bring.
In order to do this, some additional steps need to be taken. That is, to either declare those C++ classes as QML types and define the module that QML has to import in order to use those types, or set them as context properties.
This guide explains in detail and with examples how to create a C++ plugin and use it in QML as described above.
To begin with, the classes need to be created, exactly as stated in the official documentation. For the purpose of this guide, let’s assume we want to build the UI of a scanner/printer machine and thus we need to create a plugin for printer settings that will talk to the machine and bring that information into the QML UI but also will transmit the user’s preferences to the machine.
The example class will look like the following:
//printersettings.h
#ifndef PRINTERSETTINGS_H
#define PRINTERSETTINGS_H
#include <QtCore/QObject>
class PrinterSettings : public QObject
{
Q_OBJECT
Q_PROPERTY(bool printerOn READ printerOn WRITE setPrinterOn NOTIFY printerOnChanged)
public:
explicit PrinterSettings(QObject *parent = nullptr);
~PrinterSettings();
void setPrinterOn(bool printerIsOn);
bool printerOn() const;
signals:
void printerOnChanged();
private:
bool m_printerOn;
};
#endif // PRINTERSETTINGS_H
//printersettings.cpp
#include "printersettings.h"
PrinterSettings::PrinterSettings(QObject *parent)
:QObject(parent)
, m_printerOn(false)
{
}
PrinterSettings::~PrinterSettings()
{
}
void PrinterSettings::setPrinterOn(bool printerIsOn)
{
if (m_printerOn != printerIsOn) {
m_printerOn = printerIsOn;
emit printerOnChanged();
}
}
bool PrinterSettings::printerOn() const
{
return m_printerOn;
}
The next step needed is to make the class available to QML. As stated above, there are 2 ways, registering it as a QML and also setting it as a context property.
Registering a QML type
To register the class as a QML type, there are 2 places we can do this.
- In our applications’ main.cpp file.
- In a plugin.cpp file we create.
Going the first way, the only thing we have to do is to import the header file in our main.cpp file and then later inside int main call the qmlRegisterType function as follows:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "printersettings.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("./main.qml"));
//registering QML type here
qmlRegisterType<PrinterSettings>("com.org.printersettings", 1, 0, "PrinterSettings");
engine.load(url);
return app.exec();
}
Where the first parameter is the name of the module that QML has to import, the second the version number of that module and the third the name of the type to be used in QML.
After compilation, in QML it can be used like this:
import QtQuick 2.12
import QtQuick.Window 2.12
import com.org.printersettings 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
PrinterSettings {
id: printerSettings
}
}
This way is only recommended if the project is really small – perhaps a prototype – that needs only a few classes and is not meant to be scaled, updated and/or maintained. The idea is always to keep main.cpp as clean as possible.
The second way, which is preferred, is to put the classes in a separate folder under plugins, name it in this case “printersettings” keeping the project structure clean and easy to follow. That said, each different plugin or interface should be in its own folder.
After the source files are moved into printersettings folder, a plugin.cpp file needs to be created which will be the one responsible for QML type registration and thus for calling the qmlRegisterType function.
This file will look like this:
#include <QtQml/qqmlextensionplugin.h>
#include <QtQml>
#include "printersettings.h"
class PrinterSettingsPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char *uri) override
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("com.org.printersettings"));
qmlRegisterType<PrinterSettings>(uri, 1, 0, "PrinterSettings");
}
};
#include "plugin.moc"
Secondly, a qmldir file needs to be created inside the same folder which will look as follows:
module com.org.printersettings
plugin printersettingsplugin
And finally the .pro file:
TEMPLATE = lib
TARGET = printersettingsplugin
QT += qml
CONFIG += qt plugin c++11
uri = com.org.printersettings
DESTDIR = $$OUT_PWD/../../plugins/com/org/printersettings
SOURCES += \
plugin.cpp \
printersettings.cpp \
HEADERS += \
printersettings.h \
OTHER_FILES += qmldir
qmldir.files = qmldir
qmldir.path = $$DESTDIR
INSTALLS += qmldir
In order to compile the plugin together with the whole project, the project’s .pro file needs to know where to find it. In this case, the project .pro file can be defined as TEMPLATE = subdirs, moving the main.cpp in a separate folder under src together with a .pro file that will be the TEMPLATE = app file.
The new project structure is demonstrated in the image below

The .pro files added are analyzed below.
plugins/plugins.pro
TEMPLATE = subdirs
SUBDIRS += printersettings
qt-qml-module-example.pro
TEMPLATE = subdirs
CONFIG += ordered
SUBDIRS += plugins
SUBDIRS += src
OTHER_FILES += $$files($$PWD/*.qml, true)
src/src.pro
TEMPLATE = subdirs
SUBDIRS += test_printer_ui
src/printer_ui.pro
TEMPLATE = app
TARGET = printerui
QT += quick
CONFIG += c++11
SOURCES += \
main.cpp
target.path = $$OUT_PWD/../bin
INSTALLS += target
This way the project implements a modularized and organized structure that is easier to maintain and of course scale.
Now the import path variable needs to be set so that the application knows where to look for the qmldir files. This can be done with a couple of ways, those being:
- Set -I when running the project in the console like: -I path/to/imports ./myapp
- Call the addImportPaths functions in the project’s main.cpp file like: engine.addImportsPath(QUrl(“path/to/imports/”)
- Set the QML2_IMPORTS_PATH environment variable like: QML2_IMPORTS_PATH=path/to/imports/ in QtCreator or terminal
The second one is always the preference as this way no additional steps need to be done when setting up the project for the first time.
That said, main.cpp now would look like this:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImportPath(QUrl(QLatin1String("./plugins/")).toString());
const QUrl url(QStringLiteral("./main.qml"));
engine.load(url);
return app.exec();
}
If all the above are correctly done, it should be possible after this point to go to any qml file and import the created module using the url defined in the qmldir file. This would look like:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import com.org.printersettings 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("qt-qml-module-example")
PrinterSettings {
id: printerSettings
}
Item {
id: mainUI
anchors.fill: parent
Rectangle {
anchors.fill: parent
anchors.margins: 50
color: printerSettings.printerOn ? "green" : "red"
}
ToolButton {
width: 120
height: 120
anchors.centerIn: parent
background: Rectangle {
width: 120
height: 120
color: "transparent"
border.color: "black"
radius: width/2
Image {
anchors.fill: parent
anchors.margins: 40
fillMode: Image.PreserveAspectFit
source: "onoff.png"
}
}
onClicked: {
printerSettings.printerOn = !printerSettings.printerOn;
}
}
}
}
Ideally, this application when run, it should show an on/off button which when pressed, it sets the printerOn property accordingly, and based on that property the background color changes between red when off and green when on.
Setting a context property
To create context properties instead of qml types, in main.cpp replace the qmlRegisterType function with the following:
engine.rootContext()->setContextProperty("PrinterSettings", &printerSettings);
In QML this doesn’t need to import any module and it can be used by any file as a normal property:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("qt-qml-module-example")
Item {
id: mainUI
anchors.fill: parent
Rectangle {
anchors.fill: parent
anchors.margins: 50
color: printerSettings.printerOn ? "green" : "red"
}
ToolButton {
width: 120
height: 120
anchors.centerIn: parent
background: Rectangle {
width: 120
height: 120
color: "transparent"
border.color: "black"
radius: width/2
Image {
anchors.fill: parent
anchors.margins: 40
fillMode: Image.PreserveAspectFit
source: "onoff.png"
}
}
onClicked: {
printerSettings.printerOn = !printerSettings.printerOn;
}
}
}
}
Both ways described so far are used for exposing the C++ interfaces to qml, the difference here is among others, that if the class needs to be globally known in the application and initiated on start up or it’s just needed in certain parts. Hypothetically speaking, in the above example, the scanner settings should be only available to the scanner application, whereas the printer settings to the printer application.
For further reference, the source code of the example can be also found here.
Concluding, as in every project, we should always be careful on how we design the architecture depending always on the needs in functionality, stability, performance and scalability. It’s not the same implementing a dummy prototype for some show or exhibition, and implementing a real project or a prototype that will most luckily go into production.
Nice write up. test_printer_ui should be simply printerui. I ran into another issue, the qmldir file is not put into the build directory together with the DLL or .so file.
Did you double check whether your DESTDIR is set correctly? Is the qmldir file not copied at all in any place in the build folder?