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 os
import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
from person import Boy, Girl
from birthdayparty import BirthdayParty
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