1

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

spooky
  • 75
  • 1
  • 6

1 Answers1

3

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thank you very much. That is exactly what I'm looking for. One additional question: Is it good practice to let python load the QML-Views (I'm going for sort of an MVC/MVVM pattern), or should I somehow delegate it to QML. Is there an easier way to identify elements in my view? (Currently using objectName Property and findChild-Method). Thanks again – spooky Mar 22 '18 at 01:21
  • @spooky Actually what is recommended is not to create components from python/C++, but to create new types of items, or properties. For example, create the business logic in python/C++ and inject it into the QML, let the main task of the GUI do it QML. The other question, access by objectName and findChildren is the best way to access the elements. – eyllanesc Mar 22 '18 at 01:25
  • I might be missing something here. I have seen that I can register my own types (via qmlRegisterType) and that if I use it a new Object of my QObject (with pyqtProperty) is created. However, my business logic should still decide which (and when) a particular view is created. Then I'm still left with loading qml files or types from python. Maybe you have a link/tutorial that I could take a look at? – spooky Mar 22 '18 at 02:45
  • I recommend you not mix the .py with the .qml much, create an object made in python but manage the visibility in the QML. I mean try to do most things in the QML, and if you can not do it just in the .py. To load new components there are Loaders or Component. QML is prepared to do most things, in fact many QML things are written in python. – eyllanesc Mar 22 '18 at 02:51