Schreiben von QML-Erweiterungen mit C++

Das Qt Qml Modul bietet eine Reihe von APIs zur Erweiterung von QML durch C++-Erweiterungen. Sie können Erweiterungen schreiben, um Ihre eigenen QML-Typen hinzuzufügen, bestehende Qt-Typen zu erweitern oder C/C++-Funktionen aufzurufen, die vom normalen QML-Code aus nicht zugänglich sind.

Dieses Tutorial zeigt, wie man eine QML-Erweiterung mit C++ schreibt, die zentrale QML-Funktionen wie Eigenschaften, Signale und Bindungen enthält. Es wird auch gezeigt, wie Erweiterungen durch Plugins bereitgestellt werden können.

Viele der in diesem Tutorial behandelten Themen sind in Übersicht - QML- und C++-Integration und den entsprechenden Unterthemen der Dokumentation ausführlicher dokumentiert. Insbesondere die Unterthemen Exposing Attributes of C++ Classes to QML und Defining QML Types from C++ könnten für Sie von Interesse sein.

Öffnen der Tutorial-Quellen

Der Code in diesem Tutorial ist als Teil der Qt-Quellen verfügbar. Wenn Sie Qt mit dem Qt Online Installer installiert haben, finden Sie die Quellen im Qt-Installationsverzeichnis unter Examples/Qt-6.8.2/qml/tutorials/extending-qml/.

Projekt von Grund auf neu erstellen

Alternativ können Sie dem Tutorial auch folgen, indem Sie die Quellen von Grund auf neu erstellen: Erstellen Sie für jedes Kapitel ein neues Projekt unter Verwendung der Qt Quick Anwendungsvorlage in Qt Creator, wie in Qt Creator beschrieben: Erstellen von Qt Quick Projekten. Dann folgen Sie den Anweisungen, indem Sie den generierten Skelettcode anpassen und erweitern.

Kapitel 1: Erstellen eines neuen Typs

extending-qml/chapter1-basics

Eine häufige Aufgabe bei der Erweiterung von QML ist es, einen neuen QML-Typ zu erstellen, der eine benutzerdefinierte Funktionalität unterstützt, die über die der eingebauten Qt Quick types. Dies kann beispielsweise geschehen, um bestimmte Datenmodelle zu implementieren, Typen mit benutzerdefinierten Mal- und Zeichenfunktionen zu versehen oder auf Systemfunktionen wie die Netzwerkprogrammierung zuzugreifen, die über die eingebauten QML-Funktionen nicht zugänglich sind.

In diesem Tutorial wird gezeigt, wie man die C++-Klassen im Modul Qt Quick verwendet, um QML zu erweitern. Das Endergebnis wird eine einfache Tortendiagramm-Anzeige sein, die durch mehrere benutzerdefinierte QML-Typen implementiert wird, die durch QML-Funktionen wie Bindungen und Signale miteinander verbunden sind und der QML Runtime über ein Plugin zur Verfügung gestellt werden.

Zunächst erstellen wir einen neuen QML-Typ namens "PieChart", der zwei Eigenschaften hat: einen Namen und eine Farbe. Wir werden ihn in einem importierbaren Typ-Namensraum namens "Charts" mit einer Version von 1.0 zur Verfügung stellen.

Wir möchten, dass dieser PieChart Typ von QML aus wie folgt verwendet werden kann:

import Charts

PieChart {
    width: 100; height: 100
    name: "A simple pie chart"
    color: "red"
}

Dazu benötigen wir eine C++-Klasse, die diesen Typ PieChart und seine Eigenschaften kapselt. Da QML das Metaobjektsystem von Qt ausgiebig nutzt, muss diese neue Klasse:

Klassendeklaration

Hier ist unsere Klasse PieChart, definiert in piechart.h:

#include <QtQuick/QQuickPaintedItem>
#include <QColor>

class PieChart : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName FINAL)
    Q_PROPERTY(QColor color READ color WRITE setColor FINAL)
    QML_ELEMENT

public:
    PieChart(QQuickItem *parent = nullptr);

    QString name() const;
    void setName(const QString &name);

    QColor color() const;
    void setColor(const QColor &color);

    void paint(QPainter *painter) override;

private:
    QString m_name;
    QColor m_color;
};

