Extending QML - Creating a New Type#
This is the first of a series of 6 examples forming a tutorial about extending QML with Python.
The Qt QML module provides a set of APIs for extending QML through Python extensions. You can write extensions to add your own QML types, extend existing Qt types, or call Python functions that are not accessible from ordinary QML code.
This tutorial shows how to write a QML extension using Python that includes core QML features, including properties, signals and bindings. It also shows how extensions can be deployed through plugins.
A common task when extending QML is to provide a new QML type that supports some custom functionality beyond what is provided by the built-in Qt Quick types. For example, this could be done to implement particular data models, or provide types with custom painting and drawing capabilities, or access system features like network programming that are not accessible through built-in QML features.
In this tutorial, we will show how to use the C++ classes in the Qt Quick module to extend QML. The end result will be a simple Pie Chart display implemented by several custom QML types connected together through QML features like bindings and signals, and made available to the QML runtime through a plugin.
To begin with, let’s create a new QML type called PieChart
that has two
properties: a name and a color. We will make it available in an importable type
namespace called Charts
, with a version of 1.0.
We want this PieChart
type to be usable from QML like this:
import Charts 1.0
PieChart {
width: 100; height: 100
name: "A simple pie chart"
color: "red"
}
To do this, we need a C++ class that encapsulates this PieChart
type and
its two properties. Since QML makes extensive use of Qt’s Meta-Object System
this new class must:
Inherit from
QObject
Declare its properties using the
Property
decorator
Class Implementation#
Here is our PieChart
class, defined in 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
The class inherits from QQuickPaintedItem
because we want to override
QQuickPaintedItem.paint()
to perform drawing operations with the
QPainter
API. If the class just represented some data type and was not an
item that actually needed to be displayed, it could simply inherit from
QObject
. Or, if we want to extend the functionality of an existing
QObject
-based class, it could inherit from that class instead.
Alternatively, if we want to create a visual item that doesn’t need to perform
drawing operations with the QPainter
API, we can just subclass
QQuickItem
.
The PieChart
class defines the two properties, name
and color
, with
the Property
decorator, and overrides QQuickPaintedItem.paint()
. The
PieChart
class is registered using the QmlElement
decorator, to allow
it to be used from QML. If you don’t register the class, app.qml
won’t be
able to create a PieChart
.
QML Usage#
Now that we have defined the PieChart
type, we will use it from QML. The
app.qml
file creates a PieChart
item and displays the pie chart’s details
using a standard QML Text
item:
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}
Notice that although the color is specified as a string in QML, it is
automatically converted to a QColor
object for the PieChart color
property. Automatic conversions are provided for various other QML value types.
For example, a string like “640x480” can be automatically converted to a
QSize
value.
We’ll also create a main function that uses a QQuickView
to run and display
app.qml
. Here is the application 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)
Note
You may see a warning Expression … depends on non-NOTIFYable properties:
PieChart.name. This happens because we add a binding to the writable name
property, but haven’t yet defined a notify signal for it. The QML engine therefore
cannot update the binding if the name
value changes. This is addressed in
the following chapters.
# 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
}
}