Extending QML - Connecting to C++ Methods and Signals

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

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:

 4import Charts
 5import QtQuick
 6
 7Item {
 8    width: 300; height: 200
 9
10    PieChart {
11        id: aPieChart
12        anchors.centerIn: parent
13        width: 100; height: 100
14        color: "red"
15
16        onChartCleared: console.log("The chart has been cleared")
17    }
18
19    MouseArea {
20        anchors.fill: parent
21        onClicked: aPieChart.clearChart()
22    }
23
24    Text {
25        anchors {
26            bottom: parent.bottom;
27            horizontalCenter: parent.horizontalCenter;
28            bottomMargin: 20
29        }
30        text: "Click anywhere to clear the chart"
31    }
32}

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

54
55    @Slot()  # This should be something like @Invokable
56    def clearChart(self):
57        self.color = Qt.transparent
58        self.update()

The use of @Slot makes the clearChart() method available to the Qt Meta-Object system, and in turn, to QML. The method simply changes the color to Qt::transparent, repaints the chart, then emits the chartCleared() signal:

21
22@QmlElement
23class PieChart(QQuickPaintedItem):
24

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

qml: The chart has been cleared

Download this example

# 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/chapter2-methods example from Qt v5.x"""

import os
from pathlib import Path
import sys

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

    chartCleared = Signal()
    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

    @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.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
        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"
    }
}