使用 C++ 编写 QML 扩展

关于使用 Qt C++ 扩展 QML 的教程。

Qt QML 模块提供了一套 API,用于通过 C++ 扩展 QML。你可以编写扩展来添加你自己的 QML 类型,扩展现有的 Qt 类型,或调用普通 QML 代码无法访问的 C/C++ 函数。

本教程展示了如何使用 C++ 编写 QML 扩展,其中包括 QML 核心功能,包括属性、信号和绑定。它还展示了如何通过插件来部署扩展。

本教程中所涉及的许多主题在 概述——QML 和 C++ 集成 及其文档子主题中有更详细的说明。特别是,您可能对 向 QML 公开 C++ 类型的属性在 C++ 中定义 QML 类型 这两个子主题感兴趣。

运行教程示例

本教程中的代码都可通过 Examples 获取,包含每个章节相关的子项目。在 Qt Creator 中,打开 欢迎 模式并从 示例 中选择 extending-qml

创建教程项目

我们按照 Qt Creator: 创建 Qt Quick 项目 中的说明,使用 Qt Creator 中的 Qt Quick Application 模板创建一个新项目。

第 1 章:创建新类型

在扩展 QML 时,一个常见的任务是提供一个新的 QML 类型,支持一些超出内置 Qt Quick 类型所提供的自定义功能。例如,这可能是为了实现特定的数据模型,或提供具有自定义绘画和绘图功能的类型,或访问系统功能(如网络编程,不能通过内置的 QML 功能访问)。

在本教程中,我们将展示如何使用 Qt Quick 模块中的 C++ 类来扩展 QML。最终结果将是显示一个简单的饼图,由几个自定义 QML 类型实现,这些类型通过 QML 功能(如绑定和信号)连接在一起,并通过插件提供给 QML 运行时。

首先,让我们创建一个新的 QML 类型,叫做 “PieChart”,它有两个属性:名称和颜色。我们将使其在名为 “Charts” 的可导入类型命名空间中可用,版本为 1.0。

我们希望 PieChart 类型可以这样在 QML 中使用:

 import Charts 1.0

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

要做到这点,我们需要一个 C++ 类来封装 PieChart 类型和它的两个属性。由于 QML 广泛使用 Qt 的 元对象系统,这个新类必须:

类的声明

下面是我们的 PieChart 类,定义在 piechart.h 中:

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

 class PieChart : public QQuickPaintedItem
 {
     Q_OBJECT
     Q_PROPERTY(QString name READ name WRITE setName)
     Q_PROPERTY(QColor color READ color WRITE setColor)
     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;
 };

该类继承自 QQuickPaintedItem,因为我们要重写 QQuickPaintedItem::paint(),以便用 QPainter API 进行绘图操作。如果这个类只是表示一些数据类型,而不是实际需要显示的项目,它可以简单地继承QObject。或者,如果我们想扩展现有的基于 QObject 的类的功能,它可以从该类继承。另外,如果我们想创建一个不需要用 QPainter API 进行绘图操作的可视项,我们可以只继承 QQuickItem

PieChart 类用 Q_PROPERTY 定义了 名称颜色 这两个属性,并重写了 QQuickPaintedItem::paint()PieChart 类是用 QML_ELEMENT 宏注册的,以允许它被 QML 使用。如果你不注册这个类,app.qml 将无法创建 PieChart

qmake 设置

为了使注册生效,在项目文件的 CONFIG 中添加了 qmltypes 选项,并设置了 QML_IMPORT_NAMEQML_IMPORT_MAJOR_VERSION

 CONFIG += qmltypes
 QML_IMPORT_NAME = Charts
 QML_IMPORT_MAJOR_VERSION = 1

CMake 设置

同样,要在使用 CMake 时使注册生效,请使用 qt_add_qml_module 命令:

 qt_add_qml_module(chapter1-basics
     URI Charts
     VERSION 1.0
     QML_FILES app.qml
     NO_RESOURCE_TARGET_PATH
 )

类的实现

piechart.cpp 中的类实现只是简单地设置和返回 m_namem_color 值,并实现 paint() 来绘制一个简单的饼图:

 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 用法

现在我们已经定义了 PieChart 类型,我们将在 QML 中使用它。app.qml 创建了一个 PieChart item,并使用一个标准的 QML Text 显示饼图的文本信息。

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

