Photo by ConvertKit on Unsplash
Creating a QML Plugin: Here's How!
2/3 Building and Publishing Qt-Framework Plugins with GitHub-Actions step by step
QML plugins are a great way to package QML
files and C++
code into a single library and then use it in other applications. This allows us to modularize our applications and create reusable code. In this article, we will focus on how to create a QML plugin that provides an API in another application.
On the part of the QT framework, there is unfortunately currently no adequate example for the use of this great tool, which is why this article will potentially help some developers.
The QQmlExtensionPlugin Class
To create a QML plugin, we need a class that is derived from QQmlExtensionPlugin
called e. g. ClockPlugin
. As we derive from this class we also have to use the Q_OBJECT
macro. So we can take advantage of the Qt's meta-object system I won't go deeper in this article.
#include <QQmlExtensionPlugin>
class ClockPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char* uri) {}
};
This class provides us with two functions that we need to implement: registerTypes()
and initializeEngine()
.
registerTypes()
is called by the Qt framework when the plugin is loaded. It is responsible for registering the custom QML types provided by the plugin. In this case we just leave this function empty as we don't have any custom C++ types.
qmlRegisterType()
method allows you to make your C++ classes available as QML types, so they can be instantiated and used directly in QML code.initializeEngine()
is called by the Qt framework after the QML engine has been created but before any QML components are loaded. So, it provides an opportunity to perform any necessary initialization steps specific to your plugin or QML environment. You could e. g. register singletons, or perform other tasks required for your plugin to work correctly.
By using Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
, you are telling the Qt plugin system that your plugin implements the QQmlExtensionInterface
, which is required for QML extension plugins. This allows the Qt framework to load and manage your plugin correctly.
Creating a qmldir File
In addition to the QQmlExtensionPlugin class, we also need a qmldir file. This file describes the QML files that are included in our plugin and is used by Qt to load the plugin. So basically it informs Qt which functionality the plugin has and which components it is using.
module ClockPlugin
ClockPlugin 1.0 Clock.qml
plugin clockplugin
module ClockPlugin
states the module name. This name is used to group the components of the plugin and to avoid conflicts with other modules. ClockPlugin 1.0 Clock.qml
defines a component of the plugin with the name "ClockPlugin" and version "1.0". "Clock.qml" is the file in which the plugin is implemented.
In summary, the directory structure for the part that is necessary for the plugin itself looks like this:
clock_plugin
|__ qml
...
|__ Clock
qmldir
Clock.qml
GenericClockHand.qml
...
|__ src
clock_plugin.pro
It is important, that the qmldir file is located in the directory of the plugin, (here: where Clock.qml is located) as the qml engine will only search in this directory.
Explanation of the .pro File
To compile our plugin, we also need a .pro file. This file describes which files need to be compiled and how they need to be compiled. In this section, we will go through the .pro file line by line and explain which settings are necessary to compile the plugin correctly.
QT += qml # 1
TEMPLATE = lib # 2
CONFIG += qt plugin qmltypes # 3
QML_IMPORT_NAME = ClockPlugin # 4
QML_IMPORT_MAJOR_VERSION = 1 # 5
DESTDIR = imports/$$QML_IMPORT_NAME # 6
TARGET = clockplugin # 7
RESOURCES += qml/plugin_qml.qrc # 8
# 9
SOURCES += \
src/ClockPlugin.cpp
# 10
HEADERS += \
src/ClockPlugin.h
# copying dependecies of the plugin in the output-folder
PLUGIN_DEPENDENCY_DIRECTORY = $$PWD/qml/Clock # 11
# 12
PluginDependencies.commands = $(COPY_DIR) $$shell_path($$PLUGIN_DEPENDENCY_DIRECTORY) $$shell_path($$DESTDIR)
first.depends = $(first) PluginDependencies # 13
export(first.depends) # 14
export(PluginDependencies) # 15
QMAKE_EXTRA_TARGETS += first PluginDependencies # 16
#1
to#3
sets some basic configuration parameters like that we want to use QML (#1
) or that this project should be treated as a library (#2
).#4
and#5
is important, as we have to set, how the library should be imported when we want to use it.#6
says where the target file should be located, whereas#7
specifies the name of the target file. (default: name of the .pro-file).#8
adds theqrc
(Qt Resource Collection) file to the project. This file contains the compiled QML resources.#9
and#10
add the source files of the project.#11
defines the directory path for the plugin dependencies in its own variable for better readability.$$PWD
defines the full path to the directory of the current file. I used this predefined variable so that this approach can work for everyone.#12
defines the command to copy the plugin dependencies from the specified directory to the output directory.#13
specifies that the targetfirst
depends on both thefirst
target itself and thePluginDependencies
target.#14
and#15
export both dependencies to be used by other parts of the build system.#16
adds both targets to the build system.
Deployment: Ensuring Your Plugin Loads Correctly
If we want to use our plugin in other applications, we need to ensure that all dependencies of the plugin are also present.
When we compile the plugin project, we automatically create a shadow build directory. (typical name: build-clock_plugin-Desktop_Qt_6_2_4_MinGW_64_bit-Debug
) In this directory all build files according to the plugin can be found. So also the directory imports
, in which the defined plugin with all dependencies is present inside of a subdirectory. The name of the subdirectory is determined by QML_IMPORT_NAME
. (in this case: "ClockPlugin")
Now we can copy the folder of the plugin into the debug build directory ./{shadow-build}/debug/
of another project. To use the plugin inside of the project we just have to import it in QML.
...
import ClockPlugin 1.0
Window
{
width: 640
height: 480
visible: true
title: qsTr("TestingApplication")
ClockPlugin
{
id: clock
anchors.centerIn: parent
}
}
QML_IMPORT_NAME
and QML_IMPORT_MAJOR_VERSION
to import the module.If we now start the test project in debug mode, the plugin is loaded and can be used. At this point, you should see the created plugin inside of your test project.
Using QMake Parameters: Optimizing the Make Behavior
In this section, we will focus on using QMake parameters to change the making behaviour. I will explain the advantages of this method and how it can be used to compile a plugin that can be used both as a standalone application and as a plugin in other applications.
First, we have to enhance the .pro
file.
isEqual(BUILD_PLUGIN, "1") {
{CODE_FOR_BUILDING_ONLY_PLUGIN}
} else {
QT += quick
RESOURCES += qml/qml.qrc
SOURCES += \
src/main.cpp
}
We use a conditional to check if the self-defined Qmake variable BUILD_PLUGIN
is defined. You can define the variable in QtCreator like this:
Navigate to "Projects".
Select the correct kit.
Navigate to "Build Steps".
Click on "qmake".
Click on "Details".
Type in the text field "Extra Arguments"
BUILD_PLUGIN=1
.
If the variable is defined, we proceed as explained in the previous chapter.
If the variable is not defined, then we start the plugin as a standalone application. For this scenario, we don't need to configure the plugin. But we have to create and load another .qrc
file. A .qrc file in Qt is a resource file that allows you to bundle and manage external resources, such as images, icons, and translation files, within your application. It provides a convenient way to organize and access these resources in a cross-platform manner, making it easier to distribute and deploy your Qt application.
So, we do need the QML files of the plugin, but don't need the qmldir file which describes the plugin. As a result, I create a new .qrc file in ./qml/
called qml.qrc
.
plugin_qml.qrc
file is the same just with an extra file entry for the qmldir
.<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>Clock/Clock.qml</file>
<file>Clock/ClockBackground.qml</file>
<file>Clock/GenericClockHand.qml</file>
<file>Clock/HourClockHand.qml</file>
<file>Clock/MinuteClockHand.qml</file>
<file>Clock/SecondClockHand.qml</file>
<!-- <file>Clock/qmldir</file> when using the plugin -->
</qresource>
</RCC>
As a source file, we just need main.cpp
as we don't have to use any plugin source files. The advantage of this approach is that we can develop the plugin with the non-plugin (BUILD_PLUGIN=0
) build and deploy it with the plugin build (BUILD_PLUGIN=1
).
Summary and Next Steps
Now that we have a working QML plugin, we are ready to take the project to the next level. In the next and thus last article in this series, we will look at how to create a CICD pipeline for the automated compilation of our project.
You are also welcome to expand the project. Take a look at the ReadMe of the project on GitHub.
Thank you for reading this article. Hopefully, you gained some knowledge. If you have any suggestions for improving my articles, please feel free to write them in the comments. If you have any further questions or need assistance, do not hesitate to contact me. I am happy to help! โฅ๏ธ