Extending QML (advanced) - Inheritance and Coercion#
This is the second of a series of 6 examples forming a tutorial using the example of a birthday party to demonstrate some of the advanced features of QML.
Right now, each attendant is being modelled as a person. This is a bit too generic and it would be nice to be able to know more about the attendees. By specializing them as boys and girls, we can already get a better idea of who’s coming.
To do this, the Boy
and Girl
classes are introduced, both inheriting from
Person
.
43@QmlElement
44class Boy(Person):
45 def __init__(self, parent=None):
46 super().__init__(parent)
49@QmlElement
50class Girl(Person):
51 def __init__(self, parent=None):
52 super().__init__(parent)
The Person
class remains unaltered and the Boy
and Girl
classes are
trivial extensions of it. The types and their QML name are registered with the
QML engine with @QmlElement
.
Notice that the host
and guests
properties in BirthdayParty
still
take instances of Person
.
26 @Property(Person, notify=host_changed, final=True)
46 guests = ListProperty(Person, appendGuest, notify=guests_changed, final=True)
The implementation of the Person
class itself has not been changed.
However, as the Person
class was repurposed as a common base for Boy
and Girl
, Person
should no longer be instantiable from QML directly. An
explicit Boy
or Girl
should be instantiated instead.
13@QmlElement
14@QmlUncreatable("Person is an abstract base class.")
15class Person(QObject):
While we want to disallow instantiating Person
from within QML, it still
needs to be registered with the QML engine so that it can be used as a property
type and other types can be coerced to it. This is what the @QmlUncreatable
macro does. As all three types, Person
, Boy
and Girl
, have been
registered with the QML system, on assignment, QML automatically (and
type-safely) converts the Boy
and Girl
objects into a Person
.
With these changes in place, we can now specify the birthday party with the extra information about the attendees as follows.
6BirthdayParty {
7 host: Boy {
8 name: "Bob Jones"
9 shoe_size: 12
10 }
11 guests: [
12 Boy { name: "Leo Hodges" },
13 Boy { name: "Jack Smith" },
14 Girl { name: "Anne Brown" }
15 ]
16}
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 port of the
qml/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion example
from Qt v6.x"""
from pathlib import Path
import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
from person import Boy, Girl # noqa: F401
from birthdayparty import BirthdayParty # noqa: F401
app = QCoreApplication(sys.argv)
engine = QQmlEngine()
engine.addImportPath(Path(__file__).parent)
component = QQmlComponent(engine)
component.loadFromModule("People", "Main")
party = component.create()
if not party:
print(component.errors())
del engine
sys.exit(-1)
host = party.host
print(f"{host.name} is having a birthday!")
if isinstance(host, Boy):
print("He is inviting:")
else:
print("She is inviting:")
for g in range(party.guestCount()):
name = party.guest(g).name
print(f" {name}")
del engine
sys.exit(0)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtCore import QObject, Property, Signal
from PySide6.QtQml import QmlElement, ListProperty
from person import Person
# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "People"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
class BirthdayParty(QObject):
host_changed = Signal()
guests_changed = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._host = None
self._guests = []
@Property(Person, notify=host_changed, final=True)
def host(self):
return self._host
@host.setter
def host(self, h):
if self._host != h:
self._host = h
self.host_changed.emit()
def guest(self, n):
return self._guests[n]
def guestCount(self):
return len(self._guests)
def appendGuest(self, guest):
self._guests.append(guest)
self.guests_changed.emit()
guests = ListProperty(Person, appendGuest, notify=guests_changed, final=True)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtCore import QObject, Property, Signal
from PySide6.QtQml import QmlElement, QmlUncreatable
# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "People"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
@QmlUncreatable("Person is an abstract base class.")
class Person(QObject):
name_changed = Signal()
shoe_size_changed = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._name = ''
self._shoe_size = 0
@Property(str, notify=name_changed, final=True)
def name(self):
return self._name
@name.setter
def name(self, n):
if self._name != n:
self._name = n
self.name_changed.emit()
@Property(int, notify=shoe_size_changed, final=True)
def shoe_size(self):
return self._shoe_size
@shoe_size.setter
def shoe_size(self, s):
self._shoe_size = s
@QmlElement
class Boy(Person):
def __init__(self, parent=None):
super().__init__(parent)
@QmlElement
class Girl(Person):
def __init__(self, parent=None):
super().__init__(parent)
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import People
BirthdayParty {
host: Boy {
name: "Bob Jones"
shoe_size: 12
}
guests: [
Boy { name: "Leo Hodges" },
Boy { name: "Jack Smith" },
Girl { name: "Anne Brown" }
]
}
module People
typeinfo coercion.qmltypes
Main 1.0 Main.qml