请注意,尽管颜色在 QML 中被指定为字符串,但对于 PieChart 的 color 属性,它被自动转换为一个 QColor 对象。各种 值类型 都会自动转换。例如,像 “640x480” 这样的字符串可以自动转换为 QSize 值。

我们还将创建一个 C++ 应用程序,使用 QQuickView 来运行和显示 app.qml

这是应用程序 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.setSource(QUrl("qrc:///app.qml"));
     view.show();
     return QGuiApplication::exec();
 }

项目构建

为了构建项目,我们包含文件,链接库,并为公开给 QML 的类型定义了一个名为 “Charts” 的类型命名空间,版本为 1.0。

使用 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

使用 CMake:

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

 set(CMAKE_AUTOMOC ON)

 if(NOT DEFINED INSTALL_EXAMPLESDIR)
     set(INSTALL_EXAMPLESDIR "examples")
 endif()

 set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qml/tutorials/extending-qml/chapter1-basics")

 find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)

 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
     Qt::Core
     Qt::Gui
     Qt::Qml
     Qt::Quick
 )
 qt_add_qml_module(chapter1-basics
     URI Charts
     VERSION 1.0
     QML_FILES app.qml
     NO_RESOURCE_TARGET_PATH
 )
 install(TARGETS chapter1-basics
     RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
     BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
     LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
 )

现在我们可以构建并运行应用程序:

注意:你可能会看到一个警告 Expression ... depends on non-NOTIFYable properties: PieChart::name。这是因为我们给可写的 name 属性添加了一个绑定,但还没有为它定义一个通知信号。因此,如果 name 的值发生变化,QML 引擎将无法更新绑定。这个问题将在后面章节中解决。

本章引用了以下源码:extending-qml/chapter1-basics

第 2 章:与 C++ 方法和信号的连接

假设我们希望 PieChart 有个 “clearChart()” 方法来擦除图表,然后发出 “chartCleared” 信号。app.qml 可以这样调用 clearChart() 并接收 chartCleared() 信号:

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

为此,我们在 C++ 类中添加 clearChart() 方法和 chartCleared() 信号:

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

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

Q_INVOKABLE 的使用使得 clearChart() 方法在 Qt 元对象系统可用,进而可用于 QML。请注意,它可以被声明为 Qt 槽函数而非使用 Q_INVOKABLE,因为槽函数也可以从 QML 调用。这两种方法都是有效的。

clearChart() 方法只是将颜色更改为 Qt::transparent,重新绘制图表,然后发出 chartCleared() 信号:

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

     emit chartCleared();
 }

现在,当我们运行应用程序并单击窗口时,饼图消失,而应用程序则输出:

 qml: The chart has been cleared

本章引用了以下源码:extending-qml/chapter2-methods

第 3 章:添加属性绑定

属性绑定是 QML 的一个强大功能,它允许自动同步不同类型的值。当属性值发生变化时,它使用信号来通知和更新其他类型的值。

让我们为 color 属性启用属性绑定。这意味着如果我们有这样的代码:

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

“color: chartA.color” 语句将 chartBcolor 值与 chartAcolor 值绑定。每当 chartAcolor 值发生变化时,chartBcolor 值也会更新为相同的值。当窗口被点击时,MouseArea 中的 onClicked 处理程序会改变 chartA 的颜色,从而将两个图表都更改为蓝色。

color 属性启用属性绑定很容易。我们在其 Q_PROPERTY() 声明中添加了 NOTIFY 特性,以表明每当值发生变化时就会发出 “colorChanged” 信号。

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

然后,我们在 setColor() 中发出 colorChanged() 信号:

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

对于 setColor() 来说,在发出 colorChanged() 之前,检查颜色值是否真的发生了变化是很重要的。这可以确保信号不会被不必要地发出,也可以防止其他类型响应值更改时出现循环。

绑定的使用对 QML 来说是至关重要的。如果属性能够被实现,你应该始终为属性添加 NOTIFY 信号,这样你的属性就可以在绑定中使用。不能被绑定的属性不能被自动更新,也不能在 QML 中被灵活地使用。此外,由于绑定在 QML 的使用中被频繁地调用和依赖,如果没有实现绑定,自定义 QML 类型的用户可能会遇到 unexpected behavior。

