Extending QML - Binding Example#

This example builds on the Extending QML - Adding Types Example, the Extending QML - Attached Properties Example, the Extending QML - Default Property Example, the Extending QML - Inheritance and Coercion Example the Extending QML - Object and List Property Types Example and the Extending QML - Value Source Example.

Running the Example#

The main.py file in the example includes a simple shell application that loads and runs the QML snippet shown below.

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

"""PySide6 port of the qml/examples/qml/referenceexamples/binding example from Qt v6.x"""

from pathlib import Path
import sys

from PySide6.QtCore import QCoreApplication, QUrl
from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject

from person import Boy, Girl
from birthdayparty import BirthdayParty
from happybirthdaysong import HappyBirthdaySong


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)
    qml_file = Path(__file__).parent / "example.qml"
    url = QUrl.fromLocalFile(qml_file)
    engine = QQmlEngine()
    component = QQmlComponent(engine, url)
    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()):
        guest = party.guest(g)
        name = guest.name

        rsvp_date = None
        attached = qmlAttachedPropertiesObject(BirthdayParty, guest, False)
        if attached:
            rsvp_date = attached.rsvp.toString()
        if rsvp_date:
            print(f"    {name} RSVP date: {rsvp_date}")
        else:
            print(f"    {name} RSVP date: Hasn't RSVP'd")

    party.startParty()

    r = app.exec()

    del engine
    sys.exit(r)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import QDate, QObject, ClassInfo, Property, QTime, Signal
from PySide6.QtQml import QmlAnonymous, QmlAttached, QmlElement, ListProperty

from person import Person


# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "examples.binding.people"
QML_IMPORT_MAJOR_VERSION = 1


@QmlAnonymous
class BirthdayPartyAttached(QObject):

    rsvp_changed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._rsvp = QDate()

    @Property(QDate, notify=rsvp_changed)
    def rsvp(self):
        return self._rsvp

    @rsvp.setter
    def rsvp(self, d):
        if self._rsvp != d:
            self._rsvp = d
            self.rsvp_changed.emit()


@QmlElement
@ClassInfo(DefaultProperty="guests")
@QmlAttached(BirthdayPartyAttached)
class BirthdayParty(QObject):

    partyStarted = Signal(QTime)
    host_changed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._host = None
        self._guests = []

    def startParty(self):
        self.partyStarted.emit(QTime.currentTime())

    @Property(Person, notify=host_changed)
    def host(self):
        return self._host

    @host.setter
    def host(self, h):
        if self._host != h:
            self._host = h
            self.host_changed.emit()

    @Property(str)
    def announcement(self):
        return ""

    @announcement.setter
    def announcement(self, a):
        print(a)

    def guest(self, n):
        return self._guests[n]

    def guestCount(self):
        return len(self._guests)

    def appendGuest(self, guest):
        self._guests.append(guest)

    @staticmethod
    def qmlAttachedProperties(self, o):
        return BirthdayPartyAttached(o)

    guests = ListProperty(Person, appendGuest)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import QObject, QTimer, Property, Slot
from PySide6.QtQml import QmlElement, QPyQmlPropertyValueSource

# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "examples.binding.people"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class HappyBirthdaySong(QPyQmlPropertyValueSource):

    def __init__(self, parent=None):
        super().__init__(parent)

        self.m_target = None
        self.m_name = ""
        self.m_line = -1
        self.m_lyrics = []

        self.m_timer = QTimer(self)
        self.m_timer.timeout.connect(self.advance)
        self.m_timer.start(1000)

    def setTarget(self, property):
        self.m_target = property

    @Property(str)
    def name(self):
        return self.m_name

    @name.setter
    def name(self, n):
        self.m_name = n
        self.m_lyrics = ["Happy birthday to you,",
                         "Happy birthday to you,",
                         f"Happy birthday dear {self.m_name},",
                         "Happy birthday to you!",
                         ""]

    @Slot()
    def advance(self):
        self.m_line = (self.m_line + 1) % len(self.m_lyrics)
        self.m_target.write(self.m_lyrics[self.m_line])
# 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 QmlAnonymous, QmlElement

# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "examples.binding.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)
    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)
    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()


@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) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import examples.binding.people

BirthdayParty {
    id: theParty

    HappyBirthdaySong on announcement { name: theParty.host.name }

    onPartyStarted: (time) => { console.log("This party started rockin' at " + time); }

    host: Boy {
        name: "Bob Jones"
        shoe_size: 12
    }

    Boy {
        name: "Leo Hodges"
        BirthdayParty.rsvp: "2009-07-06"
    }
    Boy {
        name: "Jack Smith"
    }
    Girl {
        name: "Anne Brown"
        BirthdayParty.rsvp: "2009-07-01"
    }
}