Die Klasse erbt von QQuickPaintedItem, weil wir QQuickPaintedItem::paint() überschreiben wollen, um Zeichenoperationen mit der QPainter API durchzuführen. Wenn die Klasse nur einen Datentyp repräsentieren würde und nicht ein Element wäre, das tatsächlich angezeigt werden müsste, könnte sie einfach von QObject erben. Oder, wenn wir die Funktionalität einer bestehenden QObject-basierten Klasse erweitern wollen, könnte sie stattdessen von dieser Klasse erben. Wenn wir ein visuelles Element erstellen wollen, das keine Zeichenoperationen mit der API von QPainter durchführen muss, können wir auch einfach eine Unterklasse von QQuickItem erstellen.

Die Klasse PieChart definiert die beiden Eigenschaften name und color mit dem Makro Q_PROPERTY und setzt QQuickPaintedItem::paint() außer Kraft. Die Klasse PieChart wird mit dem Makro QML_ELEMENT registriert, damit sie von QML aus verwendet werden kann. Wenn Sie die Klasse nicht registrieren, ist App.qml nicht in der Lage, eine PieChart zu erstellen.

qmake-Einrichtung

Damit die Registrierung wirksam wird, wird die Option qmltypes zu CONFIG in der Projektdatei hinzugefügt, und es werden QML_IMPORT_NAME und QML_IMPORT_MAJOR_VERSION angegeben:

CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

CMake-Setup

Damit die Registrierung auch bei der Verwendung von CMake wirksam wird, verwenden Sie den Befehl qt_add_qml_module:

qt_add_qml_module(chapter1-basics
    URI Charts
    QML_FILES App.qml
    DEPENDENCIES QtQuick
)

Klassenimplementierung

Die Klassenimplementierung in piechart.cpp setzt einfach die Werte m_name und m_color und gibt sie entsprechend zurück, und implementiert paint(), um ein einfaches Tortendiagramm zu zeichnen:

PieChart::PieChart(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
}
...
void PieChart::paint(QPainter *painter)
{
    QPen pen(m_color, 2);
    painter->setPen(pen);
    painter->setRenderHints(QPainter::Antialiasing, true);
    painter->drawPie(boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16);
}

QML-Verwendung

Nachdem wir nun den Typ PieChart definiert haben, werden wir ihn in QML verwenden. Die Datei App.qml erstellt ein Element PieChart und zeigt die Details des Kreisdiagramms mit einem standardmäßigen QML-Element Text an:

import Charts
import QtQuick

Item {
    width: 300; height: 200

    PieChart {
        id: aPieChart
        anchors.centerIn: parent
        width: 100; height: 100
        name: "A simple pie chart"
        color: "red"
    }

    Text {
        anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
        text: aPieChart.name
    }
}

Beachten Sie, dass die Farbe zwar als String in QML angegeben wird, aber automatisch in ein QColor Objekt für die PieChart color Eigenschaft konvertiert wird. Automatische Konvertierungen sind für verschiedene andere Wertetypen vorgesehen. Zum Beispiel kann ein String wie "640x480" automatisch in einen QSize Wert konvertiert werden.

Wir werden auch eine C++-Anwendung erstellen, die ein QQuickView verwendet, um App.qml auszuführen und anzuzeigen.

Hier ist die Anwendung main.cpp:

#include "piechart.h"
#include <QtQuick/QQuickView>
#include <QGuiApplication>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQuickView view;
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.loadFromModule("Charts", "App");
    view.show();
    return QGuiApplication::exec();
}

Projekt erstellen

Um das Projekt zu erstellen, binden wir die Dateien ein, linken gegen die Bibliotheken und definieren einen Typ-Namensraum namens "Charts" mit Version 1.0 für alle Typen, die QML ausgesetzt sind.

Verwendung von qmake:

QT += qml quick

CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

HEADERS += piechart.h
SOURCES += piechart.cpp \
           main.cpp

RESOURCES += chapter1-basics.qrc

DESTPATH = $$[QT_INSTALL_EXAMPLES]/qml/tutorials/extending-qml/chapter1-basics
target.path = $$DESTPATH
INSTALLS += target

Verwendung von CMake:

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

cmake_minimum_required(VERSION 3.16)
project(chapter1-basics LANGUAGES CXX)

find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)

qt_standard_project_setup(REQUIRES 6.8)

