Search code examples
pythonqtqmlpyside

Use Properties declared in separate Python file in QML


I want to keep order in my project, hence I thought about moving the properties from the "main.py" file to another called "CustomVariables.py". But now I get "TypeError: Cannot read property 'loaderSource' of undefined". When you uncomment the comments in the code below, it works OK, however I don't want to declare properties inside "BG" class. Can anyone guide me how to properly set this up?

CustomVariables.py

from PySide6.QtCore import QObject, Property, Signal

class CustomVariables(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.loaderSource = 'PageWork.qml'

    def get_loaderSource(self):
        return self._loaderSource
    
    def set_loaderSource(self, new_loaderSource):
        self._loaderSource = new_loaderSource
        self.loaderSourceChanged.emit()
        
    loaderSourceChanged = Signal()
    
    loaderSource = Property(str, get_loaderSource, set_loaderSource, notify=loaderSourceChanged)

main.py

import sys
from pathlib import Path

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, Property, Signal

from CustomVariables import CustomVariables



class BG(QObject):
    def __init__(self):
        QObject.__init__(self)
        # self.loaderSource = 'PageWork.qml'

    cv = CustomVariables()

    # def get_loaderSource(self):
    #     return self._loaderSource
    # def set_loaderSource(self, new_loaderSource):
    #     self._loaderSource = new_loaderSource
    #     self.loaderSourceChanged.emit()
    # loaderSourceChanged = Signal()
    # loaderSource = Property(str, get_loaderSource, set_loaderSource, notify=loaderSourceChanged)


if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    background = BG()
    engine.rootContext().setContextProperty("bg", background)

    qml_file = Path(__file__).resolve().parent / "main.qml"
    engine.load(qml_file)
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec())

main.qml

import QtQuick.Controls.FluentWinUI3
import QtQuick


Window {
    id: root
    width: 1280
    height: 720
    visible: true

    Loader {
        id: projectLoader
        anchors.fill: parent
        asynchronous: true
        source: bg.cv.loaderSource
        //source: bg.loaderSource
    }
}

I tried also to declare the "cv" inside "init":

self.cv = CustomVariables()

but still without success


Solution

  • QML doesn't know about dependency relationships between objects unless you set them with QProperties. Python class relationships don't come into play in QML since it doesn't have introspection of those objects. In your case cv has to be a constant QProperty, meaning that the reference to the cv object does not change.

    Another mistake of yours is that _loaderSource is not initialized so it will return an error.

    class CustomVariables(QObject):
        def __init__(self):
            QObject.__init__(self)
            self._loaderSource = "PageWork.qml"
    
        def get_loaderSource(self):
            return self._loaderSource
    
        def set_loaderSource(self, new_loaderSource):
            self._loaderSource = new_loaderSource
            self.loaderSourceChanged.emit()
    
        loaderSourceChanged = Signal()
    
        loaderSource = Property(
            str, get_loaderSource, set_loaderSource, notify=loaderSourceChanged
        )
    

    class BG(QObject):
        def __init__(self):
            QObject.__init__(self)
            self._cv = CustomVariables()
    
        @Property(QObject, constant=True)
        def cv(self):
            return self._cv