Sur cette page

Écrire des extensions QML avec C++

Le module Qt Qml fournit un ensemble d'API permettant d'étendre QML par le biais d'extensions C++. Vous pouvez écrire des extensions pour ajouter vos propres types QML, étendre les types Qt existants ou appeler des fonctions C/C++ qui ne sont pas accessibles à partir du code QML ordinaire.

Ce tutoriel montre comment écrire une extension QML à l'aide de C++ qui inclut les caractéristiques principales de QML, y compris les propriétés, les signaux et les liaisons. Il montre également comment les extensions peuvent être déployées par le biais de plugins.

La plupart des sujets abordés dans ce tutoriel sont documentés plus en détail dans Vue d'ensemble - Intégration QML et C++ et dans les sous-sujets de la documentation. En particulier, vous pouvez être intéressé par les sous-thèmes Exposer les attributs des classes C++ à QML et Définir les types QML à partir de C++.

Ouvrir les sources du tutoriel

Le code de ce tutoriel est disponible dans les sources de Qt. Si vous avez installé Qt avec Qt Online Installer, vous trouverez les sources dans le répertoire d'installation de Qt sous Examples/Qt-6.11.0/qml/tutorials/extending-qml/.

Création d'un projet à partir de zéro

Vous pouvez également suivre le tutoriel en créant les sources à partir de zéro : Pour chaque chapitre, créez un nouveau projet en utilisant le modèle d'applicationQt Quick dans Qt Creator, comme indiqué dans Qt Creator: Créer des applications Qt Quick . Ensuite, suivez le tutoriel en adaptant et en étendant le code squelette généré.

Chapitre 1 : Création d'un nouveau type

extending-qml/chapter1-basics

Une tâche courante lors de l'extension de QML est de fournir un nouveau type QML qui prend en charge une fonctionnalité personnalisée au-delà de ce qui est fourni par le type intégré Qt Quick types. Par exemple, cela peut être fait pour mettre en œuvre des modèles de données particuliers, ou pour fournir des types avec des capacités de peinture et de dessin personnalisées, ou encore pour accéder à des fonctionnalités système telles que la programmation réseau qui ne sont pas accessibles par le biais des fonctionnalités QML intégrées.

Dans ce tutoriel, nous montrerons comment utiliser les classes C++ du module Qt Quick pour étendre QML. Le résultat final sera un simple graphique en camembert implémenté par plusieurs types QML personnalisés reliés entre eux par des fonctionnalités QML telles que les bindings et les signaux, et mis à la disposition du runtime QML par l'intermédiaire d'un plugin.

Pour commencer, créons un nouveau type QML appelé "PieChart" qui possède deux propriétés : un nom et une couleur. Nous le rendrons disponible dans un espace de noms de types importables appelé "Charts", avec une version 1.0.

Nous voulons que ce type PieChart soit utilisable à partir de QML comme ceci :

import Charts

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

Pour ce faire, nous avons besoin d'une classe C++ qui encapsule ce type PieChart et ses propriétés. Puisque Qt Qml utilise largement le système de méta-objets de Qt, cette nouvelle classe doit :

Déclaration de la classe

Voici notre classe PieChart, définie dans 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;
};

La classe hérite de QQuickPaintedItem parce que nous voulons surcharger QQuickPaintedItem::paint() pour effectuer des opérations de dessin avec l'API QPainter. Si la classe représentait simplement un type de données et n'était pas un élément devant être affiché, elle pourrait simplement hériter de QObject. Ou, si nous voulons étendre la fonctionnalité d'une classe existante basée sur QObject, elle pourrait hériter de cette classe à la place. Par ailleurs, si nous voulons créer un élément visuel qui n'a pas besoin d'effectuer des opérations de dessin avec l'API QPainter, nous pouvons simplement sous-classer QQuickItem.

La classe PieChart définit les deux propriétés, name et color, à l'aide de la macro Q_PROPERTY, et surcharge QQuickPaintedItem::paint(). La classe PieChart est enregistrée à l'aide de la macro QML_ELEMENT, afin de pouvoir être utilisée à partir de QML. Si vous n'enregistrez pas la classe, App.qml ne pourra pas créer de PieChart.

qmake Configuration

Pour que l'enregistrement prenne effet, l'option qmltypes est ajoutée à CONFIG dans le fichier de projet et les options QML_IMPORT_NAME et QML_IMPORT_MAJOR_VERSION sont données :

CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

De plus, un fichier qmldir doit être ajouté manuellement pour créer un module QML.

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

Configuration de CMake

