扩展QML - 创建新的类型 #

这是关于使用Python扩展QML的6个示例系列中的第一个,形成了一个教程。

Qt QML模块通过Python扩展提供一组API来扩展QML。您可以通过写出扩展来添加自己的QML类型,扩展现有的Qt类型,或调用无法从普通QML代码访问的Python函数。

本教程展示了如何使用Python编写一个包含核心QML特性(包括属性、信号和绑定)的QML扩展。它还展示了如何通过插件部署扩展。

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

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

首先,让我们创建一个新的名为PieChart的QML类型,它具有两个属性:一个名称和一个颜色。我们将它放置在一个名为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的元对象系统,这个新的类必须:

  • QObject继承

  • 使用Property装饰器声明其属性

类实现#

下面是我们的PieChart类,定义在basics.py中:

21@QmlElement
22class PieChart (QQuickPaintedItem):
23
24    nameChanged = Signal()
25
26    def __init__(self, parent=None):
27        QQuickPaintedItem.__init__(self, parent)
28        self._name = u''
29        self._color = QColor()
30
31    def paint(self, painter):
32        pen = QPen(self.color, 2)
33        painter.setPen(pen)
34        painter.setRenderHints(QPainter.Antialiasing, True)
35        painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16)
36
37    @Property(QColor, final=True)
38    def color(self):
39        return self._color
40
41    @color.setter
42    def color(self, value):
43        self._color = value
44
45    @Property(str, notify=nameChanged, final=True)
46    def name(self):
47        return self._name
48
49    @name.setter
50    def name(self, value):
51        self._name = value

该类从QQuickPaintedItem继承,因为我们想要重写QQuickPaintedItem.paint()以使用QPainter API进行绘图操作。如果一个类仅仅代表某种数据类型,而实际上并不需要有显示的项,那么它只需从QObject继承即可。或者,如果我们想扩展现有基于QObject的类的功能,则可以继承那个类。或者,如果我们想要创建一个不需要使用QPainter API进行绘图操作的可视项,我们可以简单地从QQuickItem派生。

PieChart类定义了两个属性,使用Property装饰器,并重写了QQuickPaintedItem.paint()。该PieChart类使用QmlElement装饰器进行注册,以便可以从QML中使用。如果你没有注册这个类,则app.qml将无法创建一个PieChart

QML使用#

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

 7Item {
 8    width: 300; height: 200
 9
10    PieChart {
11        id: aPieChart
12        anchors.centerIn: parent
13        width: 100; height: 100
14        name: "A simple pie chart"
15        color: "red"
16    }
17
18    Text {
19        anchors {
20            bottom: parent.bottom;
21            horizontalCenter: parent.horizontalCenter;
22            bottomMargin: 20
23        }
24        text: aPieChart.name
25    }
26}

请注意,虽然颜色在QML中被指定为一个字符串,但它会自动转换为QColor对象,用于饼图的color属性。提供自动转换以支持其他各种QML值类型。例如,类似于“640x480”的字符串可以自动转换为QSize值。

我们还将创建一个主函数,它使用一个 QQuickView 来运行和显示 app.qml。以下是应用程序的 basics.py

54if __name__ == '__main__':
55    app = QGuiApplication(sys.argv)
56
57    view = QQuickView()
58    view.setResizeMode(QQuickView.SizeRootObjectToView)
59    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
60    view.setSource(QUrl.fromLocalFile(qml_file))
61    if view.status() == QQuickView.Error:
62        sys.exit(-1)
63    view.show()
64    res = app.exec()
65    # Deleting the view before it goes out of scope is required to make sure all child QML instances
66    # are destroyed in the correct order.
67    del view
68    sys.exit(res)

注意

您可能会看到一个警告 表达式中 ... 依赖于非通知属性:PieChart.name。这是因为我们向可写的 name 属性添加了绑定,但我们还没有为它定义通知信号。因此,当 name 值改变时,QML 引擎无法更新此绑定。这在接下来的章节中得到了解决。

下载 此示例

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

"""PySide6 port of the qml/tutorials/extending-qml/chapter1-basics example from Qt v5.x"""

import os
from pathlib import Path
import sys

from PySide6.QtCore import Property, Signal, QUrl
from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor
from PySide6.QtQml import QmlElement
from PySide6.QtQuick import QQuickPaintedItem, QQuickView

# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "Charts"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class PieChart (QQuickPaintedItem):

    nameChanged = Signal()

    def __init__(self, parent=None):
        QQuickPaintedItem.__init__(self, parent)
        self._name = u''
        self._color = QColor()

    def paint(self, painter):
        pen = QPen(self.color, 2)
        painter.setPen(pen)
        painter.setRenderHints(QPainter.Antialiasing, True)
        painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16)

    @Property(QColor, final=True)
    def color(self):
        return self._color

    @color.setter
    def color(self, value):
        self._color = value

    @Property(str, notify=nameChanged, final=True)
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value


if __name__ == '__main__':
    app = QGuiApplication(sys.argv)

    view = QQuickView()
    view.setResizeMode(QQuickView.SizeRootObjectToView)
    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
    view.setSource(QUrl.fromLocalFile(qml_file))
    if view.status() == QQuickView.Error:
        sys.exit(-1)
    view.show()
    res = app.exec()
    # Deleting the view before it goes out of scope is required to make sure all child QML instances
    # are destroyed in the correct order.
    del view
    sys.exit(res)
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

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