qt_add_executable(chapter1-basics
    main.cpp
    piechart.cpp piechart.h
)

set_target_properties(chapter1-basics PROPERTIES
    WIN32_EXECUTABLE TRUE
    MACOSX_BUNDLE TRUE
)

target_link_libraries(chapter1-basics PUBLIC
    Qt6::Core
    Qt6::Gui
    Qt6::Qml
    Qt6::Quick
)
qt_add_qml_module(chapter1-basics
    URI Charts
    QML_FILES App.qml
    DEPENDENCIES QtQuick
)
install(TARGETS chapter1-basics
    BUNDLE  DESTINATION .
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

qt_generate_deploy_qml_app_script(
    TARGET chapter1-basics
    OUTPUT_SCRIPT deploy_script
    MACOS_BUNDLE_POST_BUILD
    NO_UNSUPPORTED_PLATFORM_ERROR
    DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})

Jetzt können wir die Anwendung erstellen und ausführen:

Hinweis: Möglicherweise wird eine Warnung angezeigt Expression ... depends on non-NOTIFYable properties: PieChart::name. Dies geschieht, weil wir eine Bindung an die beschreibbare Eigenschaft name hinzufügen, aber noch kein Benachrichtigungssignal für diese definiert haben. Die QML-Engine kann daher die Bindung nicht aktualisieren, wenn sich der Wert name ändert. Dies wird in den folgenden Kapiteln behandelt.

Kapitel 2: Verbindung zu C++ Methoden und Signalen

extending-qml/chapter2-methods

Angenommen, PieChart soll eine "clearChart()"-Methode haben, die das Diagramm löscht und dann ein "chartCleared"-Signal ausgibt. Unser App.qml könnte dann clearChart() aufrufen und chartCleared() Signale wie dieses empfangen:

import Charts
import QtQuick

Item {
    width: 300; height: 200

    PieChart {
        id: aPieChart
        anchors.centerIn: parent
        width: 100; height: 100
        color: "red"

        onChartCleared: console.log("The chart has been cleared")
    }

    MouseArea {
        anchors.fill: parent
        onClicked: aPieChart.clearChart()
    }

    Text {
        anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
        text: "Click anywhere to clear the chart"
    }
}

Dazu fügen wir unserer C++ Klasse eine clearChart() Methode und ein chartCleared() Signal hinzu:

class PieChart : public QQuickPaintedItem
{
    ...
public:
    ...
    Q_INVOKABLE void clearChart();

signals:
    void chartCleared();
    ...
};

Die Verwendung von Q_INVOKABLE macht die Methode clearChart() für das Qt-Meta-Object-System und damit auch für QML verfügbar.

Hinweis: Sie können die Methode auch als Qt-Slot deklarieren, anstatt Q_INVOKABLE zu verwenden, da öffentliche und geschützte Slots auch von QML aus aufgerufen werden können (private Slots können Sie nicht aufrufen).

Die Methode clearChart() ändert die Farbe in Qt::transparent, färbt das Diagramm neu und gibt dann das Signal chartCleared() aus:

void PieChart::clearChart()
{
    setColor(QColor(Qt::transparent));
    update();

    emit chartCleared();
}

Wenn wir nun die Anwendung ausführen und auf das Fenster klicken, wird das Tortendiagramm ausgeblendet, und die Anwendung wird ausgegeben:

qml: The chart has been cleared

Kapitel 3: Hinzufügen von Eigenschaftsbindungen

extending-qml/chapter3-bindings

Die Eigenschaftsbindung ist eine leistungsstarke Funktion von QML, mit der Werte verschiedener Typen automatisch synchronisiert werden können. Sie verwendet Signale, um die Werte anderer Typen zu benachrichtigen und zu aktualisieren, wenn Eigenschaftswerte geändert werden.

Lassen Sie uns die Eigenschaftsbindung für die Eigenschaft color aktivieren. Das heißt, wenn wir Code wie diesen haben:

import Charts
import QtQuick