本章引用了以下源码:extending-qml/chapter3-bindings

第 4 章:使用自定义属性类型

PieChart 目前有一个字符串类型的属性和一个颜色类型的属性。它可以有许多其他类型的属性。例如,它可以有一个 int-type 属性来存储每个图表的标识符:

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

除了 int 之外,我们还可以使用其他各种属性类型。QML 自动支持许多 Qt 的数据类型,例如 QColorQSizeQRect。(有关完整列表,请参阅 QML 和 C++ 之间的数据类型转换 文档)

如果我们想创建一个其类型不被 QML 默认支持的属性,我们需要向 QML 引擎注册该类型。

例如,让我们用一个叫做 “PieSlice” 的类型来代替 property 的使用,它有一个 color 属性。我们没有分配颜色,而是分配一个本身包含 color 值的 PieSlice

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

PieChart,这个新 PieSlice 类型继承自 QQuickPaintedItem,并且用 Q_PROPERTY() 声明其属性。

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

 public:
     PieSlice(QQuickItem *parent = nullptr);

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

     void paint(QPainter *painter) override;

 private:
     QColor m_color;
 };

要在 PieChart 中使用它,我们修改 color 属性声明和相关的方法签名:

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

定义 setPieSlice() 时需要注意一件事。PieSlice 是一个可视项,所以必须使用 QQuickItem::setParentItem() 将其设置为 PieChart 的子项,这样 PieChart 就知道在绘制其内容时要绘制这个子项:

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

PieChart 类型一样,必须使用 QML_ELEMENTPieSlice 类型发布到 QML。

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

 public:
     PieSlice(QQuickItem *parent = nullptr);

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

     void paint(QPainter *painter) override;

 private:
     QColor m_color;
 };
     ...

PieChart 一样,我们将 “Charts” 类型命名空间,版本 1.0,添加到我们的构建文件中:

使用 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:

     ...
 qt_add_executable(chapter4-customPropertyTypes
     main.cpp
     piechart.cpp piechart.h
     pieslice.cpp pieslice.h
 )
 qt_add_qml_module(chapter4-customPropertyTypes
     URI Charts
     VERSION 1.0
     QML_FILES app.qml
     NO_RESOURCE_TARGET_PATH
 )
     ...

本章引用了以下源码:extending-qml/chapter4-customPropertyTypes

第 5 章:使用列表属性类型

现在,一个 PieChart 只能有一个 PieSlice。理想情况下,一个 chart 会有多个 slice,有不同的颜色和大小。为此,我们可以设一个 slices 属性,接受 PieSlice 项的列表:

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

为此,我们将 PieChart 中的 pieSlice 属性改为 slices 属性,声明为 QQmlListProperty 类型。QQmlListProperty 类能够在 QML 扩展中创建列表属性。我们用返回 slice 列表的 slices() 函数替换了 pieSlice() 函数,并添加了内部的 append_slice() 函数(在下面讨论)。我们还使用名为 m_slicesQList 来存储内部的 slice 列表:

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

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

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

尽管 slices 属性没有相关的 WRITE 函数,但由于 QQmlListProperty 的工作方式,它仍然是可修改的。在 PieChart 的实现中,我们实现了 PieChart::slices() 来返回 QQmlListProperty 的值,并表明每当 QML 发出向列表添加项目的请求时,内部的 PieChart::append_slice() 函数将被调用:

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

append_slice() 函数还是像之前那样设置父项,并将新项添加到 m_slices 列表中。QQmlListProperty 的 append 函数是用两个参数调用的:列表属性和要附加的项目。

PieSlice 类也被修改为包含 fromAngleangleSpan 属性,并根据这些值绘制 slice。如果你已阅读本教程的前几页,这是一个简单的修改,所以此处不显示代码。

本章引用了以下源码:extending-qml/chapter5-listproperties

第 6 章:编写扩展插件

目前,PieChartPieSlice 类型由 app.qml 使用,在 C++ 应用程序中使用 QQuickView 显示。使用我们的 QML 扩展的另一种方法是创建一个插件库,把它当作新的 QML 导入模块提供给 QML 引擎。这使 PieChartPieSlice 类型被注册到一个类型命名空间,可以被任何 QML 应用程序导入,而非限制这些类型只能被一个应用程序使用。

