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

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.

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

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
from __future__ import annotations

"""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 __future__ import annotations

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 __future__ import annotations

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