Item {
    width: 300; height: 200

    Row {
        anchors.centerIn: parent
        spacing: 20

        PieChart {
            id: chartA
            width: 100; height: 100
            color: "red"
        }

        PieChart {
            id: chartB
            width: 100; height: 100
            color: chartA.color
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: { chartA.color = "blue" }
    }

    Text {
        anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
        text: "Click anywhere to change the chart color"
    }
}

Die Anweisung "color: chartA.color" bindet den Wert color von chartB an den Wert color von chartA. Immer wenn sich der Wert color von chartA ändert, wird der Wert color von chartB auf denselben Wert aktualisiert. Wenn das Fenster angeklickt wird, ändert der onClicked -Handler in MouseArea die Farbe von chartA, wodurch beide Diagramme die Farbe Blau erhalten.

Es ist einfach, die Eigenschaftsbindung für die Eigenschaft color zu aktivieren. Wir fügen ein NOTIFY-Merkmal in die Q_PROPERTY()-Deklaration ein, um anzugeben, dass ein "colorChanged"-Signal ausgegeben wird, sobald sich der Wert ändert.

class PieChart : public QQuickPaintedItem
{
    ...
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
public:
    ...
signals:
    void colorChanged();
    ...
};

Anschließend wird dieses Signal in setColor() ausgegeben:

void PieChart::setColor(const QColor &color)
{
    if (color != m_color) {
        m_color = color;
        update();   // repaint with the new color
        emit colorChanged();
    }
}

Es ist wichtig, dass setColor() überprüft, ob sich der Farbwert tatsächlich geändert hat, bevor es colorChanged() sendet. Dadurch wird sichergestellt, dass das Signal nicht unnötig ausgegeben wird und Schleifen vermieden werden, wenn andere Typen auf die Wertänderung reagieren.

Die Verwendung von Bindungen ist ein wesentlicher Bestandteil von QML. Sie sollten immer NOTIFY-Signale für Eigenschaften hinzufügen, wenn diese implementiert werden können, so dass Ihre Eigenschaften in Bindungen verwendet werden können. Eigenschaften, die nicht gebunden werden können, lassen sich nicht automatisch aktualisieren und können in QML nicht so flexibel verwendet werden. Da Bindungen bei der Verwendung von QML so häufig aufgerufen werden und man sich auf sie verlässt, können Benutzer Ihrer benutzerdefinierten QML-Typen ein unerwartetes Verhalten feststellen, wenn Bindungen nicht implementiert sind.

Kapitel 4: Verwendung von benutzerdefinierten Eigenschaftstypen

extending-qml/chapter4-customPropertyTypes

Der Typ PieChart hat derzeit eine Eigenschaft vom Typ String und eine Eigenschaft vom Typ Color. Er könnte viele andere Arten von Eigenschaften haben. Zum Beispiel könnte er eine Eigenschaft vom Typ int haben, um einen Bezeichner für jedes Diagramm zu speichern:

// C++
class PieChart : public QQuickPaintedItem
{
    Q_PROPERTY(int chartId READ chartId WRITE setChartId NOTIFY chartIdChanged)
    ...

public:
    void setChartId(int chartId);
    int chartId() const;
    ...

signals:
    void chartIdChanged();
};

// QML
PieChart {
    ...
    chartId: 100
}

Abgesehen von int könnten wir verschiedene andere Eigenschaftstypen verwenden. Viele der Qt-Datentypen wie QColor, QSize und QRect werden automatisch von QML unterstützt. (Eine vollständige Liste finden Sie in der Dokumentation Datentypkonvertierung zwischen QML und C++ ).

Wenn wir eine Eigenschaft erstellen wollen, deren Typ von QML nicht standardmäßig unterstützt wird, müssen wir den Typ bei der QML-Engine registrieren.

Ersetzen wir zum Beispiel die Verwendung von property durch einen Typ namens "PieSlice", der eine color Eigenschaft hat. Anstatt eine Farbe zuzuweisen, weisen wir einen PieSlice Wert zu, der selbst eine color enthält:

import Charts
import QtQuick

Item {
    width: 300; height: 200

    PieChart {
        id: chart
        anchors.centerIn: parent
        width: 100; height: 100

        pieSlice: PieSlice {
            anchors.fill: parent
            color: "red"
        }
    }

    Component.onCompleted: console.log("The pie is colored " + chart.pieSlice.color)
}

Wie PieChart erbt auch dieser neue Typ PieSlice von QQuickPaintedItem und deklariert seine Eigenschaften mit Q_PROPERTY():

class PieSlice : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QColor color READ color WRITE setColor FINAL)
    QML_ELEMENT

public:
    PieSlice(QQuickItem *parent = nullptr);

    QColor color() const;
    void setColor(const QColor &color);

    void paint(QPainter *painter) override;

