用C++编写QML扩展

Writing QML Extensions with C++

用C++编写QML扩展

The Qt QML module provides a set of APIs for extending QML through C++ extensions. You can write extensions to add your own QML types, extend existing Qt types, or call C/C++ functions that are not accessible from ordinary QML code.

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

This tutorial shows how to write a QML extension using C++ that includes core QML features, including properties, signals and bindings. It also shows how extensions can be deployed through plugins.

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

Many of the topics covered in this tutorial are documented in further detail in Overview - QML and C++ Integration and its documentation sub-topics. In particular, you may be interested in the sub-topics Exposing Attributes of C++ Classes to QML and Defining QML Types from C++.

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

Running the Tutorial Examples

运行教程示例

The code in this tutorial is available as an example project with subprojects associated with each tutorial chapter. In Qt Creator, open the Welcome mode and select the tutorial from Examples. In Edit mode, expand the extending-qml project, right-click on the subproject (chapter) you want to run and select Run.

​本教程中的代码作为示例项目提供,其中包含与每个教程章节相关联的子项目。在Qt Creator中,打开欢迎模式,然后从示例中选择教程。在编辑模式下,展开extending-qml项目,右键单击要运行的子项目(章节),然后选择运行。

Creating Tutorial Project

创建教程项目

We create a new project using the Qt Quick Application template in Qt Creator, as instructed in Qt Creator: Creating Qt Quick Projects.

​我们使用Qt Creator中的Qt Quick应用程序模板创建一个新项目,如Qt Creator:Createing Qt Quick Projects中所述。

Chapter 1: Creating a New Type

第一章:创造一种新型

extending-qml/chapter1-basics

A common task when extending QML is to provide a new QML type that supports some custom functionality beyond what is provided by the built-in Qt Quick types. For example, this could be done to implement particular data models, or provide types with custom painting and drawing capabilities, or access system features like network programming that are not accessible through built-in QML features.

​扩展QML时的一个常见任务是提供一种新的QML类型,该类型支持内置Qt-Quick类型之外的一些自定义功能。例如,这样做可以实现特定的数据模型,或者为类型提供自定义绘画和绘图功能,或者访问无法通过内置QML功能访问的系统功能,如网络编程。

In this tutorial, we will show how to use the C++ classes in the Qt Quick module to extend QML. The end result will be a simple Pie Chart display implemented by several custom QML types connected together through QML features like bindings and signals, and made available to the QML runtime through a plugin.

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

To begin with, let's create a new QML type called "PieChart" that has two properties: a name and a color. We will make it available in an importable type namespace called "Charts", with a version of 1.0.

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

We want this PieChart type to be usable from QML like this:

我们希望QML可以使用这种饼图类型,如下所示:

import Charts 1.0

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

To do this, we need a C++ class that encapsulates this PieChart type and its two properties. Since QML makes extensive use of Qt's meta object system, this new class must:

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

  • Inherit from QObject
  • 从QObject继承
  • Declare its properties using the Q_PROPERTY macro
  • ​使用Q_PROPERTY宏声明其属性

Class Declaration

类声明

Here is our PieChart class, defined in piechart.h:

这是我们的PieChart类,在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;
};

The class inherits from QQuickPaintedItem because we want to override QQuickPaintedItem::paint() to perform drawing operations with the QPainter API. If the class just represented some data type and was not an item that actually needed to be displayed, it could simply inherit from QObject. Or, if we want to extend the functionality of an existing QObject-based class, it could inherit from that class instead. Alternatively, if we want to create a visual item that doesn't need to perform drawing operations with the QPainter API, we can just subclass QQuickItem.

​该类继承自QQuickPaintedItem,因为我们希望重写QQuickPaintedItem::paint()以使用QPainter API执行绘图操作。如果类只是表示某种数据类型,而不是实际需要显示的项,那么它可以简单地从QObject继承。或者,如果我们想扩展现有的基于QObject的类的功能,它可以从该类继承。或者,如果想创建一个不需要使用QPainter API执行绘图操作的可视化项,我们可以将QQuickItem子类化。

The PieChart class defines the two properties, name and color, with the Q_PROPERTY macro, and overrides QQuickPaintedItem::paint(). The PieChart class is registered using the QML_ELEMENT macro, to allow it to be used from QML. If you don't register the class, app.qml won't be able to create a PieChart.

​PieChart类使用Q_PROPERTY宏定义名称和颜色这两个属性,并覆盖QQuickPaintedItem::paint()。PieChart类是使用QML_ELEMENT宏注册的,以允许从QML使用它。如果不注册该类,app.qml将无法创建饼图。

qmake Setup

qmake设置

