Extending QML - Using Custom Property Types#

This is the fourth of a series of 6 examples forming a tutorial about extending QML with Python.

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:

class PieChart(QQuickPaintedItem):
    chartIdChanged = Signal()

    @Property(int, notify=chartIdChanged)
    def chartId(self):
        pass

    @chartId.setter
    def setChartId(self, chartId):
        pass
// QML
PieChart {
    ...
    chartId: 100
}

Aside from int, we could use various other property types. Many of the Qt data types such as QColor, QSize and QRect are automatically supported from QML.

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.

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:

 4import Charts
 5import QtQuick
 6
 7Item {
 8    width: 300; height: 200
 9
10    PieChart {
11        id: chart
12        anchors.centerIn: parent
13        width: 100; height: 100
14
15        pieSlice: PieSlice {
16            anchors.fill: parent
17            color: "red"
18        }
19    }
20
21    Component.onCompleted: console.log("The pie is colored " + chart.pieSlice.color)
22}

Like PieChart, this new PieSlice type inherits from QQuickPaintedItem, is exposed via the QmlElement decorator and declares its properties with the Property decorator:

21@QmlElement
22class PieSlice (QQuickPaintedItem):
23
24    def __init__(self, parent=None):
25        QQuickPaintedItem.__init__(self, parent)
26        self._color = QColor()
27
28    @Property(QColor, final=True)
29    def color(self):
30        return self._color
31
32    @color.setter
33    def color(self, value):
34        self._color = value
35
36    def paint(self, painter):
37        pen = QPen(self._color, 2)
38        painter.setPen(pen)
39        painter.setRenderHints(QPainter.Antialiasing, True)
40        painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16)

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

58    @Property(PieSlice, final=True)
59    def pieSlice(self):
60        return self._pieSlice
61
62    @pieSlice.setter
63    def pieSlice(self, value):
64        self._pieSlice = value
65        self._pieSlice.setParentItem(self)

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.

As with PieChart, we add the Charts type namespace, version 1.0:

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

Download this example

// 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: 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)
}
# 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/chapter4-customPropertyTypes example from Qt v5.x"""

import os
from pathlib import Path
import sys

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

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


@QmlElement
class PieSlice (QQuickPaintedItem):

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

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

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

    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)


@QmlElement
class PieChart (QQuickItem):
    def __init__(self, parent=None):
        QQuickItem.__init__(self, parent)
        self._name = None
        self._pieSlice = None

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

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

    @Property(PieSlice, final=True)
    def pieSlice(self):
        return self._pieSlice

    @pieSlice.setter
    def pieSlice(self, value):
        self._pieSlice = value
        self._pieSlice.setParentItem(self)


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)