private:
    QColor m_color;
};

Um sie in PieChart zu verwenden, ändern wir die Eigenschaftsdeklaration color und die zugehörigen Methodensignaturen:

class PieChart : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(PieSlice* pieSlice READ pieSlice WRITE setPieSlice FINAL)
    ...
public:
    ...
    PieSlice *pieSlice() const;
    void setPieSlice(PieSlice *pieSlice);
    ...
};

Bei der Implementierung von setPieSlice() muss eine Sache beachtet werden. PieSlice ist ein visuelles Element, daher muss es mit QQuickItem::setParentItem() als untergeordnetes Element von PieChart festgelegt werden, damit PieChart weiß, dass dieses untergeordnete Element gezeichnet werden soll, wenn sein Inhalt gezeichnet wird:

void PieChart::setPieSlice(PieSlice *pieSlice)
{
    m_pieSlice = pieSlice;
    pieSlice->setParentItem(this);
}

Wie der Typ PieChart muss auch der Typ PieSlice mit QML_ELEMENT in QML exponiert werden.

class PieSlice : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QColor color READ color WRITE setColor FINAL)
    QML_ELEMENT

public:
    PieSlice(QQuickItem *parent = nullptr);

    QColor color() const;
    void setColor(const QColor &color);

    void paint(QPainter *painter) override;

private:
    QColor m_color;
};
    ...

Wie bei PieChart fügen wir den Typ-Namensraum "Charts", Version 1.0, zu unserer Build-Datei hinzu:

Mit qmake:

QT += qml quick

CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

HEADERS += piechart.h \
           pieslice.h
SOURCES += piechart.cpp \
           pieslice.cpp \
           main.cpp

RESOURCES += chapter4-customPropertyTypes.qrc

DESTPATH = $$[QT_INSTALL_EXAMPLES]/qml/tutorials/extending-qml/chapter4-customPropertyTypes
target.path = $$DESTPATH
INSTALLS += target

CMake verwenden:

    ...
qt_add_executable(chapter4-customPropertyTypes
    main.cpp
    piechart.cpp piechart.h
    pieslice.cpp pieslice.h
)
qt_add_qml_module(chapter4-customPropertyTypes
    URI Charts
    QML_FILES App.qml
    DEPENDENCIES QtQuick
)
    ...

Kapitel 5: Listeneigenschaftstypen verwenden

extending-qml/chapter5-listproperties

Momentan kann ein PieChart nur eine PieSlice haben. Idealerweise hätte ein Diagramm mehrere Slices, mit unterschiedlichen Farben und Größen. Um dies zu erreichen, könnten wir eine slices Eigenschaft haben, die eine Liste von PieSlice Elementen akzeptiert:

import Charts
import QtQuick

Item {
    width: 300; height: 200

    PieChart {
        anchors.centerIn: parent
        width: 100; height: 100

        slices: [
            PieSlice {
                anchors.fill: parent
                color: "red"
                fromAngle: 0; angleSpan: 110
            },
            PieSlice {
                anchors.fill: parent
                color: "black"
                fromAngle: 110; angleSpan: 50
            },
            PieSlice {
                anchors.fill: parent
                color: "blue"
                fromAngle: 160; angleSpan: 100
            }
        ]
    }
}

Dazu ersetzen wir die Eigenschaft pieSlice in PieChart durch eine Eigenschaft slices, die als Typ QQmlListProperty deklariert ist. Die Klasse QQmlListProperty ermöglicht die Erstellung von Listeneigenschaften in QML-Erweiterungen. Wir ersetzen die Funktion pieSlice() durch eine Funktion slices(), die eine Liste von Slices zurückgibt, und fügen eine interne Funktion append_slice() hinzu (siehe unten). Wir verwenden auch QList, um die interne Liste der Slices als m_slices zu speichern:

class PieChart : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<PieSlice> slices READ slices FINAL)
    ...
public:
    ...
    QQmlListProperty<PieSlice> slices();

private:
    static void append_slice(QQmlListProperty<PieSlice> *list, PieSlice *slice);

    QString m_name;
    QList<PieSlice *> m_slices;
};

