Extending QML (advanced) - Default Properties

This is the third 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.

Currently, in the QML file, each property is assigned explicitly. For example, the host property is assigned a Boy and the guests property is assigned a list of Boy or Girl. This is easy but it can be made a bit simpler for this specific use case. Instead of assigning the guests property explicitly, we can add Boy and Girl objects inside the party directly and have them assigned to guests implicitly. It makes sense that all the attendees that we specify, and that are not the host, are guests. This change is purely syntactical but it can add a more natural feel in many situations.

The guests property can be designated as the default property of BirthdayParty. Meaning that each object created inside of a BirthdayParty is implicitly appended to the default property guests. The resulting QML looks like this.

 6BirthdayParty {
 7    host: Boy {
 8        name: "Bob Jones"
 9        shoe_size: 12
10    }
11
12    Boy { name: "Leo Hodges" }
13    Boy { name: "Jack Smith" }
14    Girl { name: "Anne Brown" }
15}

The only change required to enable this behavior is to add the DefaultProperty class info annotation to BirthdayParty to designate guests as its default property.

16
17@QmlElement
18@ClassInfo(DefaultProperty="guests")

You may already be familiar with this mechanism. The default property for all descendants of Item in QML is the data property. All elements not explicitly added to a property of an Item will be added to data. This makes the structure clear and reduces unnecessary noise in the code.

Download this example

# Copyright (C) 2022 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/default advanced3-Default-properties 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) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import QObject, ClassInfo, 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
@ClassInfo(DefaultProperty="guests")
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 __future__ import annotations

from PySide6.QtCore import QObject, Property, Signal
from PySide6.QtQml import QmlAnonymous, QmlElement

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


@QmlAnonymous
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
    }

    Boy { name: "Leo Hodges" }
    Boy { name: "Jack Smith" }
    Girl { name: "Anne Brown" }
}
module People
typeinfo coercion.qmltypes
Main 1.0 Main.qml