Extending QML (advanced) - BirthdayParty Base Project#

This is the first 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. The code for the various features explained below is based on this birthday party project and relies on some of the material in the basic tutorial. This simple example is then expanded upon to illustrate the various QML extensions explained below. The complete code for each new extension to the code can be found at the end of the respective page.

The base project defines the Person class and the BirthdayParty class, which model the attendees and the party itself respectively.

13@QmlElement
14class Person(QObject):
15    name_changed = Signal()
16    shoe_size_changed = Signal()
17
18    def __init__(self, parent=None):
19        super().__init__(parent)
20        self._name = ''
21        self._shoe_size = 0
22
23    @Property(str, notify=name_changed, final=True)
24    def name(self):
25        return self._name
26
27    @name.setter
28    def name(self, n):
29        if self._name != n:
30            self._name = n
31            self.name_changed.emit()
32
33    @Property(int, notify=shoe_size_changed, final=True)
34    def shoe_size(self):
35        return self._shoe_size
36
37    @shoe_size.setter
38    def shoe_size(self, s):
39        if self._shoe_size != s:
40            self._shoe_size = s
41            self.shoe_size_changed.emit()
16@QmlElement
17class BirthdayParty(QObject):
18    host_changed = Signal()
19    guests_changed = Signal()
20
21    def __init__(self, parent=None):
22        super().__init__(parent)
23        self._host = None
24        self._guests = []
25
26    @Property(Person, notify=host_changed, final=True)
27    def host(self):
28        return self._host
29
30    @host.setter
31    def host(self, h):
32        if self._host != h:
33            self._host = h
34            self.host_changed.emit()
35
36    def guest(self, n):
37        return self._guests[n]
38
39    def guestCount(self):
40        return len(self._guests)
41
42    def appendGuest(self, guest):
43        self._guests.append(guest)
44        self.guests_changed.emit()
45
46    guests = ListProperty(Person, appendGuest, notify=guests_changed, final=True)

All the information about the party can then be stored in the corresponding QML file.

 4import People
 5
 6BirthdayParty {
 7    host: Person {
 8        name: "Bob Jones"
 9        shoe_size: 12
10    }
11    guests: [
12        Person { name: "Leo Hodges" },
13        Person { name: "Jack Smith" },
14        Person { name: "Anne Brown" }
15    ]
16}

The main.py file creates a simple shell application that displays whose birthday it is and who is invited to their party.

17app = QCoreApplication(sys.argv)
18engine = QQmlEngine()
19engine.addImportPath(Path(__file__).parent)
20component = QQmlComponent(engine)
21component.loadFromModule("People", "Main")

The app outputs the following summary of the party:

"Bob Jones" is having a birthday!
They are inviting:
    "Leo Hodges"
    "Jack Smith"
    "Anne Brown"

Outlook#

The following sections go into how to add support for Boy and Girl attendees instead of just Person by using inheritance and coercion, how to make use of default properties to implicitly assign attendees of the party as guests, how to assign properties as groups instead of one by one, how to use attached objects to keep track of invited guests’ reponses, how to use a property value source to display the lyrics of the happy birthday song over time, and how to expose third party objects to QML.

Download this example

# Copyright (C) 2023 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/advanced1-Base-project 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 Person  # 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!\nThey are 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) 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

# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "People"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
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):
        if self._shoe_size != s:
            self._shoe_size = s
            self.shoe_size_changed.emit()
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import People

BirthdayParty {
    host: Person {
        name: "Bob Jones"
        shoe_size: 12
    }
    guests: [
        Person { name: "Leo Hodges" },
        Person { name: "Jack Smith" },
        Person { name: "Anne Brown" }
    ]
}
module People
typeinfo coercion.qmltypes
Main 1.0 Main.qml