扩展QML - 添加属性绑定#

这是关于使用Python扩展QML的6个示例教程系列的第三篇。

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

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

 7Item {
 8    width: 300; height: 200
 9
10    Row {
11        anchors.centerIn: parent
12        spacing: 20
13
14        PieChart {
15            id: chartA
16            width: 100; height: 100
17            color: "red"
18        }
19
20        PieChart {
21            id: chartB
22            width: 100; height: 100
23            color: chartA.color
24        }
25    }
26
27    MouseArea {
28        anchors.fill: parent
29        onClicked: { chartA.color = "blue" }
30    }
31
32    Text {
33        anchors {
34            bottom: parent.bottom;
35            horizontalCenter: parent.horizontalCenter;
36            bottomMargin: 20
37        }
38        text: "Click anywhere to change the chart color"
39    }
40}

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

color 属性启用属性绑定很容易。我们在其 Property 装饰器中添加一个 notify 参数,以指示每当值发生变化时都会发出 colorChanged 信号。

39    @Property(QColor, notify=colorChanged, final=True)
21@QmlElement
22class PieChart (QQuickPaintedItem):
23
24    chartCleared = Signal()
25    nameChanged = Signal()
26    colorChanged = Signal()

然后,我们在 setColor() 中发出这个信号

43    @color.setter
44    def color(self, value):
45        if value != self._color:
46            self._color = value
47            self.update()
48            self.colorChanged.emit()

在发出 colorChanged(). 之前,确保 setColor() 检查颜色值是否实际上发生了变化。这确保信号不会被无谓地发出,并且也防止其他类型响应值变化时的循环。

绑定在 QML 中使用是必不可少的。如果您能实现,应始终为属性添加 notify 信号,以便您的属性可以在绑定中使用。无法绑定的属性不能自动更新,并且不能在 QML 中灵活使用。此外,由于绑定在 QML 使用中频繁调用,并且被视为依赖,因此如果未实现绑定,您的自定义 QML 类型的用户可能会看到意外的行为。

下载示例

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

    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"
    }
}
# 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/chapter3-bindings example from Qt v5.x"""

import os
from pathlib import Path
import sys

from PySide6.QtCore import Property, Signal, Slot, QUrl, Qt
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):

    chartCleared = Signal()
    nameChanged = Signal()
    colorChanged = 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, notify=colorChanged, final=True)
    def color(self):
        return self._color

    @color.setter
    def color(self, value):
        if value != self._color:
            self._color = value
            self.update()
            self.colorChanged.emit()

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

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

    @Slot()  # This should be something like @Invokable
    def clearChart(self):
        self.color = Qt.transparent
        self.update()
        self.chartCleared.emit()


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)