What’s new for QML Modules in 6.5
March 03, 2023 by Fabian Kosmale | Comments
While QML modules have existed for a long time, their use had been rather sparse before Qt 6. With the introduction of qt_add_qml_module
in Qt 6, they have however become much more prevalent. And with good reason: Only by placing all related QML in a module can tooling like qmllint or the Qt Quick Compilers work correctly.
However, some parts of Qt’s own API were not aware of modules so far. When interacting with QML types, e.g. via QQmlComponent
, you would need to use explicit file paths so far. Starting from 6.5, there’s now an alternative solution leveraging modules, which we’ll introduce in this blog post. Moreover, we now provide a solution which obsoletes fiddling with import paths in most common cases.
New default import path
Let’s start with the new default import path that has been added: You can now put your modules under qrc:/qt/qml
, and they will be automatically found there. So far, if you wanted to place modules the resource system, you would either had to call QQmlEngine::addImportPath
with a path of your choosing or place it under qrc:/qt-project.org/imports
. The latter is strongly discouraged, as it is only meant for Qt’s own modules and libraries that are expected to be used system wide. The former cannot be easily done by a library, and forcing every user of a library to first call addImportPath
isn’t much of a solution either. To remedy this, we now provide qrc:/qt/qml
as a default import path, and recommend applications and libraries to use it.
NOTE:
Applications could avoid the issue with the import path by simply loading their main entry point from a path in the resource system, and relying on the implicit import to find the module’s qmldir, assuming the main file and the qmldir are all part of the module and placed in the same folder. We’ll see below why this solution has its own shortcomings, and for libraries it was never feasible in any case.
CMake integration
Unfortunately, qt_add_qml_module
has been using /
as the default for the resource prefix so far, and we cannot silently change it. Fortunately, Qt 6.5 also brings support Qt CMake policies, which allow for a controlled way of evolving the defaults in our CMake API. Enabling QTP0001 sets /qt/qml
as the default when no explicit value for RESOURCE_PREFIX
has been provided. The easiest way to enable this policy is to call
qt_standard_project_setup(REQUIRES 6.5)
Passing REQUIRES 6.5
to qt_standard_project_setup will globally enable all new policies introduce up to Qt 6.5. That happens to only be the one about the resource prefix. For more details on controlling policies, please consult their documentation and the documentation of the qt_policy command.
But I’m using qmake!
In case you’re on a project using qmake
, you can still benefit from the new default import path, by setting the prefix on the RESOURCES variable.
QML_IMPORT_NAME = MyModule
QML_IMPORT_MAJOR_VERSION = 1
SOURCES += \
main.cpp
HEADERS += \
filesystemmodel.h
qml_resources.files = \
qmldir \
Main.qml \
MyType.qml
qml_resources.prefix = /qt/qml/MyModule
RESOURCES += qml_resources
With this setup, the MyModule
module can be found by the engine without any further setup at runtime.
Module aware API
The second substantial change related to QML module is a new set of APIs to interact with QML types, both from C++
and from QML.
Loading Components: The state so far
Before we dive into the new API, let’s first look at how a simple QML application project would look before. We would have the following directory structure:
The CMakeLists.txt would look like:
cmake_minimum_required(VERSION 3.21)
project(helloqml VERSION 0.1 LANGUAGES CXX)
find_package(Qt6 6.5 COMPONENTS Quick REQUIRED)
qt_standard_project_setup(REQUIRES 6.5)
qt_add_executable(helloqmlapp
main.cpp
)
qt_add_qml_module(helloqmlapp
URI helloqml
QML_FILES main.qml
)
target_link_libraries(helloqmlapp
PRIVATE Qt6::Quick)
Note that we’re already using the new default import path mentioned above. main.cpp contains
#include <QGuiApplication>
#include <QQmlApplicationEngine>
using namespace Qt::Literals::StringLiterals;
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/qt/qml/helloqml/main.qml"_s);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
and finally, main.qml contains
The line of interest is const QUrl url(u"qrc:/qt/qml/helloqml/main.qml"_s);
. There are multiple gotchas in here. The first is that because we are dealing with a QML module, main.qml
has now been placed under a helloqml folder (remember, a QML module must be in one of the import paths within a folder corresponding to the module’s URI). The second gotcha is that while qt_add_qml_module
helpfully places the QML files into the resource system, we still have to remember to load from there and not directly from the file system if we want to avoid potentially slow file system operations (and ensure that AOT compiled bindings are executed instead of being interpreted at runtime). On the other hand, if we do want to load from the normal file system during development (to iterate faster on our QML files without having to recompile), we would be out of luck as we have now hard-coded the qrc
path. With Qt 6.5 we have a new option which avoids all the issues.
Loading Components: The new approach
With QML modules, we know that a given QML element can be identified via the name of its module and its type-name. So let’s use those: We rename main.qml to Main.qml (recally that only upper case files lead to exported QML types by default), and change main.cpp to use engine.loadFromModule("helloqml", "Main");
instead of engine.load(url)
. And we’re done. We no longer have to care where exactly the file is located, and the loading from the resource file system is handled behind the scenes. But loadFromModule
can do more than just replacing existing load(QUrl)
calls. It also enables a few things that were not possible before. loadFromModule
does not require that the QML element we’re loading is a file. Instead it supports any element that can be created in QML. That includes
-
elements backed by files (as we’ve already seen),
-
inline components: Given
we can load Inner via
engine.loadFromModule("MyModule", "Outer.Inner")
. -
and types defined in C++: Given
we can create an instance of MyFancyItem via
engine.loadFromModule("MyModule", "MyFancyItem")
.
Before, the last two cases would have required creating a wrapper QML file instantiating the desired element. Additionally, the above is not limited to QQmlApplicationEngine
. An equivalent API exists also in QQmlComponent
in the form of QQmlComponent::loadFromModule
. Lastly, there’s also a new overload of Qt.createComponent
, exposing the functionality in QML:
Singletons
The API so far was used to create new objects, and thus it’s unsuitable for QML singletons. For consistency’s sake, there is however also a new overload of QQmlEngine::singletonInstance
working with singletons. That can be useful when setting up some application global data, e.g. to expose a QAbstractItemModel, or some application configuration data fetched from the network: Given a singleton defined in C++ 1
class Globals : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
//...
}
we can then write
QQmlApplicationEngine engine;
// access the singleton
auto globals = engine.singletonInstance<Globals>("MyModule", "Globals");
// set up global state
globals.setModel(fancyModel);
// start the application after the initial setup
engine.loadFromModule("MyModule", "Main")
Note that it was possible to do this even in prior Qt versions by using qmlTypeId in combination with the the id based overload of QQmlEngine::singletonInstance
. The new function has two benefits: It's slighly less to write, and it is a tiny bit faster if only one call to singletonInstance
2 is needed.
Outlook
We hope that the new module related functionality will make it easier to work with them. The ability to load QML elements also brings us a step closer to a more seamless integration with the QML type compiler. Currently, usage of the type compiler requires modifying the entry point of your application. In the future, we plan to support qmltc-compiled types transparently in the various loadFromModule
functions. Stay tuned for futher updates!
Blog Topics:
Comments
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.7 Now!
Download the latest release here: www.qt.io/download.
Qt 6.7 focuses on the expansion of supported platforms and industry standards. This makes code written with Qt more sustainable and brings more value in Qt as a long-term investment.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.