QML-Module schreiben

Sie sollten ein QML-Modul mit Hilfe der CMake QML Module API deklarieren, um:

  • qmldir und *.qmltypes Dateien zu generieren.
  • C++-Typen zu registrieren, die mit QML_ELEMENT annotiert sind.
  • Kombinieren von QML-Dateien und C++-basierten Typen im selben Modul.
  • qmlcachegen für alle QML-Dateien aufzurufen.
  • Verwenden Sie die vorkompilierten Versionen von QML-Dateien innerhalb des Moduls.
  • Stellen Sie das Modul sowohl im physischen als auch im Ressourcendateisystem bereit.
  • Erstellen Sie eine Backing-Bibliothek und ein optionales Plugin. Binden Sie die Backing Library in die Anwendung ein, um das Laden des Plugins zur Laufzeit zu vermeiden.

Alle oben genannten Aktionen können auch separat konfiguriert werden. Weitere Informationen finden Sie unter CMake QML Module API.

Mehrere QML-Module in einer Binärdatei

Sie können mehrere QML-Module in ein und dasselbe Binary einbinden. Definieren Sie für jedes Modul ein CMake-Target und binden Sie dann die Targets mit der ausführbaren Datei. Wenn die zusätzlichen Targets alle statische Bibliotheken sind, ist das Ergebnis eine Binärdatei, die mehrere QML-Module enthält. Kurzum, Sie können eine Anwendung wie diese erstellen:

myProject
    | - CMakeLists.txt
    | - main.cpp
    | - main.qml
    | - onething.h
    | - onething.cpp
    | - ExtraModule
        | - CMakeLists.txt
        | - Extra.qml
        | - extrathing.h
        | - extrathing.cpp

Nehmen wir an, main.qml enthält eine Instanziierung von Extra.qml:

import ExtraModule
Extra { ... }

Das Extra-Modul muss eine statische Bibliothek sein, damit Sie es in das Hauptprogramm einbinden können. Geben Sie dies daher in ExtraModule/CMakeLists.txt an:

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

qt_add_library(extra_module STATIC)
qt_add_qml_module(extra_module
    URI "ExtraModule"
    VERSION 1.0
    QML_FILES
        Extra.qml
    SOURCES
        extrathing.cpp extrathing.h
    RESOURCE_PREFIX /
)

Dies erzeugt zwei Ziele: extra_module für die unterstützende Bibliothek und extra_moduleplugin für das Plugin. Da das Plugin ebenfalls eine statische Bibliothek ist, kann es nicht zur Laufzeit geladen werden.

In myProject/CMakeLists.txt müssen Sie das QML-Modul angeben, zu dem main.qml und alle in onething.h deklarierten Typen gehören:

qt_add_executable(main_program main.cpp)

qt_add_qml_module(main_program
    VERSION 1.0
    URI myProject
    QML_FILES
        main.qml
    SOURCES
        onething.cpp onething.h

)

Von dort aus fügen Sie das Unterverzeichnis für das zusätzliche Modul hinzu:

add_subdirectory(ExtraModule)

Um sicherzustellen, dass die Verknüpfung des Zusatzmoduls korrekt funktioniert, müssen Sie:

  • Definieren Sie ein Symbol im Zusatzmodul.
  • Einen Verweis auf das Symbol im Hauptprogramm erstellen.

QML-Plugins enthalten ein Symbol, das Sie für diesen Zweck verwenden können. Sie können das Makro Q_IMPORT_QML_PLUGIN verwenden, um einen Verweis auf dieses Symbol zu erstellen. Fügen Sie den folgenden Code in die Datei main.cpp ein:

#include <QtQml/QQmlExtensionPlugin>
Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)

ExtraModulePlugin ist der Name der generierten Plugin-Klasse. Er setzt sich aus der Modul-URI und dem angehängten Plugin zusammen. Binden Sie dann in der CMakeLists.txt des Hauptprogramms das Plugin, nicht die unterstützende Bibliothek, in das Hauptprogramm ein:

target_link_libraries(main_program PRIVATE extra_moduleplugin)

Versionen

QML hat ein komplexes System, um den Komponenten und Modulen Versionen zuzuweisen. In den meisten Fällen sollten Sie das Ganze ignorieren:

  1. Niemals eine Version zu Ihren Import-Anweisungen hinzufügen
  2. Niemals eine Version in qt_add_qml_module angeben
  3. Niemals QML_ADDED_IN_VERSION oder QT_QML_SOURCE_VERSIONS verwenden
  4. Verwenden Sie niemals Q_REVISION oder das REVISION() Attribut in Q_PROPERTY
  5. Vermeiden von unqualifiziertem Zugriff
  6. Großzügige Verwendung von Import-Namespaces