Pour que l'enregistrement prenne effet lors de l'utilisation de CMake, utilisez la commande qt_add_qml_module:

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

L'API qt_add_qml_module génère automatiquement un fichier qmldir pour le module QML.

Mise en œuvre de la classe

L'implémentation de la classe dans piechart.cpp définit et renvoie simplement les valeurs m_name et m_color comme il convient, et implémente paint() pour dessiner un simple diagramme circulaire :

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);
}

Utilisation de QML

Maintenant que nous avons défini le type PieChart, nous allons l'utiliser à partir de QML. Le fichier App.qml crée un élément PieChart et affiche les détails du camembert à l'aide d'un élément QML Text standard :

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
    }
}

Remarquez que, bien que la couleur soit spécifiée sous forme de chaîne en QML, elle est automatiquement convertie en objet QColor pour la propriété PieChart color. Des conversions automatiques sont prévues pour divers autres types de valeurs. Par exemple, une chaîne comme "640x480" peut être automatiquement convertie en une valeur QSize.

Nous allons également créer une application C++ qui utilise QQuickView pour exécuter et afficher App.qml.

Voici l'application 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();
}

Construction du projet

Pour construire le projet, nous incluons les fichiers, nous établissons un lien avec les bibliothèques et nous définissons un espace de noms de types appelé "Charts" avec la version 1.0 pour tous les types exposés à QML.

Utilisation de 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

Utilisation de 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})

Nous pouvons maintenant construire et exécuter l'application :

L'application crée le diagramme à secteurs avec les propriétés définies par le type de diagramme à secteurs.

Note : You may see a warning Expression ... depends on non-bindable properties : PieChart::name. Cela se produit parce que nous ajoutons une liaison à la propriété inscriptible name, mais que nous n'avons pas encore défini de signal de notification pour cette propriété. Le moteur QML ne peut donc pas mettre à jour la liaison si la valeur de name change. Ce problème est abordé dans les chapitres suivants.

Chapitre 2 : Connexion aux méthodes et signaux C++

extending-qml/chapter2-methods

Supposons que nous souhaitions que PieChart dispose d'une méthode "clearChart()" qui efface le graphique et émette ensuite un signal "chartCleared". Notre App.qml serait capable d'appeler clearChart() et de recevoir des signaux chartCleared() de cette manière :

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"
    }
}

L'utilisateur peut cliquer n'importe où dans la fenêtre de l'application pour effacer le graphique. Cela invoque la méthode Clear Chart (Effacer le graphique)

Pour ce faire, nous ajoutons une méthode clearChart() et un signal chartCleared() à notre classe C++ :

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

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

L'utilisation de Q_INVOKABLE rend la méthode clearChart() disponible au système Qt Meta-Object, et à son tour, à QML.

Remarque : vous pouvez également déclarer la méthode en tant que slot Qt au lieu d'utiliser Q_INVOKABLE, car les slots publics et protégés peuvent également être appelés à partir de Qtml (vous ne pouvez pas appeler les slots privés).

La méthode clearChart() change la couleur en Qt::transparent, repeint le graphique, puis émet le signal chartCleared():

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

    emit chartCleared();
}

Désormais, lorsque nous exécutons l'application et que nous cliquons sur la fenêtre, le graphique disparaît et l'application s'éteint :

qml: The chart has been cleared

Chapitre 3 : Ajout de liaisons de propriétés

extending-qml/chapter3-bindings

La liaison de propriétés est une fonctionnalité puissante de QML qui permet de synchroniser automatiquement des valeurs de différents types. Elle utilise des signaux pour notifier et mettre à jour les valeurs d'autres types lorsque les valeurs des propriétés sont modifiées.

Activons les liaisons de propriétés pour la propriété color. Cela signifie que si nous avons un code comme celui-ci :

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"
    }
}

La propriété de couleur du camembert B est liée à la propriété de couleur du camembert A.

L'instruction "color : chartA.color" lie la valeur color de chartB à la valeur color de chartA. Lorsque la valeur color de chartA change, la valeur color de chartB est mise à jour avec la même valeur. Lorsque l'on clique sur la fenêtre, le gestionnaire onClicked de MouseArea modifie la couleur de chartA, ce qui a pour effet de donner la couleur bleue aux deux graphiques.

Il est facile d'activer la liaison de propriété pour la propriété color. Nous ajoutons une fonction NOTIFY à sa déclaration Q_PROPERTY() pour indiquer qu'un signal "colorChanged" est émis chaque fois que la valeur change.

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

Nous émettons ensuite ce signal dans setColor():

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

