spooky
spooky

Reputation: 75

Injecting Properties into QML-Component

I am fairly new to Qt/PyQt and currently struggling with some basic functionality. What I'm trying to do is, to dynamically load QML-Views (*.qml) files from python and replace specific content on the fly. For example a checkbox gets checked and part of my current view is replaced with another qml file. First I wanted to provide this logic via PyQt, but it seems a StackView is a better idea (multiple qml files in pyqt).

However, in this case I am not able to inject properties into my QML files. I am only able to inject a property into the rootContext. That however limits the usage of my QML-Views since I can only use one view (of the same type) at once. I would like to inject properties dynamically into QML-Views and make them only visible to this particular view. In this case I can use the same view more than once with more than one object in the back-end to catch the signals.

Here is my SimpleMainWindow.qml file (the main view:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4

ApplicationWindow {
    id: window
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    objectName : "WINDOW"
    property ApplicationWindow appWindow : window

}

And here the file I try to load (TestViewButton.qml):

import QtQuick 2.9
import QtQuick.Controls 1.4

Button {
    id: test
    text: "test"
    objectName : "Button"
    onClicked: {
        configurator.sum(1, 2)
        configurator.info("TestOutput")
    }
}

Python file loading QML-View (Component)

from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine, QQmlComponent

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

    engine.load("qml/SimpleMainWindow.qml")
    engine.quit.connect(app.quit)

    rootWindow = engine.rootObjects()[0].children()[0].parent()

    # create component
    component = QQmlComponent(engine)
    component.loadUrl(QUrl("qml/TestViewButton.qml"))

    configurator = SignalHandler()
    component.setProperty("configurator", configurator)

    itm = component.create()
    itm.setParentItem(rootWindow.children()[0])

    itm.setProperty("configurator", configurator)

    app.exec_()

And the python object that I use to handle the signals from the view (SignalHandler.py):

from PyQt5.QtCore import QObject, pyqtSlot

class SignalHandler(QObject):

    @pyqtSlot(int, int)
    def sum(self, arg1, arg2):
        print("Adding two numbers")

    @pyqtSlot(str)
    def info(self, arg1):
        print("STR " + arg1)

The button loads fine (by the way, is there a better way to identify the parent I want to add my button to, wasn't having any look with findChild). What is not working is the component.setProperty.... part. If I set the property in the rootContext of the engine it works fine (the SignalHandler methods are called). Already checked similar topics (like Load a qml component from another qml file dynamically and access that component's qml type properties ...)

Is this possible, or am I getting something wrong here?

thanks

Upvotes: 1

Views: 1644

Answers (1)

eyllanesc
eyllanesc

Reputation: 244252

From what I understand, you want to load the configuration object only in TestViewButton.qml and it is not visible in SimpleMainWindow.qml.

To do this TestViewButton.qml must have its own QQmlContext when it is loaded and is not the rootContext().

To test my response and observe that behavior we will create a similar button that tries to use the configurator, if this is pressed it should throw an error noting that the property does not exist but if the button loaded is pressed by the QQmlComponent should do its job normally.

qml/SimpleMainWindow.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4

ApplicationWindow {
    id: window
    visible: true
    width: 640
    color: "red"
    height: 480
    title: qsTr("Hello World")

    Button {
        x: 100
        y: 100
        text: "ApplicationWindow"
        onClicked: {
            console.log("ApplicationWindow")
            configurator.sum(1, 2)
            configurator.info("TestOutput")
        }
    }
}

As I commented previously I added the component with a new context:

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

    configurator = SignalHandler()

    engine.load("qml/SimpleMainWindow.qml")
    engine.quit.connect(app.quit)

    rootWindow = engine.rootObjects()[0]
    content_item = rootWindow.property("contentItem")

    context = QQmlContext(engine)
    component = QQmlComponent(engine)
    component.loadUrl(QUrl("qml/TestViewButton.qml"))
    itm = component.create(context)
    context.setContextProperty("configurator", configurator)
    itm.setProperty("parent", content_item)

    sys.exit(app.exec_())

At the end we get the following output:

qml: Component
Adding two numbers
STR TestOutput
qml: ApplicationWindow
file:///xxx/qml/SimpleMainWindow.qml:20: ReferenceError: configurator is not defined
qml: Component
Adding two numbers
STR TestOutput
qml: ApplicationWindow
file:///xxx/qml/SimpleMainWindow.qml:20: ReferenceError: configurator is not defined

Where we observe the desired behavior. The complete example can be found in the following link.

Upvotes: 3

Related Questions