For the registration to take effect, the qmltypes option is added to CONFIG in the project file and a QML_IMPORT_NAME and QML_IMPORT_MAJOR_VERSION are given:

为了使注册生效,将qmltypes选项添加到项目文件中的CONFIG中,并给出QML_IMPORT_NAME和QML_IMPORT _MAJOR_VERSION:

CONFIG += qmltypes
QML_IMPORT_NAME = Charts
QML_IMPORT_MAJOR_VERSION = 1

CMake Setup

CMake设置

Similarly, for the registration to take effect when using CMake, use the qt_add_qml_module command:

​类似地,要使注册在使用CMake时生效,请使用qt_add_qml_module命令:

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

Class Implementation

类的实现

The class implementation in piechart.cpp simply sets and returns the m_name and m_color values as appropriate, and implements paint() to draw a simple pie chart:

piechart.cpp中的类实现只是根据需要设置并返回m_name和m_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 Usage

QML使用

Now that we have defined the PieChart type, we will use it from QML. The app.qml file creates a PieChart item and displays the pie chart's details using a standard QML Text item:

​既然我们已经定义了饼图类型,我们将从QML中使用它。app.qml文件创建一个饼图项目,并使用标准qml文本项目显示饼图的详细信息:

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

Notice that although the color is specified as a string in QML, it is automatically converted to a QColor object for the PieChart color property. Automatic conversions are provided for various other value types. For example, a string like "640x480" can be automatically converted to a QSize value.

​请注意,尽管颜色在QML中被指定为字符串,但它会自动转换为饼图颜色属性的QColor对象。为各种其他值类型提供了自动转换。例如,像“640x480”这样的字符串可以自动转换为QSize值。

We'll also create a C++ application that uses a QQuickView to run and display app.qml.

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

Here is the application main.cpp:

以下是应用程序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();
}

Project Build

项目构建

To build the project we include the files, link against the libraries, and define a type namespace called "Charts" with version 1.0 for any types exposed to QML.

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

Using qmake:

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

Using CMake:

使用CMake:

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

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

Now we can build and run the application:

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

Note: You may see a warning Expression ... depends on non-NOTIFYable properties: PieChart::name. This happens because we add a binding to the writable name property, but haven't yet defined a notify signal for it. The QML engine therefore cannot update the binding if the name value changes. This is addressed in the following chapters.

注意:可能会看到一个警告表达式。。。依赖于不可通知的属性:PieChart::name。发生这种情况是因为我们向可写名称属性添加了绑定,但尚未为其定义通知信号。因此,如果名称值发生更改,QML引擎将无法更新绑定。这将在以下章节中讨论。

Chapter 2: Connecting to C++ Methods and Signals

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

extending-qml/chapter2-methods

Suppose we want PieChart to have a "clearChart()" method that erases the chart and then emits a "chartCleared" signal. Our app.qml would be able to call clearChart() and receive chartCleared() signals like this:

假设我们希望PieChart有一个“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"
    }
}

To do this, we add a clearChart() method and a chartCleared() signal to our C++ class:

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

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

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

The use of Q_INVOKABLE makes the clearChart() method available to the Qt Meta-Object system, and in turn, to QML. Note that it could have been declared as a Qt slot instead of using Q_INVOKABLE, as slots are also callable from QML. Both of these approaches are valid.

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

The clearChart() method simply changes the color to Qt::transparent, repaints the chart, then emits the chartCleared() signal:

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

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

    emit chartCleared();
}

Now when we run the application and click the window, the pie chart disappears, and the application outputs:

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

qml: The chart has been cleared

Chapter 3: Adding Property Bindings

第3章:添加属性绑定

extending-qml/chapter3-bindings

Property binding is a powerful feature of QML that allows values of different types to be synchronized automatically. It uses signals to notify and update other types' values when property values are changed.

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

Let's enable property bindings for the color property. That means if we have code like this:

让我们为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"
    }
}

The "color: chartA.color" statement binds the color value of chartB to the color of chartA. Whenever chartA's color value changes, chartB's color value updates to the same value. When the window is clicked, the onClicked handler in the MouseArea changes the color of chartA, thereby changing both charts to the color blue.

​“color:chartA.color”语句将chartB的颜色值绑定到chartA的颜色。每当chartA的颜色值发生变化时,chartB的颜色值就会更新为相同的值。单击窗口时,鼠标区域中的onClicked处理程序会更改chartA的颜色,从而将两个图表都更改为蓝色。

It's easy to enable property binding for the color property. We add a NOTIFY feature to its Q_PROPERTY() declaration to indicate that a "colorChanged" signal is emitted whenever the value changes.

​为color属性启用属性绑定很容易。我们在其Q_PROPERTY()声明中添加了NOTIFY功能,以指示每当值更改时都会发出“colorChanged”信号。

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