Il est important que setColor() vérifie que la valeur de la couleur a effectivement changé avant d'émettre colorChanged(). Cela permet de s'assurer que le signal n'est pas émis inutilement et d'éviter les boucles lorsque d'autres types réagissent au changement de valeur.

L'utilisation de bindings est essentielle pour QML. Vous devez toujours ajouter des signaux NOTIFY pour les propriétés s'ils peuvent être mis en œuvre, afin que vos propriétés puissent être utilisées dans les liaisons. Les propriétés qui ne peuvent pas être liées ne peuvent pas être mises à jour automatiquement et ne peuvent pas être utilisées de manière aussi flexible en QML. De plus, comme les liaisons sont invoquées très souvent et qu'on s'y fie dans l'utilisation de QML, les utilisateurs de vos types QML personnalisés peuvent observer un comportement inattendu si les liaisons ne sont pas mises en œuvre.

Chapitre 4 : Utilisation de types de propriétés personnalisés

extending-qml/chapter4-customPropertyTypes

Le type PieChart possède actuellement une propriété de type chaîne et une propriété de type couleur. Il pourrait avoir de nombreux autres types de propriétés. Par exemple, il pourrait avoir une propriété de type int pour stocker un identifiant pour chaque graphique :

// 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
}

Outre int, nous pourrions utiliser d'autres types de propriétés. De nombreux types de données Qt, tels que QColor, QSize et QRect, sont automatiquement pris en charge par QML. (Voir la documentation sur la conversion des types de données entre QML et C++ pour une liste complète).

Si nous voulons créer une propriété dont le type n'est pas pris en charge par défaut par QML, nous devons enregistrer le type auprès du moteur QML.

Par exemple, remplaçons l'utilisation de property par un type appelé "PieSlice" qui possède une propriété color. Au lieu d'assigner une couleur, nous assignons une valeur PieSlice qui contient elle-même une propriété color:

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)
}

Comme PieChart, ce nouveau type PieSlice hérite de QQuickPaintedItem et déclare ses propriétés avec 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;
};

Pour l'utiliser dans PieChart, nous modifions la déclaration de propriété color et les signatures de méthodes associées :

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

Il y a une chose à laquelle il faut faire attention lors de l'implémentation de setPieSlice(). Le type PieSlice est un élément visuel, il doit donc être défini comme un enfant du type PieChart à l'aide de QQuickItem::setParentItem() afin que le type PieChart sache qu'il doit peindre cet élément enfant lorsque son contenu est dessiné :

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

Comme le type PieChart, le type PieSlice doit être exposé à QML à l'aide de QML_ELEMENT.

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;
};
    ...

Comme pour PieChart, nous ajoutons l'espace de noms du type "Charts", version 1.0, à notre fichier de construction :

Utilisation de 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

Utilisation de 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
)
    ...

Chapitre 5 : Utilisation des types de propriété de liste

extending-qml/chapter5-listproperties

Pour l'instant, un site PieChart ne peut avoir qu'un seul site PieSlice. Idéalement, un graphique devrait avoir plusieurs tranches, avec des couleurs et des tailles différentes. Pour ce faire, nous pourrions avoir une propriété slices qui accepte une liste d'éléments PieSlice:

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
            }
        ]
    }
}

La propriété slices accepte une liste d'éléments de tranches de tarte. L'élément pieslice définit l'angle et la couleur de chaque part de tarte.

Pour ce faire, nous remplaçons la propriété pieSlice dans PieChart par une propriété slices, déclarée comme un type QQmlListProperty. La classe QQmlListProperty permet de créer des propriétés de liste dans les types exposés à QML. Nous remplaçons la fonction pieSlice() par une fonction slices() qui renvoie une liste de tranches. Nous utilisons également un QList pour stocker la liste interne des tranches en tant que m_slices:

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;
};

Bien que la propriété slices n'ait pas de fonction WRITE associée, elle est toujours modifiable en raison de la façon dont QQmlListProperty fonctionne. Dans l'implémentation de PieChart, nous implémentons PieChart::slices() pour qu'il renvoie une valeur QQmlListProperty:

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

Cela permet de synthétiser les fonctions nécessaires pour interagir avec la liste à partir de QML. Le résultat QQmlListProperty est une vue de la liste. Vous pouvez également fournir manuellement les différentes fonctions d'accès à la liste. Cela est nécessaire si votre liste n'est pas une QList ou si vous souhaitez restreindre ou personnaliser l'accès QML à votre liste. Dans la plupart des cas, cependant, le constructeur qui prend un pointeur QList est l'option la plus sûre et la plus facile.

