Search code examples
pythonqmlpyside2

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

  • 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
        }
    }