Then, we emit this signal in setColor():

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

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

It's important for setColor() to check that the color value has actually changed before emitting colorChanged(). This ensures the signal is not emitted unnecessarily and also prevents loops when other types respond to the value change.

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

The use of bindings is essential to QML. You should always add NOTIFY signals for properties if they are able to be implemented, so that your properties can be used in bindings. Properties that cannot be bound cannot be automatically updated and cannot be used as flexibly in QML. Also, since bindings are invoked so often and relied upon in QML usage, users of your custom QML types may see unexpected behavior if bindings are not implemented.

绑定的使用对QML至关重要。如果可以实现属性,则应始终为其添加NOTIFY信号,以便在绑定中使用属性。无法绑定的属性不能自动更新,也不能在QML中灵活使用。此外,由于绑定在QML使用中经常被调用和依赖,因此如果不实现绑定,自定义QML类型的用户可能会看到意外行为。

Chapter 4: Using Custom Property Types

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

extending-qml/chapter4-customPropertyTypes

The PieChart type currently has a string-type property and a color-type property. It could have many other types of properties. For example, it could have an int-type property to store an identifier for each chart:

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

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

Aside from int, we could use various other property types. Many of the Qt data types such as QColorQSize and QRect are automatically supported from QML. (See Data Type Conversion Between QML and C++ documentation for a full list.)

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

If we want to create a property whose type is not supported by QML by default, we need to register the type with the QML engine.

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

For example, let's replace the use of the property with a type called "PieSlice" that has a color property. Instead of assigning a color, we assign an PieSlice value which itself contains a color:

例如,让我们将属性的使用替换为具有颜色属性的名为“PieSlice”的类型。我们不指定颜色,而是指定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)
}

Like PieChart, this new PieSlice type inherits from QQuickPaintedItem and declares its properties with Q_PROPERTY():

​与PieChart一样,这个新的PieSlice类型继承自QQuickPaintedItem,并使用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;
};

To use it in PieChart, we modify the color property declaration and associated method signatures:

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

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

There is one thing to be aware of when implementing setPieSlice(). The PieSlice is a visual item, so it must be set as a child of the PieChart using QQuickItem::setParentItem() so that the PieChart knows to paint this child item when its contents are drawn:

​在实现setPieSlice()时需要注意一件事。PieSlice是一个可视化项,因此必须使用QQuickItem::setParentItem()将其设置为PieChart的子项,以便PieChart知道在绘制其内容时绘制此子项:

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

Like the PieChart type, the PieSlice type has to be exposted to QML using QML_ELEMENT.

​与PieChart类型一样,PieSlice类型必须使用QML_ELEMENT暴露给QML。

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

As with PieChart, we add the "Charts" type namespace, version 1.0, to our build file:

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

Using qmake:

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

Using CMake:

使用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
    NO_RESOURCE_TARGET_PATH
    DEPENDENCIES QtQuick
)
    ...

Chapter 5: Using List Property Types

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

extending-qml/chapter5-listproperties

Right now, a PieChart can only have one PieSlice. Ideally a chart would have multiple slices, with different colors and sizes. To do this, we could have a slices property that accepts a list of PieSlice items:

目前,饼图只能有一个饼图切片。理想情况下,一个图表应该有多个不同颜色和大小的切片。要做到这一点,我们可以有一个接受PieSlice项目列表的slice属性:

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

To do this, we replace the pieSlice property in PieChart with a slices property, declared as a QQmlListProperty type. The QQmlListProperty class enables the creation of list properties in QML extensions. We replace the pieSlice() function with a slices() function that returns a list of slices, and add an internal append_slice() function (discussed below). We also use a QList to store the internal list of slices as m_slices:

​为此,我们将PieChart中的pieSlice属性替换为slice属性,声明为QQmlListProperty类型。QQmlListProperty类允许在QML扩展中创建列表属性。我们用一个返回切片列表的slices()函数替换pieSlice()函数,并添加一个内部append_slice(()函数(如下所述)。我们还使用QList将切片的内部列表存储为m_slices:

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

Although the slices property does not have an associated WRITE function, it is still modifiable because of the way QQmlListProperty works. In the PieChart implementation, we implement PieChart::slices() to return a QQmlListProperty value and indicate that the internal PieChart::append_slice() function is to be called whenever a request is made from QML to add items to the list:

​尽管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);
    }
}

The append_slice() function simply sets the parent item as before, and adds the new item to the m_slices list. As you can see, the append function for a QQmlListProperty is called with two arguments: the list property, and the item that is to be appended.

​append_slice()函数只需将父项设置为以前的项,并将新项添加到m_slice列表中。正如所看到的,QQmlListProperty的append函数是用两个参数调用的:list属性和要附加的项。