La classe PieSlice a également été modifiée pour inclure les propriétés fromAngle et angleSpan et pour dessiner la tranche en fonction de ces valeurs. Il s'agit d'une modification simple si vous avez lu les pages précédentes de ce tutoriel, c'est pourquoi le code n'est pas montré ici.

Chapitre 6 : Écrire un plugin d'extension

extending-qml/chapter6-plugins

Actuellement, les types PieChart et PieSlice sont utilisés par App.qml, qui est affiché à l'aide de QQuickView dans une application C++. Une autre façon d'utiliser notre extension QML consiste à créer une bibliothèque d'extension pour la mettre à la disposition du moteur QML en tant que nouveau module d'importation QML. Cela permet aux types PieChart et PieSlice d'être enregistrés dans un espace de noms de types qui peut être importé par n'importe quelle application QML, au lieu de restreindre l'utilisation de ces types à une seule application.

Les étapes de création d'un plugin sont décrites dans la section Création de plugins C++ pour QML. Pour commencer, nous créons une classe de plugin nommée ChartsPlugin. Elle sous-classe QQmlEngineExtensionPlugin et utilise la macro Q_PLUGIN_METADATA() pour enregistrer le plugin dans le système de métaobjets de Qt XML.

Voici la définition de ChartsPlugin dans chartsplugin.h:

#include <QQmlEngineExtensionPlugin>

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

Ensuite, nous configurons le fichier de construction pour définir le projet comme une bibliothèque de plugins.

Utilisation de 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

Utilisation de 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"
)

Lors de la construction de cet exemple sous Windows ou Linux, le répertoire Charts sera situé au même niveau que l'application qui utilise notre nouveau module d'importation. De cette manière, le moteur QML trouvera notre module car le chemin de recherche par défaut pour les importations QML inclut le répertoire de l'exécutable de l'application. Sur macOS, le binaire du plugin est copié sur Contents/PlugIns dans le bundle de l'application. Avec qmake, ce chemin est défini dans chapter6-plugins/app.pro:

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

Pour en tenir compte, nous devons également ajouter cet emplacement en tant que chemin d'importation QML dans main.cpp:

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

La définition de chemins d'importation personnalisés est également utile lorsque plusieurs applications utilisent les mêmes importations QML.

Le fichier .pro contient également une magie supplémentaire pour s'assurer que le fichier qmldir de définition du module est toujours copié au même endroit que le binaire du plugin.

Le fichier qmldir déclare le nom du module et le plugin mis à disposition par le module :

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

Nous disposons à présent d'un module QML qui peut être importé dans n'importe quelle application, à condition que le moteur QML sache où le trouver. L'exemple contient un exécutable qui charge App.qml, qui utilise l'instruction import Charts 1.0. Vous pouvez également charger le fichier QML à l'aide de l'outil qml, en définissant le chemin d'importation dans le répertoire actuel de manière à ce qu'il trouve le fichier qmldir:

qml -I . App.qml

Le module "Charts" sera chargé par le moteur QML et les types fournis par ce module pourront être utilisés dans tout document QML qui l'importe.

Chapitre 7 : Résumé

Dans ce tutoriel, nous avons montré les étapes de base de la création d'une extension QML :

  • Définir de nouveaux types QML en sous-classant QObject et en les enregistrant avec QML_ELEMENT ou QML_NAMED_ELEMENT()
  • Ajouter des méthodes appelables en utilisant Q_INVOKABLE ou des slots Qt, et se connecter aux signaux Qt avec une syntaxe onSignal
  • Ajouter des liaisons de propriété en définissant des signaux NOTIFY
  • Définir des types de propriétés personnalisés si les types intégrés ne sont pas suffisants
  • Définir des types de propriétés de liste en utilisant QQmlListProperty
  • Créer une bibliothèque de plugins en définissant un plugin Qt et en écrivant un fichier qmldir.

La documentation générale sur l'intégration de QML et de C++ présente d'autres fonctionnalités utiles qui peuvent être ajoutées aux extensions QML. Par exemple, nous pourrions utiliser des propriétés par défaut pour permettre l'ajout de tranches sans utiliser la propriété slices:

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

Ou ajouter et supprimer des tranches de manière aléatoire en utilisant des sources de valeurs de propriétés:

PieChart {
    PieSliceRandomizer on slices {}
}

Note : Pour poursuivre l'apprentissage des extensions et des fonctionnalités QML, suivez le tutoriel Writing advanced QML Extensions with C++ (Écrire des extensions QML avancées avec 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.