创建插件的步骤在 为 QML 创建 C++ 插件 中描述。首先,我们创建一个名为 ChartsPlugin 的插件类。它继承 QQmlEngineExtensionPlugin,并使用 Q_PLUGIN_METADATA() 宏将插件注册到 Qt 元对象系统中。

这是 chartsplugin.h 中的 ChartsPlugin 定义:

 #include <QQmlEngineExtensionPlugin>

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

然后,我们配置构建文件,将该项目定义为插件库。

使用 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:

 qt6_add_qml_module(chartsplugin
     VERSION 1.0
     URI "Charts"
     PLUGIN_TARGET chartsplugin
 )

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

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

 if(QT6_IS_SHARED_LIBS_BUILD AND APPLE)
     get_target_property(is_bundle chapter6-plugins MACOSX_BUNDLE)
     if(is_bundle)
         # The application's main.cpp adds an explicit QML import path to look for qml modules under
         # a PlugIns subdirectory in a macOS bundle.
         # Copy the qmldir and shared library qml plugin.

         set(charts_dir "$<TARGET_FILE_DIR:chartsplugin>")
         set(chars_qmldir_file "${charts_dir}/qmldir")
         set(app_dir "$<TARGET_FILE_DIR:chapter6-plugins>")
         set(bundle_charts_dir "${app_dir}/../PlugIns/Charts")

         add_custom_command(TARGET chartsplugin POST_BUILD
             COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_charts_dir}
             COMMAND ${CMAKE_COMMAND} -E copy_if_different
                     $<TARGET_FILE:chartsplugin> ${bundle_charts_dir}
             COMMAND ${CMAKE_COMMAND} -E copy_if_different
                     ${chars_qmldir_file} ${bundle_charts_dir}
             VERBATIM
         )
     endif()
 endif()

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

在 Windows 或 Linux 上构建该示例时,Charts 目录将与使用我们新导入模块的应用程序位于同一级别。这样,QML 引擎会找到我们的模块,因为 QML 导入的默认搜索路径包括应用程序的可执行目录。在 macOS 上,插件的二进制文件被复制到应用程序包中的 Contents/PlugIns。在 qmake 中,此路径设置在 chapter6-plugins/app.pro

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

考虑到这一点,我们还需要在 main.cpp 中添加此位置作为 QML 导入路径

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

当有多个应用程序使用相同的 QML 导入时,定义自定义导入路径也很有用。

.pro 文件还包含额外的魔法,以确保 模块定义 qmldir 文件 始终复制到与插件二进制文件相同的位置。

qmldir 文件声明了模块名称和模块可用的插件:

 module Charts
 linktarget chartsplugin
 optional plugin chartsplugin
 classname ChartsPlugin
 typeinfo chartsplugin.qmltypes
 prefer :/Charts/

现在我们有了一个 QML 模块,只要QML引擎知道在哪里可以找到它,它就可以被导入到任何应用程序。该示例包含加载 app.qml 的可执行文件,它使用 import Charts 1.0 语句。另外,你可以使用 qml 工具 加载 QML 文件,将导入路径设置为当前目录,这样它就能找到 qmldir 文件:

 qml -I . app.qml

“Charts” 模块将由 QML 引擎加载,该模块提供的类型将可用于任何导入它的 QML 文档。

本章引用了以下源码:extending-qml/chapter6-plugins

第 7 章:总结

在本教程中,我们展示了创建 QML 扩展的基本步骤:

  • 通过继承 QObject 并使用 QML_ELEMENTQML_NAMED_ELEMENT() 注册来定义新的 QML 类型
  • 使用 Q_INVOKABLE 或 Qt 槽函数添加可调用的方法,并使用 onSignal 语法连接 Qt 信号
  • 通过定义 NOTIFY 信号添加属性绑定
  • 如果内置类型不够用,可以定义自定义属性类型
  • 使用 QQmlListProperty 定义列表属性类型
  • 通过定义 Qt 插件并编写 qmldir 文件来创建插件库

QML 和 C++ 集成概述 文档显示了可以添加到 QML 扩展中的其他有用功能。例如,我们可以使用 默认属性 可以在不使用 slices 属性的情况下添加 slice:

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

或者使用 property value sources 不时地随机添加和删除 slice:

 PieChart {
     PieSliceRandomizer on slices {}
 }

示例项目 @ code.qt.io