Die Versionierung wird idealerweise außerhalb der Sprache selbst gehandhabt. Sie können z.B. getrennte Importpfade für verschiedene Gruppen von QML-Modulen verwenden. Oder Sie verwenden einen von Ihrem Betriebssystem bereitgestellten Versionskontrollmechanismus, um Pakete mit QML-Modulen zu installieren oder zu deinstallieren.

In einigen Fällen können die Qt-eigenen QML-Module ein unterschiedliches Verhalten zeigen, je nachdem, welche Version importiert wird. Insbesondere, wenn eine Eigenschaft zu einer QML-Komponente hinzugefügt wird und Ihr Code einen unqualifizierten Zugriff auf eine andere Eigenschaft mit demselben Namen enthält, wird Ihr Code abbrechen. Im folgenden Beispiel wird sich der Code je nach Qt-Version unterschiedlich verhalten, da die Eigenschaft topLeftRadius in Qt 6.7 hinzugefügt wurde:

import QtQuick

Item {
    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // correct for Qt version < 6.7 but uses Rectangle's topLeftRadius in 6.7
        objectName: "top left radius:" + topLeftRadius
    }
}

Die Lösung ist hier, den unqualifizierten Zugriff zu vermeiden. qmllint kann verwendet werden, um solche Probleme zu finden. Das folgende Beispiel greift auf die eigentlich gemeinte Eigenschaft auf eine sichere, qualifizierte Weise zu:

import QtQuick

Item {
    id: root

    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // never mixes up topLeftRadius with unrelated Rectangle's topLeftRadius
        objectName: "top left radius:" + root.topLeftRadius
    }
}

Sie können die Inkompatibilität auch vermeiden, indem Sie eine bestimmte Version von QtQuick importieren:

// make sure Rectangle has no topLeftRadius property
import QtQuick 6.6

Item {
    property real topLeftRadius: 24
    Rectangle {
        objectName: "top left radius:" + topLeftRadius
    }
}

Ein weiteres Problem, das durch die Versionierung gelöst wird, ist die Tatsache, dass QML-Komponenten, die von verschiedenen Modulen importiert werden, sich gegenseitig überschatten können. Wenn im folgenden Beispiel MyModule eine Komponente mit dem Namen Rectangle in einer neueren Version einführt, wäre das von diesem Dokument erstellte Rectangle kein QQuickRectangle mehr, sondern das neue Rectangle, das von MyModule eingeführt wurde.

import QtQuick
import MyModule

Rectangle {
    // MyModule's Rectangle, not QtQuick's
}

Eine gute Möglichkeit, die Schattenbildung zu vermeiden, wäre, QtQuick und/oder MyModule wie folgt in Typ-Namensräume zu importieren:

import QtQuick as QQ
import MyModule as MM

QQ.Rectangle {
   // QtQuick's Rectangle
}

Wenn Sie alternativ MyModule mit einer festen Version importieren und die neue Komponente über QML_ADDED_IN_VERSION oder QT_QML_SOURCE_VERSIONS ein korrektes Versions-Tag erhält, wird die Abschattung ebenfalls vermieden:

import QtQuick 6.6

// Types introduced after 1.0 are not available, like Rectangle for example
import MyModule 1.0

Rectangle {
    // QtQuick's Rectangle
}

Damit dies funktioniert, müssen Sie Versionen in MyModule verwenden. Es gibt ein paar Dinge zu beachten.

Wenn Sie Versionen hinzufügen, fügen Sie sie überall hinzu

Sie müssen ein VERSION Attribut zu qt_add_qml_module hinzufügen. Die Version sollte die neueste Version sein, die von Ihrem Modul bereitgestellt wird. Ältere Minor-Versionen der gleichen Major-Version werden automatisch registriert. Für ältere Hauptversionen, siehe unten.

Sie sollten QML_ADDED_IN_VERSION oder QT_QML_SOURCE_VERSIONS zu jedem Typ hinzufügen, der nicht in Version x.0 Ihres Moduls eingeführt wurde, wobei x die aktuelle Hauptversion ist.

Wenn Sie vergessen, ein Versions-Tag hinzuzufügen, wird die Komponente in allen Versionen verfügbar sein, was die Versionierung unwirksam macht.

