'How to pass __init__ arguments to class registered with qmlRegisterType?

Is it possible to pass init arguments to a python class registered to QML using the qmlRegisterType function? If so, would they be passed when the class is instantiated within QML?

I've registered the class with qmlRegisterType, but don't see a way to pass in another class instance as an object. I do see there is a way to register extension objects, but per the documentation, those can only be properties. I'd like to pass in another instantiated class so that I can access its methods and properties within the class I am registering to QML.

How the class would be instantiated in Python:

from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType, QQmlComponent
from PySide2.QtCore import QObject
from app import Sites
from models import RoutesConn

def main():
    # create the application instance
    app = QApplication(sys.argv)

    # create a QML engine
    engine = QQmlApplicationEngine()

    # instantiate Sites instance
    sites = Sites()
    # instantiate RoutesConn instance, and pass in sites instance
    routesconn = RoutesConn(sites)
    # this could be provided to qml as an object
    engine.rootContext().setContextProperty('RoutesConn', routesconn)

However, the sites instance can't be passed if registered as a class in qml. I would think the sites class could also be registered to qml and passed to RoutesConn when instantiated in QML, but I haven't seen a way to do that.

Register class to QML in Python:

qmlRegisterType(RoutesConn, 'RoutesConn', 1, 0, 'RoutesConn')

QML:

import RoutesConn 1.0

RoutesConn {
    id: rconn

    ....
}

I would expect there were a way to pass objects into the class during initialization after registration to qml, but that doesn't appear to be the case.



Solution 1:[1]

TL; DR; No, it is not possible.


QML expects that the QObjects registered through qmlRegisterType only have a constructor that receives a QObject as parent (qmlRegisterType does not build the object, only makes it available in QML):

class Foo(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        # ...

If you want to pass other objects you must do it through a Slot or Property:

  • Call the Slot in Component.Completed:
from PySide2 import QtCore, QtGui, QtQml


class Bar(QtCore.QObject):
    def test(self):
        print("test")


class Foo(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.m_bar = None

    @QtCore.Slot(Bar)
    def load_bar(self, bar):
        self.m_bar = bar


    @QtCore.Slot()
    def test_bar(self):
        if self.m_bar is not None:
            self.m_bar.test()


if __name__ == "__main__":
    import os
    import sys

    app = QtGui.QGuiApplication(sys.argv)
    QtQml.qmlRegisterType(Foo, 'TestComponents', 1, 0, 'Foo')
    QtQml.qmlRegisterType(Bar, 'TestComponents', 1, 0, 'Bar')
    engine = QtQml.QQmlApplicationEngine()
    file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
    engine.load(file)
    if not engine.rootObjects():
        sys.exit(-2)
    sys.exit(app.exec_())
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.5

import TestComponents 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    color: "whitesmoke"
    Foo{
        id: foo
        Component.onCompleted: load_bar(bar)
    }
    Bar{
        id: bar
    }
    Button {
        text: "Cancel"
        onClicked: foo.test_bar()
        anchors.centerIn: parent
    }
}
  • Property
from PySide2 import QtCore, QtGui, QtQml


class Bar(QtCore.QObject):
    def test(self):
        print("test")


class Foo(QtCore.QObject):
    barChanged = QtCore.Signal()

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

    def getBar(self):
        return self.m_bar

    def setBar(self, bar):
        if self.m_bar != bar:
            self.m_bar = bar
            self.barChanged.emit()

    bar = QtCore.Property(Bar, fget=getBar, fset=setBar, notify=barChanged)

    @QtCore.Slot()
    def test_bar(self):
        if self.m_bar is not None:
            self.m_bar.test()


if __name__ == "__main__":
    import os
    import sys

    app = QtGui.QGuiApplication(sys.argv)
    QtQml.qmlRegisterType(Foo, "TestComponents", 1, 0, "Foo")
    QtQml.qmlRegisterType(Bar, "TestComponents", 1, 0, "Bar")
    engine = QtQml.QQmlApplicationEngine()
    file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
    engine.load(file)
    if not engine.rootObjects():
        sys.exit(-2)
    sys.exit(app.exec_())
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.5

import TestComponents 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    color: "whitesmoke"
    Foo{
        id: foo
        bar: bar_object 
    }
    Bar{
        id: bar_object
    }
    Button {
        text: "Cancel"
        onClicked: foo.test_bar()
        anchors.centerIn: parent
    }
}

Solution 2:[2]

There is however a clean solution to this problem. I am using PySide6.

In QML:

MyPyObject {
    custom_property: "this gets passed from qml to python"
}

In Python:

from pathlib import Path

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

QML_IMPORT_NAME = "pyobjects"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class MyPyObject(QObject):

    def __init__(self):
        super().__init__()
        self._custom_property = None

    def get_custom_property(self) -> str:
        return self._custom_property

    def set_custom_property(self, value: str) -> None:
        self._custom_property = value
        self.custom_property_changed.emit(value)

    custom_property_changed = Signal(str)
    custom_property = Property(str, get_custom_property, set_custom_property, notify=custom_property_changed)

Keep in mind that those 'passed in' properties are not available in the Python class constructor.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 eyllanesc
Solution 2 trin94