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}

Download this example

# 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
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