Obwohl die Eigenschaft slices keine zugehörige Funktion WRITE hat, ist sie aufgrund der Funktionsweise von QQmlListProperty dennoch änderbar. In der PieChart -Implementierung implementieren wir PieChart::slices(), um einen QQmlListProperty -Wert zurückzugeben und anzugeben, dass die interne PieChart::append_slice() -Funktion immer dann aufgerufen werden soll, wenn eine Anfrage von QML zum Hinzufügen von Elementen zu der Liste gestellt wird:

QQmlListProperty<PieSlice> PieChart::slices()
{
    return QQmlListProperty<PieSlice>(this, nullptr, &PieChart::append_slice, nullptr,
                                      nullptr, nullptr, nullptr, nullptr);
}

void PieChart::append_slice(QQmlListProperty<PieSlice> *list, PieSlice *slice)
{
    PieChart *chart = qobject_cast<PieChart *>(list->object);
    if (chart) {
        slice->setParentItem(chart);
        chart->m_slices.append(slice);
    }
}

Die Funktion append_slice() setzt einfach das übergeordnete Element wie zuvor und fügt das neue Element der Liste m_slices hinzu. Wie Sie sehen, wird die Append-Funktion für QQmlListProperty mit zwei Argumenten aufgerufen: der Listeneigenschaft und dem Element, das angefügt werden soll.

Die Klasse PieSlice wurde außerdem so geändert, dass sie die Eigenschaften fromAngle und angleSpan enthält und das Slice entsprechend diesen Werten zeichnet. Dies ist eine einfache Änderung, wenn Sie die vorherigen Seiten dieses Tutorials gelesen haben.

Kapitel 6: Ein Erweiterungs-Plugin schreiben

extending-qml/chapter6-plugins

Derzeit werden die Typen PieChart und PieSlice von App.qml verwendet, das mit Hilfe von QQuickView in einer C++-Anwendung angezeigt wird. Eine alternative Möglichkeit, unsere QML-Erweiterung zu verwenden, besteht darin, eine Plugin-Bibliothek zu erstellen, um sie der QML-Engine als neues QML-Importmodul zur Verfügung zu stellen. Auf diese Weise können die Typen PieChart und PieSlice in einem Typennamensraum registriert werden, der von jeder QML-Anwendung importiert werden kann, anstatt diese Typen auf die Verwendung durch eine einzige Anwendung zu beschränken.

Die Schritte zur Erstellung eines Plugins sind in Erstellen von C++-Plugins für QML beschrieben. Zunächst erstellen wir eine Plugin-Klasse namens ChartsPlugin. Sie ist eine Unterklasse von QQmlEngineExtensionPlugin und verwendet das Makro Q_PLUGIN_METADATA(), um das Plugin beim Qt-Metaobjektsystem zu registrieren.

Hier ist die ChartsPlugin Definition in chartsplugin.h:

#include <QQmlEngineExtensionPlugin>

class ChartsPlugin : public QQmlEngineExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
};

Dann konfigurieren wir die Build-Datei, um das Projekt als Plugin-Bibliothek zu definieren.

Verwendung von qmake:

TEMPLATE = lib
CONFIG += plugin qmltypes
QT += qml quick

QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

TARGET = $$qtLibraryTarget(chartsplugin)

HEADERS += piechart.h \
           pieslice.h \
           chartsplugin.h

SOURCES += piechart.cpp \
           pieslice.cpp

DESTPATH=$$[QT_INSTALL_EXAMPLES]/qml/tutorials/extending-qml/chapter6-plugins/$$QML_IMPORT_NAME

target.path=$$DESTPATH
qmldir.files=$$PWD/qmldir
qmldir.path=$$DESTPATH
INSTALLS += target qmldir

CONFIG += install_ok  # Do not cargo-cult this!

OTHER_FILES += qmldir

# Copy the qmldir file to the same folder as the plugin binary
cpqmldir.files = qmldir
cpqmldir.path = .
COPIES += cpqmldir

CMake verwenden:

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

qt6_policy(SET QTP0001 NEW)
qt6_add_qml_module(chartsplugin
    URI "Charts"
    PLUGIN_TARGET chartsplugin
    DEPENDENCIES QtQuick
)

target_sources(chartsplugin PRIVATE
    piechart.cpp piechart.h
    pieslice.cpp pieslice.h
)

target_link_libraries(chartsplugin PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Qml
    Qt6::Quick
)

