Auf dieser Seite

QML-Erweiterungen mit C++ schreiben

Das Qt Qml Modul bietet eine Reihe von APIs zur Erweiterung von QML durch C++-Erweiterungen. Sie können Erweiterungen schreiben, um eigene QML-Typen hinzuzufügen, bestehende Qt-Typen zu erweitern oder C/C++-Funktionen aufzurufen, auf die aus normalem QML-Code nicht zugegriffen werden kann.

Dieses Tutorial zeigt, wie man eine QML-Erweiterung mit C++ schreibt, die zentrale QML-Funktionen wie Eigenschaften, Signale und Bindungen umfasst. Außerdem wird erläutert, wie Erweiterungen über Plugins bereitgestellt werden können.

Viele der in diesem Tutorial behandelten Themen sind ausführlicher in „Übersicht – QML- und C++-Integration“ und den dortigen Unterthemen dokumentiert. Insbesondere könnten die Unterthemen „Attribute von C++-Klassen für QML verfügbar machen“ und „QML-Typen aus C++ definieren“ für Sie von Interesse sein.

Öffnen der Tutorial-Quellcodes

Der Code dieses Tutorials ist als Teil der Qt-Quellcodes verfügbar. Wenn Sie Qt mit dem „ Qt Online Installer “ installiert haben, finden Sie die Quellcodes im Qt-Installationsverzeichnis unter „Examples/Qt-6.12.0/qml/tutorials/extending-qml/“.

Projekt von Grund auf erstellen

Alternativ können Sie dem Tutorial folgen, indem Sie die Quelldateien von Grund auf neu erstellen: Erstellen Sie für jedes Kapitel ein neues Projekt mithilfe der Anwendungsvorlage „Qt Quick “:

Folgen Sie dann den Anweisungen, indem Sie den generierten Skelettcode anpassen und erweitern.

Kapitel 1: Einen neuen Typ erstellen

extending-qml/chapter1-basics

Eine häufige Aufgabe bei der Erweiterung von QML ist die Bereitstellung eines neuen QML-Typs, der über die Funktionen der integrierten Elemente hinausgehende benutzerdefinierte Funktionen unterstützt Qt Quick types. Dies könnte beispielsweise dazu dienen, bestimmte Datenmodelle zu implementieren, Typen mit benutzerdefinierten Mal- und Zeichenfunktionen bereitzustellen oder auf Systemfunktionen wie Netzwerkprogrammierung zuzugreifen, die über die integrierten QML-Funktionen nicht zugänglich sind.

In diesem Tutorial zeigen wir, wie man die C++-Klassen im Modul „ Qt Quick “ nutzt, um QML zu erweitern. Das Endergebnis wird eine einfache Kreisdiagramm-Darstellung sein, die durch mehrere benutzerdefinierte QML-Typen implementiert wird, die über 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 besitzt: einen Namen und eine Farbe. Wir stellen ihn in einem importierbaren Typ-Namespace namens „Charts“ mit der Version 1.0 zur Verfügung.

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

import Charts

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

Hinweis: Möglicherweise müssenSie „ QML Language Server “ und dessen semantische Hervorhebung (unter Qt Creator oder unter Qt Extension for Visual Studio Code) aktivieren, damit „ Qt Creator “ PieChart korrekt hervorhebt.

Dazu benötigen wir eine C++-Klasse, die diesen PieChart -Typ und seine Eigenschaften kapseln. Da QML das Metaobjektsystem von Qt intensiv 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 “, da wir die Methode „ QQuickPaintedItem::paint()“ überschreiben möchten, um Zeichenoperationen mit der „ QPainter “-API durchzuführen. Würde die Klasse lediglich einen Datentyp darstellen und kein Element sein, das tatsächlich angezeigt werden muss, könnte sie einfach von „ QObject “ erben. Oder wenn wir die Funktionalität einer bestehenden, auf „ QObject “ basierenden Klasse erweitern möchten, könnte sie stattdessen von dieser Klasse erben. Alternativ können wir, wenn wir ein visuelles Element erstellen möchten, das keine Zeichenoperationen mit der „ QPainter “-API ausführen muss, einfach eine Unterklasse von „ QQuickItem “ erstellen.

Die Klasse „ PieChart “ definiert die beiden Eigenschaften „ name “ und „ color “ mit dem Makro „ Q_PROPERTY “ und überschreibt „ QQuickPaintedItem::paint()“. Die Klasse „ PieChart “ wird mit dem Makro „ QML_ELEMENT “ registriert, damit sie von QML aus verwendet werden kann. Wenn Sie die Klasse nicht registrieren, kann „ App.qml “ kein „ PieChart “ erstellen.

qmake-Einrichtung

