Properly reloading a QQmlApplicationEngine
Asked Answered
E

3

6

I have a QML-based app that loads a main.qml file from the file system like this:

myEngine->load("main.qml");

This works fine, but I'd like to "reload" the engine in case the main.qml was replaced with a newer version.

What I tried so far was calling load() again, assuming that the engine will automatically reset itself like in other Qt classes.

Unfortunately this is not the case. If I call the method again, another window will appear with the contents of the updated qml file, while the original window stays open and continues to display the old qml file.

To fix this I tried to call load(QUrl()), followed by clearComponentCache() and a final load call for the new file. This results in the same effect.

Any ideas how I can "properly" reload a QML engine while the application is running?

Exterminatory answered 12/5, 2015 at 16:5 Comment(1)
clearComponentCache() looks like it's only related to components, and hence doesn't account for instances of those components.Skilken
S
5

I would try storing myEngine as a pointer on the heap, and deleting it after calling quit(). Then you can reconstruct it to get the new version of the QML file.

If you don't want to do this for some reason (e.g. because you want to keep the window around or whatever), you might try using a Loader and loading the QML file that way. Your main.qml would then look something like this:

import QtQuick 2.0

Loader {
     source: "changing.qml"
}

Whenever you detect that changing.qml has changed, just toggle the active property of Loader to trigger a reload of the file.

Skilken answered 12/5, 2015 at 16:35 Comment(2)
Duh. I guess I should pack up and go home. It's so obvious it hurts. Thanks! Here, have some rep points. :)Exterminatory
I was not able to do it in any way by using pointer and quit, I would call quit and the connect to the quit signal, trying to delete it would always result in crash, tried disconnect(), deleteLater() but to no avail, loader solution works perfectlyDoornail
A
6

Just saw this, but if you're still trying to figure this out - it's a three step process, and you have some of it.

  1. You MUST close window created by the QQmlApplicationEngine first. In my case I pulled the first root object off the QQmlApplicationEngine and cast it to QQuickWindow, then call close().

  2. Now you can call clearComponentCache on the QQmlApplicationEngine.

This is what my window closing code does (note that I gave my main window an objectName)

QObject* pRootObject = in_pQmlApplicationEngine->rootObjects().first();
Q_ASSERT( pRootObject != NULL );
Q_ASSERT( pRootObject->objectName() == "mainWindow" );

QQuickWindow* pMainWindow = qobject_cast<QQuickWindow*>(pRootObject);
Q_ASSERT( pMainWindow );
pMainWindow->close();

The third step is, of course, to load your QML.

Later, I moved to creating a QQuickView window instead of QQmlApplicationEngine, so that I could just call clearComponentCache and then setSource (I didn't like the user seeing the UI window vanish and then re-appear.)

Administrative answered 15/3, 2016 at 0:43 Comment(2)
Hey, thanks for the reply! This is an excellent way to do it and I'll give it a shot. Meanwhile I have found another approach to do this: Since we are loading the QML files from the file system using a file name URL, adding a random "?nocache=1238123" to the end of the URL solved the caching/reloading issue as well. Of course this is not as deep of a "reboot" for the engine as your approach, but works so far.Exterminatory
Thanks for the nocache tip :)Administrative
S
5

I would try storing myEngine as a pointer on the heap, and deleting it after calling quit(). Then you can reconstruct it to get the new version of the QML file.

If you don't want to do this for some reason (e.g. because you want to keep the window around or whatever), you might try using a Loader and loading the QML file that way. Your main.qml would then look something like this:

import QtQuick 2.0

Loader {
     source: "changing.qml"
}

Whenever you detect that changing.qml has changed, just toggle the active property of Loader to trigger a reload of the file.

Skilken answered 12/5, 2015 at 16:35 Comment(2)
Duh. I guess I should pack up and go home. It's so obvious it hurts. Thanks! Here, have some rep points. :)Exterminatory
I was not able to do it in any way by using pointer and quit, I would call quit and the connect to the quit signal, trying to delete it would always result in crash, tried disconnect(), deleteLater() but to no avail, loader solution works perfectlyDoornail
C
0

Using a file watcher:

main.py

DEBUG = True


class EntryPoint(qtc.QObject):
    if DEBUG:
        qmlFileChanged = qtc.Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.qml_engine = qqml.QQmlApplicationEngine()
        self.qml_entry = str(PATHS.playground.resolve())
        self.qml_engine.load(self.qml_entry)
        if DEBUG:
            qml_files = []
            for file in glob.iglob('**/*.qml', root_dir=PATHS.QML, recursive=True):
                qml_files.append(str((PATHS.QML / file).resolve()))
            self.file_watcher = QFileSystemWatcher(self)
            self.file_watcher.addPaths(qml_files)
            self.file_watcher.fileChanged.connect(self.on_qml_file_changed)

    if DEBUG:
        @slot
        def on_qml_file_changed(self) -> None:  # pragma: no cover
            self.qml_engine.clearComponentCache()
            window: QQuickItem = self.qml_engine.rootObjects()[0]
            loader: QQuickItem = window.findChild(QQuickItem, 'debug_loader')
            qtc.QEventLoop().processEvents(qtc.QEventLoop.ProcessEventsFlag.AllEvents, 1000)
            prev = loader.property("source")
            loader.setProperty('source', "")
            loader.setProperty('source', prev)

playground.qml

import QtQuick
import QtQuick.Controls.Material

Window {
    id: root
    width: 1200
    height: 900
    visible: true
    flags: Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint | Qt.CustomizeWindowHint | Qt.WindowTitleHint
    Material.theme: Material.Dark
    Material.accent: Material.Cyan

    Pane {
        anchors.fill: parent
        objectName: "_rootRect"
        Loader{id: loader
            objectName: "debug_loader"
            anchors.fill: parent;
            source:"anything.qml"
        }
    }

}

Cicala answered 8/12, 2022 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.