QML Type Registration in Qt 5.15

Qt 5.15 provides a much improved way of exposing C++ types to QML. You can now specify your module name and version in a central place and there is no need to specify minor versions or revisions anymore. Furthermore, the specifics of the QML type registration can now be declared in the C++ class declaration.

The common way to make C++ types available in QML so far was using the registration functions provided in the qqml.h header: qmlRegisterType(), qmlRegisterSingletonType(), qmlRegisterUncreatableType() etc. There are downsides to this approach:

You always need to keep your type registrations in sync with the actual types. This is especially bothersome if you use revisions to make properties available in different versions of an import. Even if not, the fact that you need to specify the registration separately from the type is a burden as you can easily lose track of how you registered which types into which modules.

Furthermore, as you register your types procedurally, any QML tooling cannot automatically tell which types are available in which import. Qt Creator indeed has some heuristics that try to detect common registration patterns in C++ code, but this is necessarily incomplete. Figuring out whether a specific registration will be executed by the program is equivalent to solving the halting problem. Simpler tools like qmllint or qmlformat have no information about the C++ code and need to analyze your QML code in isolation. Therefore, they won't have any information about types registered from C++. In order to (partially) solve this problem the "qmltypes" files were introduced. When developing a QML plugin, you are encouraged to put a file called "plugins.qmltypes" next to the plugin binary. The qmltypes file contains meta-information about the types registered by the plugin. Qt Creator and other tools can then refer to this information in order to provide you
with better analysis of your code. This works, but only for plugins. If you register your types directly from the main program, you're still facing the same problem. Also, you end up specifying your types twice, once in C++ and once in qmltypes format. In order to (partially) solve the problem of redundant type specification, a tool called "qmlplugindump" is available. This tool will load your plugin in the same way the QML engine would load it. It will then extract information about all the types contained in it in order to produce a plugins.qmltypes file. This, however, will also execute unrelated code in your plugin, and it will only work if you are compiling your plugin for the same platform as qmlplugindump runs on. In practice, it does not work for cross-compiled builds.

Considering all these stumbling blocks, the type registration system has been re-designed in Qt 5.15. You should not call qmlRegisterType() and friends procedurally anymore. Instead, a set of macros you can add to your class declarations is provided in qqml.h. These macros mark the type as exported to QML. The most important one is "QML_ELEMENT". It provides the surrounding class in QML as an element named by the C++ class name. The meta object compiler (moc) will find the information given this way. When passed the option "--output-json", it will provide a JSON file that contains all meta type information in a machine-independent and readable way. You can trigger this behavior by specifying "CONFIG += metatypes" in your qmake project file. Next, a new tool called "qmltyperegistrar" reads this metatypes file, and possibly related ones, and generates a C++ file containing a function that registers all the types marked with relevant macros, as well as a qmltypes file with only the type information relevant for the QML module defined by your application. In order to do this, it needs to know the module name and major version. You can specify this in your qmake project file by using the variables "QML_IMPORT_NAME" and "QML_IMPORT_MAJOR_VERSION". The minor version does not have to be specified. qmltyperegistrar will find revisioned properties and methods in the meta type data and register different versions of the type accordingly. This defines the minor versions the module is available under. You can also make a type available from a specific minor version by adding the "QML_ADDED_IN_MINOR_VERSION(x)" macro.

Once the build system is set up this way, you can trigger the actual registration by adding "CONFIG += qmltypes". The "qmltypes" CONFIG option implies "metatypes". You don't need to specify both explicitly.

Such static registration of C++ types for QML will not only generate qmltypes files for plugins, but also for your main application. The latter files are called "app.qmltypes" and should be placed next to your application binary, the same way "plugins.qmltypes" should be placed next to the plugin they refer to.

To illustrate this, let's transform an application that registers types dynamically into one that registers the same types statically. This has already been done with all the QML and QtQuick examples. Let's look at the basic "Adding Types" example. In Qt 5.14 and before, the example had the following line of code in its main.cpp file:

qmlRegisterType<Person>("People", 1,0, "Person");

This would register the C++ class "Person" as a QML element also called "Person", into the module "People" under the version 1.0. This line has disappeared in Qt 5.15. Instead, the following lines have been added to the adding.pro file:

CONFIG += qmltypes
QML_IMPORT_NAME = People
QML_IMPORT_MAJOR_VERSION = 1

These lines specify the import name and major version for all types exposed to QML. Finally, the most important change, is the "QML_ELEMENT" macro added to the person.h file:

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize)
    QML_ELEMENT
public:
    Person(QObject *parent = nullptr);
    [...]
};

This "QML_ELEMENT" tells qmltyperegistrar to register the type. That is all. You can now specify as many "QML_ELEMENT"s as you like without mistyping the import name the versions and revisions each time. More details on registering types in specific ways, for example to provide a QML element name different from the C++ class name or to create a singleton, are shown in the documentation.


Blog Topics:

Comments