扩展QML - 创建新类型

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

Qt QML 模块提供了一组 API,用于通过 Python 扩展来扩展 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的元对象系统,这个新类必须:

类实现

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

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

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

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

QML 使用

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

 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中指定为字符串,但它会自动转换为PieChart的color属性的QColor对象。自动转换提供了各种其他QML值类型。例如,像“640x480”这样的字符串可以自动转换为QSize值。

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

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

注意

你可能会看到一个警告 表达式 … 依赖于不可通知的属性: PieChart.name。这是因为我们向可写的 name 属性添加了绑定,但尚未为其定义通知信号。因此,如果 name 值发生变化,QML 引擎无法更新绑定。这个问题将在接下来的章节中解决。

下载 这个 示例

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

"""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.RenderHint.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.Status.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
    }
}