Damit die Registrierung wirksam wird, wird die Option „ qmltypes “ zur Datei „ CONFIG “ in der Projektdatei hinzugefügt und es werden die Einträge „ QML_IMPORT_NAME “ sowie „ QML_IMPORT_MAJOR_VERSION “ angegeben:

CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

Zudem muss manuell eine qmldir-Datei hinzugefügt werden, um ein QML-Modul zu erstellen.

module Charts
typeinfo chapter1-basics.qmltypes
depends QtQuick
prefer :/qt/qml/Charts/
App 254.0 App.qml

CMake-Einrichtung

Damit die Registrierung bei 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
)

Die API ` qt_add_qml_module() ` generiert automatisch eine Datei „ qmldir “ für das QML-Modul.

Klassenimplementierung

Die Klassenimplementierung in „ piechart.cpp “ legt lediglich die Werte für „ m_name “ und „ m_color “ entsprechend fest und gibt sie zurück sowie implementiert „ paint() “, um ein einfaches Kreisdiagramm 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);
}

Verwendung in QML

Nachdem wir nun den Typ „ PieChart “ definiert haben, werden wir ihn in QML verwenden. Die Datei „ App.qml “ erstellt ein „ PieChart “-Element und zeigt die Details des Kreisdiagramms mithilfe eines standardmäßigen QML-Elements „ 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 in QML als Zeichenkette angegeben wird, für die „ color “-Eigenschaft des PieChart jedoch automatisch in ein „ QColor “-Objekt umgewandelt wird. Automatische Konvertierungen werden für verschiedene andere Werttypen bereitgestellt. So kann beispielsweise eine Zeichenkette wie „640x480“ automatisch in einen „ QSize “-Wert umgewandelt werden.