Es gibt jedoch keine Möglichkeit, den in QML definierten Eigenschaften, Methoden und Signalen Versionen hinzuzufügen. Die einzige Möglichkeit, QML-Dokumente zu versionieren, besteht darin, ein neues Dokument mit separaten QT_QML_SOURCE_VERSIONS für jede Änderung hinzuzufügen.

Versionen sind nicht transitiv

Wenn eine Komponente aus Ihrem Modul A ein anderes Modul B importiert und einen Typ aus diesem Modul als Wurzelelement instanziiert, dann ist die Importversion von B für die verfügbaren Eigenschaften der resultierenden Komponente relevant, unabhängig davon, welche Version von A von einem Benutzer importiert wird.

Betrachten Sie eine Datei TypeFromA.qml mit der Version 2.6 im Modul A:

import B 2.7

// Exposes TypeFromB 2.7, no matter what version of A is imported
TypeFromB { }

Betrachten Sie nun einen Benutzer von TypeFromA:

import A 2.6

// This is TypeFromB 2.7.
TypeFromA { }

Der Benutzer hofft, die Version 2.6 zu sehen, erhält aber tatsächlich die Version 2.7 der Basisklasse TypeFromB.

Um sicher zu sein, müssen Sie also nicht nur Ihre QML-Dateien duplizieren und ihnen neue Versionen geben, wenn Sie selbst Eigenschaften hinzufügen, sondern auch, wenn Sie die Versionen von Modulen, die Sie importieren, ändern.

Qualifizierter Zugriff kennt keine Versionierung

Die Versionierung wirkt sich nur auf den unqualifizierten Zugriff auf Mitglieder eines Typs oder den Typ selbst aus. Wenn Sie im Beispiel mit topLeftRadius this.topLeftRadius schreiben, wird die Eigenschaft aufgelöst, wenn Sie Qt 6.7 verwenden, auch wenn Sie import QtQuick 6.6 schreiben.

Versionen und Revisionen

Mit QML_ADDED_IN_VERSION und den Zwei-Argument-Varianten von Q_REVISION und Q_PROPERTY's REVISION() können Sie nur Versionen deklarieren, die eng mit der metaobject's Revision gekoppelt sind, wie sie in QMetaMethod::revision und QMetaProperty::revision dargestellt sind. Das bedeutet, dass alle Typen in Ihrer Typenhierarchie dem gleichen Versionsschema folgen müssen. Dies schließt alle Typen ein, die von Qt selbst bereitgestellt werden und von denen Sie erben.

Mit qmlRegisterType und verwandten Funktionen können Sie jedes Mapping zwischen Metaobjekt-Revisionen und Typversionen registrieren. Sie müssen dann die Ein-Argument-Formen von Q_REVISION und das REVISION -Attribut von Q_PROPERTY verwenden. Dies kann jedoch ziemlich komplex und verwirrend werden und wird nicht empfohlen.

Exportieren mehrerer Hauptversionen aus demselben Modul

qt_add_qml_module berücksichtigt standardmäßig die in seinem VERSION-Argument angegebene Hauptversion, auch wenn die einzelnen Typen in ihrer hinzugefügten spezifischen Version über QT_QML_SOURCE_VERSIONS oder Q_REVISION andere Versionen deklarieren. Wenn ein Modul unter mehr als einer Version verfügbar ist, müssen Sie auch entscheiden, unter welchen Versionen die einzelnen QML-Dateien verfügbar sind. Um weitere Hauptversionen zu deklarieren, können Sie die Option PAST_MAJOR_VERSIONS für qt_add_qml_module sowie die Eigenschaft QT_QML_SOURCE_VERSIONS für einzelne QML-Dateien verwenden.

set_source_files_properties(Thing.qml
    PROPERTIES
        QT_QML_SOURCE_VERSIONS "1.4;2.0;3.0"
)

set_source_files_properties(OtherThing.qml
    PROPERTIES
        QT_QML_SOURCE_VERSIONS "2.2;3.0"
)

qt_add_qml_module(my_module
    URI MyModule
    VERSION 3.2
    PAST_MAJOR_VERSIONS
        1 2
    QML_FILES
        Thing.qml
        OtherThing.qml
        OneMoreThing.qml
    SOURCES
        everything.cpp everything.h
)