The PieSlice class has also been modified to include fromAngle and angleSpan properties and to draw the slice according to these values. This is a straightforward modification if you have read the previous pages in this tutorial, so the code is not shown here.

PieSlice类也被修改为包括fromAngle和angleSpan属性,并根据这些值绘制切片。如果您已经阅读了本教程中的前几页,那么这是一个简单的修改,因此此处不显示代码。

Chapter 6: Writing an Extension Plugin

第6章:编写扩展插件

extending-qml/chapter6-plugins

Currently the PieChart and PieSlice types are used by app.qml, which is displayed using a QQuickView in a C++ application. An alternative way to use our QML extension is to create a plugin library to make it available to the QML engine as a new QML import module. This allows the PieChart and PieSlice types to be registered into a type namespace which can be imported by any QML application, instead of restricting these types to be only used by the one application.

​目前,饼图和饼切片类型由app.qml使用,它在C++应用程序中使用QQuickView显示。使用QML扩展的另一种方法是创建一个插件库,使其作为新的QML导入模块可用于QML引擎。这允许将PieChart和PieSlice类型注册到任何QML应用程序都可以导入的类型命名空间中,而不是限制这些类型只能由一个应用程序使用。

The steps for creating a plugin are described in Creating C++ Plugins for QML. To start with, we create a plugin class named ChartsPlugin. It subclasses QQmlEngineExtensionPlugin and uses the Q_PLUGIN_METADATA() macro to register the plugin with the Qt meta object system.

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

Here is the ChartsPlugin definition in chartsplugin.h:

以下是chartplugin.h中的ChartsPlugin定义:

#include <QQmlEngineExtensionPlugin>

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

Then, we configure the build file to define the project as a plugin library.

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

Using qmake:

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

Using CMake:

使用CMake:

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: 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
)

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

When building this example on Windows or Linux, the Charts directory will be located at the same level as the application that uses our new import module. This way, the QML engine will find our module as the default search path for QML imports includes the directory of the application executable. On macOS, the plugin binary is copied to Contents/PlugIns in the the application bundle. With qmake, this path is set in chapter6-plugins/app.pro:

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

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

To account for this, we also need to add this location as a QML import path in main.cpp:

​为此,我们还需要在main.cpp中添加此位置作为QML导入路径:

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

Defining custom import paths is useful also when there are multiple applications using the same QML imports.

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

The .pro file also contains additional magic to ensure that the module definition qmldir file is always copied to the same location as the plugin binary.

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

The qmldir file declares the module name and the plugin that is made available by the module:

qmldir文件声明模块名称和模块提供的插件:

module Charts
plugin chartsplugin

Now we have a QML module that can be imported to any application, provided that the QML engine knows where to find it. The example contains an executable that loads app.qml, which uses the import Charts 1.0 statement. Alternatively, you can load the QML file using the qml tool, setting the import path to the current directory so that it finds the qmldir file:

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

qml -I . app.qml

The module "Charts" will be loaded by the QML engine, and the types provided by that module will be available for use in any QML document which imports it.

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

Chapter 7: Summary

第7章:概述

In this tutorial, we've shown the basic steps for creating a QML extension:

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

  • Define new QML types by subclassing QObject and registering them with QML_ELEMENT or QML_NAMED_ELEMENT()
  • ​通过子类化QObject并将其注册到QML_ELEMENT或QML_NAMED_ELEMENT()来定义新的QML类型
  • Add callable methods using Q_INVOKABLE or Qt slots, and connect to Qt signals with an onSignal syntax
  • ​使用Q_INVOKABLE或Qt插槽添加可调用方法,并使用onSignal语法连接到Qt信号
  • Add property bindings by defining NOTIFY signals
  • ​通过定义NOTIFY信号添加属性绑定
  • Define custom property types if the built-in types are not sufficient
  • 如果内置类型不足,请定义自定义属性类型
  • Define list property types using QQmlListProperty
  • ​使用QQmlListProperty定义列表属性类型
  • Create a plugin library by defining a Qt plugin and writing a qmldir file
  • ​通过定义Qt插件并编写qmldir文件创建插件库

The QML and C++ Integration overview documentation shows other useful features that can be added to QML extensions. For example, we could use default properties to allow slices to be added without using the slices property:

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

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

Or randomly add and remove slices from time to time using property value sources:

​或者使用属性值源不时随机添加和删除切片:

PieChart {
    PieSliceRandomizer on slices {}
}

Note: To continue learning about QML extensions and features follow the Writing advanced QML Extensions with C++ tutorial.


注意:要继续学习QML扩展和功能,请参阅用C++编写高级QML扩展教程。

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值