install(TARGETS chartsplugin
    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}/Charts"
    LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}/Charts"
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qmldir
    DESTINATION "${CMAKE_INSTALL_BINDIR}/Charts"
)

Bei der Erstellung dieses Beispiels unter Windows oder Linux wird das Verzeichnis Charts auf der gleichen Ebene wie die Anwendung liegen, die unser neues Importmodul verwendet. Auf diese Weise wird die QML-Engine unser Modul finden, da der Standardsuchpfad für QML-Importe das Verzeichnis der ausführbaren Anwendung enthält. Unter macOS wird die Plugin-Binärdatei nach Contents/PlugIns in das Anwendungsbündel kopiert. Mit qmake wird dieser Pfad in chapter6-plugins/app.pro gesetzt:

macos:!qtConfig(static) {
    charts.files = $$OUT_PWD/Charts
    charts.path = Contents/PlugIns
    QMAKE_BUNDLE_DATA += charts
}

Um dies zu berücksichtigen, müssen wir diesen Ort auch als QML-Importpfad in main.cpp hinzufügen:

    QQuickView view;
#ifdef Q_OS_MACOS
    view.engine()->addImportPath(app.applicationDirPath() + "/../PlugIns");
#endif
    ...

Die Definition von benutzerdefinierten Importpfaden ist auch dann nützlich, wenn mehrere Anwendungen dieselben QML-Importe verwenden.

Die Datei .pro enthält außerdem einen zusätzlichen Trick, um sicherzustellen, dass die Moduldefinitionsdatei qmldir immer an denselben Ort wie die Plugin-Binärdatei kopiert wird.

Die Datei qmldir deklariert den Modulnamen und das Plugin, das durch das Modul verfügbar gemacht wird:

module Charts
optional plugin chartsplugin
typeinfo plugins.qmltypes
depends QtQuick
prefer :/qt/qml/Charts/

Jetzt haben wir ein QML-Modul, das in jede Anwendung importiert werden kann, vorausgesetzt, die QML-Engine weiß, wo es zu finden ist. Das Beispiel enthält eine ausführbare Datei, die App.qml lädt, die die Anweisung import Charts 1.0 verwendet. Alternativ können Sie die QML-Datei auch mit dem qml-Tool laden, indem Sie den Importpfad auf das aktuelle Verzeichnis setzen, damit die Datei qmldir gefunden wird:

qml -I . App.qml

Das Modul "Charts" wird von der QML-Engine geladen, und die von diesem Modul bereitgestellten Typen stehen in jedem QML-Dokument, das es importiert, zur Verfügung.

Kapitel 7: Zusammenfassung

In diesem Lernprogramm haben wir die grundlegenden Schritte zur Erstellung einer QML-Erweiterung gezeigt:

  • Definieren Sie neue QML-Typen, indem Sie QObject subclassing und sie mit QML_ELEMENT oder QML_NAMED_ELEMENT registrieren ()
  • Hinzufügen von aufrufbaren Methoden mit Q_INVOKABLE oder Qt-Slots und Verbindung zu Qt-Signalen mit einer onSignal Syntax
  • Hinzufügen von Eigenschaftsbindungen durch Definition von NOTIFY-Signalen
  • Definieren Sie eigene Eigenschaftstypen, wenn die eingebauten Typen nicht ausreichen
  • Definieren Sie Listeneigentumstypen mit QQmlListProperty
  • Erstellen Sie eine Plugin-Bibliothek, indem Sie ein Qt-Plugin definieren und eine qmldir-Datei schreiben.

Die Übersichtsdokumentation zur QML- und C++-Integration zeigt weitere nützliche Funktionen, die zu QML-Erweiterungen hinzugefügt werden können. Zum Beispiel könnten wir Standardeigenschaften verwenden, um das Hinzufügen von Slices zu ermöglichen, ohne die Eigenschaft slices zu verwenden:

PieChart {
    PieSlice { ... }
    PieSlice { ... }
    PieSlice { ... }
}

Oder wir können Slices von Zeit zu Zeit zufällig hinzufügen und entfernen, indem wir Quellen für Eigenschaftswerte verwenden:

PieChart {
    PieSliceRandomizer on slices {}
}

Hinweis: Um mehr über QML-Erweiterungen und -Funktionen zu erfahren, folgen Sie dem Tutorial Writing advanced QML Extensions with C++.

© 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.