MyModule ist in den Hauptversionen 1, 2 und 3 verfügbar. Die maximal verfügbare Version ist 3.2. Sie können jede Version 1.x oder 2.x mit einem positiven x importieren. Für Thing.qml und OtherThing.qml haben wir explizite Versionsinformationen hinzugefügt. Thing.qml ist ab Version 1.4 verfügbar, und OtherThing.qml ist ab Version 2.2 verfügbar. Sie müssen auch die späteren Versionen in jedem set_source_files_properties() angeben, weil Sie QML-Dateien aus einem Modul entfernen können, wenn Sie die Hauptversion erhöhen. Für OneMoreThing.qml gibt es keine explizite Versionsangabe. Dies bedeutet, dass OneMoreThing.qml in allen Hauptversionen ab der Nebenversion 0 verfügbar ist.

Mit dieser Einstellung wird der generierte Registrierungscode das Modul versions mit qmlRegisterModule() für jede der Hauptversionen registrieren. Auf diese Weise können alle Versionen importiert werden.

Benutzerdefinierte Verzeichnislayouts

Die einfachste Art, QML-Module zu strukturieren, besteht darin, sie in Verzeichnissen zu speichern, die nach ihren URIs benannt sind. Zum Beispiel würde ein Modul My.Extra.Module in einem Verzeichnis My/Extra/Module relativ zu der Anwendung, die es verwendet, liegen. Auf diese Weise können sie zur Laufzeit und von allen Werkzeugen leicht gefunden werden.

In komplexeren Projekten kann diese Konvention zu einschränkend sein. Sie könnten zum Beispiel alle QML-Module an einem Ort gruppieren wollen, um das Stammverzeichnis des Projekts nicht zu verschmutzen. Oder Sie möchten ein einzelnes Modul in mehreren Anwendungen wiederverwenden. Für diese Fälle kann QT_QML_OUTPUT_DIRECTORY in Kombination mit RESOURCE_PREFIX und IMPORT_PATH verwendet werden.

Um QML-Module in einem bestimmten Ausgabeverzeichnis zu sammeln, zum Beispiel einem Unterverzeichnis "qml" im Build-Verzeichnis QT_QML_OUTPUT_DIRECTORY, setzen Sie Folgendes in der CMakeLists.txt auf oberster Ebene:

set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)

Die Ausgabeverzeichnisse der QML-Module werden an den neuen Ort verschoben. Ebenso werden die Aufrufe qmllint und qmlcachegen automatisch angepasst, um das neue Ausgabeverzeichnis als Importpfad zu verwenden. Da das neue Ausgabeverzeichnis nicht Teil des standardmäßigen QML-Importpfads ist, müssen Sie es zur Laufzeit explizit hinzufügen, damit die QML-Module gefunden werden können.

Nun, da das physische Dateisystem erledigt ist, möchten Sie die QML-Module vielleicht noch an einen anderen Ort im Ressourcendateisystem verschieben. Hierfür ist die Option RESOURCE_PREFIX gedacht. Sie müssen sie in jedem qt_add_qml_module separat angeben. Das QML-Modul wird dann unter dem angegebenen Präfix platziert, wobei ein aus der URI generierter Zielpfad angehängt wird. Betrachten Sie zum Beispiel das folgende Modul:

qt_add_qml_module(
    URI My.Great.Module
    VERSION 1.0
    RESOURCE_PREFIX /example.com/qml
    QML_FILES
        A.qml
        B.qml
)

Damit wird ein Verzeichnis example.com/qml/My/Great/Module zum Ressourcendateisystem hinzugefügt und das oben definierte QML-Modul darin abgelegt. Es ist nicht unbedingt erforderlich, das Ressourcenpräfix zum QML-Importpfad hinzuzufügen, da das Modul weiterhin im physischen Dateisystem gefunden werden kann. Im Allgemeinen ist es jedoch eine gute Idee, das Ressourcenpräfix zum QML-Importpfad hinzuzufügen, da das Laden aus dem Ressourcendateisystem bei den meisten Modulen schneller ist als das Laden aus dem physischen Dateisystem.

Wenn die QML-Module in einem größeren Projekt mit mehreren Importpfaden verwendet werden sollen, müssen Sie einen zusätzlichen Schritt durchführen: Selbst wenn Sie die Importpfade zur Laufzeit hinzufügen, haben Werkzeuge wie qmllint keinen Zugriff darauf und finden möglicherweise nicht die richtigen Abhängigkeiten. Verwenden Sie IMPORT_PATH, um dem Tooling die zusätzlichen Pfade mitzuteilen, die es berücksichtigen muss. Ein Beispiel:

qt_add_qml_module(
    URI My.Dependent.Module
    VERSION 1.0
    QML_FILES
        C.qml
    IMPORT_PATH "/some/where/else"
)

Eliminierung des Dateisystemzugriffs zur Laufzeit

Wenn alle QML-Module immer aus dem Ressourcendateisystem geladen werden, können Sie die Anwendung als eine einzige Binärdatei bereitstellen.

Wenn die QTP0001-Richtlinie auf NEW gesetzt ist, wird das RESOURCE_PREFIX -Argument für qt_add_qml_module() standardmäßig auf /qt/qml/ gesetzt, so dass Ihre Module in :/qt/qml/ im Ressourcendateisystem abgelegt werden. Dies ist Teil des standardmäßigen QML-Importpfades, wird aber nicht von Qt selbst verwendet. Für Module, die innerhalb Ihrer Anwendung verwendet werden sollen, ist dies der richtige Ort.

Wenn Sie stattdessen ein benutzerdefiniertes RESOURCE_PREFIX angegeben haben, müssen Sie das benutzerdefinierte Ressourcenpräfix zum QML-Importpfad hinzufügen. Sie können auch mehrere Ressourcenpräfixe hinzufügen:

QQmlEngine qmlEngine;
qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix"));
qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix"));
// Use qmlEngine to load the main.qml file.

Dies kann notwendig sein, wenn Sie Bibliotheken von Drittanbietern verwenden, um Modulnamenskonflikte zu vermeiden. In allen anderen Fällen wird von der Verwendung eines benutzerdefinierten Ressource-Präfixes abgeraten.

Der Pfad :/qt-project.org/imports/ ist auch Teil des standardmäßigen QML-Importpfads. Für Module, die über verschiedene Projekte oder Qt-Versionen hinweg häufig wiederverwendet werden, ist :/qt-project.org/imports/ als Ressourcenpräfix akzeptabel. Qt-eigene QML-Module werden jedoch dort platziert. Sie müssen darauf achten, diese nicht zu überschreiben.

Fügen Sie keine unnötigen Importpfade hinzu. Die QML-Engine könnte Ihre Module dann an der falschen Stelle finden. Dies kann zu Problemen führen, die sich nur in bestimmten Umgebungen reproduzieren lassen.

Einbinden eigener QML-Plugins

Wenn Sie ein image provider im QML-Modul bündeln, müssen Sie die Methode QQmlEngineExtensionPlugin::initializeEngine() implementieren. Dies wiederum macht es notwendig, ein eigenes Plugin zu schreiben. Um diesen Anwendungsfall zu unterstützen, kann NO_GENERATE_PLUGIN_SOURCE verwendet werden.

Betrachten wir ein Modul, das seine eigene Plugin-Quelle bereitstellt:

qt_add_qml_module(imageproviderplugin
    VERSION 1.0
    URI "ImageProvider"
    PLUGIN_TARGET imageproviderplugin
    NO_PLUGIN_OPTIONAL
    NO_GENERATE_PLUGIN_SOURCE
    CLASS_NAME ImageProviderExtensionPlugin
    QML_FILES
        AAA.qml
        BBB.qml
    SOURCES
        moretypes.cpp moretypes.h
        myimageprovider.cpp myimageprovider.h
        plugin.cpp
)

Sie können einen Bildanbieter in myimageprovider.h deklarieren, etwa so:

class MyImageProvider : public QQuickImageProvider
{
    [...]
};

In plugin.cpp können Sie dann die QQmlEngineExtensionPlugin definieren:

#include <myimageprovider.h>
#include <QtQml/qqmlextensionplugin.h>

class ImageProviderExtensionPlugin : public QQmlEngineExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
public:
    void initializeEngine(QQmlEngine *engine, const char *uri) final
    {
        Q_UNUSED(uri);
        engine->addImageProvider("myimg", new MyImageProvider);
    }
};

Dadurch wird der Bildanbieter verfügbar gemacht. Das Plugin und die unterstützende Bibliothek befinden sich beide in demselben CMake-Ziel imageproviderplugin. Dies geschieht, damit der Linker nicht in verschiedenen Szenarien Teile des Moduls fallen lässt.

Da das Plugin einen Image-Provider erstellt, verfügt es nicht mehr über eine triviale initializeEngine Funktion. Daher ist das Plugin nicht mehr optional.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.