Außerdem erstellen wir eine C++-Anwendung, die ein ` QQuickView ` verwendet, um ein ` 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, verknüpfen sie mit den Bibliotheken und definieren einen Typ-Namespace namens „Charts“ mit der Version 1.0 für alle Typen, die für QML verfügbar 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

Mit 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
    NO_UNSUPPORTED_PLATFORM_ERROR
    DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})

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

Die Anwendung erstellt das Kreisdiagramm mit den Eigenschaften, die durch den Kreisdiagrammtyp definiert sind.

Hinweis: Möglicherweise wird die Warnung „Ausdruck … hängt von nicht bindbaren Eigenschaften ab: PieChart::name“angezeigt . Dies liegt daran, dass wir eine Bindung zur beschreibbaren Eigenschaft „ name “ hinzufügen, aber noch kein „notify“-Signal dafür definiert haben. Die QML-Engine kann die Bindung daher nicht aktualisieren, wenn sich der Wert von „ name “ ändert. Dies wird in den folgenden Kapiteln behandelt.

Kapitel 2: Anbindung an C++-Methoden und -Signale

extending-qml/chapter2-methods

Angenommen, wir möchten, dass „ PieChart “ über eine Methode „clearChart()“ verfügt, die das Diagramm löscht und anschließend ein Signal „chartCleared“ aussendet. Unser „ App.qml “ könnte dann „ clearChart() “ aufrufen und Signale wie „ chartCleared() “ wie folgt 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"
    }
}

Der Benutzer kann an einer beliebigen Stelle im Anwendungsfenster klicken, um das Diagramm zu löschen. Dadurch wird die Methode „Clear Chart“ aufgerufen.

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

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

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

Durch die Verwendung von ` Q_INVOKABLE ` wird die Methode ` clearChart() ` für das Qt-Metaobjekt-System und damit auch für QML verfügbar gemacht.

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

Die Methode ` clearChart() ` ändert die Farbe in ` Qt::transparent`, zeichnet das Diagramm neu und sendet anschließend 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, verschwindet das Kreisdiagramm, und die Anwendung gibt Folgendes aus:

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 unterschiedlicher Typen automatisch synchronisiert werden können. Sie nutzt Signale, um andere Typen zu benachrichtigen und deren Werte zu aktualisieren, wenn sich Eigenschaftswerte ändern.

Aktivieren wir die Eigenschaftsbindungen für die Eigenschaft „ color “. Das bedeutet, wenn wir folgenden Code 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 Farbeigenschaft von Kreisdiagramm B ist an die Farbeigenschaft von Kreisdiagramm A gebunden.

Die Anweisung „color: chartA.color“ bindet den Wert „ color “ von „ chartB “ an den Wert „ color “ von „ chartA “. Immer wenn sich der Wert von „ color “ bei „ chartA “ ändert, wird der Wert von „ color “ bei „ chartB “ auf denselben Wert aktualisiert. Wenn auf das Fenster geklickt wird, ändert der Handler „ onClicked “ in der Datei „ MouseArea “ die Farbe von „ chartA “, wodurch beide Diagramme die Farbe Blau annehmen.

Es ist einfach, die Eigenschaftsbindung für die Eigenschaft „ color “ zu aktivieren. Wir fügen der Deklaration „ Q_PROPERTY()“ eine NOTIFY-Funktion hinzu, um anzugeben, dass bei jeder Wertänderung ein „colorChanged“-Signal ausgesendet wird.

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

Anschließend senden wir dieses Signal in ` setColor()` aus:

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() “ vor dem Auslösen von „ colorChanged() “ überprüft, ob sich der Farbwert tatsächlich geändert hat. Dadurch wird sichergestellt, dass das Signal nicht unnötig ausgelöst wird, und es werden Endlosschleifen verhindert, wenn andere Typen auf die Wertänderung reagieren.

Die Verwendung von Bindungen ist für QML unerlässlich. Sie sollten für Eigenschaften immer NOTIFY-Signale hinzufügen, sofern diese implementiert werden können, damit 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 genutzt werden. Da Bindungen in QML zudem sehr häufig aufgerufen werden und eine wichtige Rolle spielen, kann es bei Benutzern Ihrer benutzerdefinierten QML-Typen zu unerwartetem Verhalten kommen, wenn keine Bindungen implementiert sind.

Kapitel 4: Verwendung benutzerdefinierter Eigenschaftstypen

extending-qml/chapter4-customPropertyTypes

Der Typ „ PieChart “ verfügt derzeit über eine Eigenschaft vom Typ „string“ und eine Eigenschaft vom Typ „color“. Er könnte viele weitere Arten von Eigenschaften aufweisen. Beispielsweise könnte er eine Eigenschaft vom Typ „int“ besitzen, um eine Kennung 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
}

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

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

Ersetzen wir beispielsweise die Verwendung von „ property “ durch einen Typ namens „PieSlice“, der über eine Eigenschaft „ color “ verfügt. Anstatt eine Farbe zuzuweisen, weisen wir einen Wert vom Typ „ PieSlice “ zu, der wiederum 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 ihn in „ PieChart “ zu verwenden, ändern wir die Eigenschaftsdeklaration von „ 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() “ ist eine Sache zu beachten. „ PieSlice “ ist ein visuelles Element und muss daher mithilfe von „ QQuickItem::setParentItem()“ als untergeordnetes Element von „ PieChart “ festgelegt werden, damit „ PieChart “ weiß, dass dieses untergeordnete Element beim Zeichnen seines Inhalts gerendert werden muss:

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

Wie der Typ „ PieChart “ muss auch der Typ „ PieSlice “ mithilfe von „ QML_ELEMENT “ für 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-Namespace „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

Mit CMake:

    ...
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: Verwendung von Listen-Eigenschaftstypen

extending-qml/chapter5-listproperties

Derzeit kann ein ` PieChart ` nur ein einziges ` PieSlice` enthalten. Im Idealfall sollte ein Diagramm jedoch mehrere Segmente mit unterschiedlichen Farben und Größen aufweisen. Dazu könnten wir eine ` slices `-Eigenschaft definieren, die eine Liste von ` PieSlice `-Elementen akzeptiert:

pragma ComponentBehavior: Bound
import Charts
import QtQuick

Item {
    width: 300; height: 200

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

        component Slice: PieSlice {
            parent: chart
            anchors.fill: parent
        }

        slices: [
            Slice {
                color: "red"
                fromAngle: 0
                angleSpan: 110
            },
            Slice {
                color: "black"
                fromAngle: 110
                angleSpan: 50
            },
            Slice {
                color: "blue"
                fromAngle: 160
                angleSpan: 100
            }
        ]
    }
}

Die Eigenschaft „slices“ akzeptiert eine Liste von „pieslice“-Elementen. Das „pieslice“-Element legt den Winkel und die Farbe jedes Tortenstücks fest.

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 Listen-Eigenschaften in Typen, die für QML verfügbar sind. Wir ersetzen die Funktion „ pieSlice() “ durch eine Funktion „ slices() “, die eine Liste von Slices zurückgibt. Außerdem verwenden wir ein „ 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:
    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 veränderbar. In der Implementierung von „ PieChart “ implementieren wir „ PieChart::slices() “, um einen Wert für „ QQmlListProperty “ zurückzugeben:

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

Dadurch werden die erforderlichen Funktionen für die Interaktion mit der Liste aus QML heraus synthetisiert. Das resultierende ` QQmlListProperty ` ist eine Ansicht der Liste. Alternativ können Sie die einzelnen Zugriffsfunktionen für die Liste manuell bereitstellen. Dies ist erforderlich, wenn Ihre Liste kein ` QList ` ist oder wenn Sie den QML-Zugriff auf Ihre Liste einschränken oder anderweitig anpassen möchten. In den meisten Fällen ist jedoch der Konstruktor, der einen „ QList “-Zeiger entgegennimmt, die sicherste und einfachste Option.

Die Klasse „ PieSlice “ wurde ebenfalls angepasst, um die Eigenschaften „ fromAngle “ und „ angleSpan “ aufzunehmen und den Ausschnitt entsprechend diesen Werten zu zeichnen. Dies ist eine einfache Anpassung, wenn Sie die vorherigen Seiten dieses Tutorials gelesen haben; daher wird der Code hier nicht gezeigt.

Kapitel 6: Ein Erweiterungs-Plugin schreiben

extending-qml/chapter6-plugins

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

Die Schritte zum Erstellen eines Plugins werden unter „Erstellen von C++-Plugins für QML“ beschrieben. Zunächst erstellen wir eine Plugin-Klasse namens „ ChartsPlugin “. Diese ist eine Unterklasse von „ QQmlEngineExtensionPlugin “ und verwendet das Makro „ Q_PLUGIN_METADATA()“, um das Plugin beim Qt-Metaobjektsystem zu registrieren.

Hier ist die Definition von „ ChartsPlugin “ in „ chartsplugin.h “:

#include <QQmlEngineExtensionPlugin>

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

Anschließend konfigurieren wir die Build-Datei so, dass das Projekt als Plugin-Bibliothek definiert wird.

Mit 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

Mit CMake:

# 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"
)

Beim Erstellen dieses Beispiels unter Windows oder Linux befindet sich das Verzeichnis „ Charts “ auf derselben Ebene wie die Anwendung, die unser neues Importmodul verwendet. Auf diese Weise findet die QML-Engine unser Modul, da der Standard-Suchpfad für QML-Importe das Verzeichnis der ausführbaren Anwendungsdatei enthält. Unter macOS wird die Plugin-Binärdatei in das Verzeichnis „ Contents/PlugIns “ im Anwendungsbundle kopiert. Bei qmake wird dieser Pfad in „ chapter6-plugins/app.pro “ festgelegt:

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 Speicherort außerdem als QML-Importpfad in „ main.cpp “ hinzufügen:

    QQuickView view;
    ...

Das Definieren benutzerdefinierter Importpfade ist auch dann nützlich, wenn mehrere Anwendungen dieselben QML-Importe verwenden.

Die Datei „ .pro “ enthält zudem eine zusätzliche Funktion, die sicherstellt, dass die qmldir-Datei mit der Moduldefinition immer an denselben Speicherort wie die Plugin-Binärdatei kopiert wird.

Die Datei „ qmldir “ deklariert den Modulnamen und das Plugin, das durch das Modul bereitgestellt wird:

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

Nun verfügen wir über ein QML-Modul, das in jede beliebige Anwendung importiert werden kann, vorausgesetzt, die QML-Engine weiß, wo sie es finden kann. Das Beispiel enthält eine ausführbare Datei, die „ App.qml “ lädt, wobei die Anweisung „ import Charts 1.0 “ verwendet wird. Alternativ können Sie die QML-Datei 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 zur Verfügung, das es importiert.

Kapitel 7: Zusammenfassung

In diesem Tutorial haben wir die grundlegenden Schritte zum Erstellen einer QML-Erweiterung gezeigt:

  • Definieren Sie neue QML-Typen, indem Sie „ QObject “ unterklassen und diese mit „ QML_ELEMENT “ oder „ QML_NAMED_ELEMENT()“ registrieren.
  • Fügen Sie aufrufbare Methoden mithilfe von ` Q_INVOKABLE ` oder Qt-Slots hinzu und verbinden Sie diese mit Qt-Signalen unter Verwendung der ` onSignal `-Syntax
  • Fügen Sie Eigenschaftsbindungen hinzu, indem Sie NOTIFY -Signale definieren
  • Definieren Sie benutzerdefinierte Eigenschaftstypen, falls die integrierten Typen nicht ausreichen
  • Definieren Sie Listen-Eigenschaftstypen mithilfe von QQmlListProperty
  • Erstellen Sie eine Plugin-Bibliothek, indem Sie ein Qt-Plugin definieren und eine ` qmldir` -Datei schreiben

Die Dokumentation zur QML- und C++-Integration bietet einen Überblick über weitere nützliche Funktionen, die zu QML-Erweiterungen hinzugefügt werden können. Beispielsweise könnten wir Standard-Eigenschaften verwenden, um das Hinzufügen von Slices ohne die Eigenschaft „ slices “ zu ermöglichen:

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

Oder von Zeit zu Zeit mithilfe von Eigenschaftswertquellen zufällig „Slices“ hinzufügen und entfernen:

PieChart {
    PieSliceRandomizer on slices {}
}

Hinweis: Um mehr über QML-Erweiterungen und deren Funktionen zu erfahren, folgen Sie dem Tutorial „Erstellen fortgeschrittener QML-Erweiterungen mit C++ “.

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