14

I have main.qml and dynamic.qml files that i want to load dynamic.qml on main.qml using Loader {}.
Content of dynamic.qml file is dynamic and another program may change its content and overwrite it. So i wrote some C++ code for detecting changes on file and fires Signal.
My problem is that I don't know how can i force Loader to reload file.

This is my current work:

MainController {
    id: mainController
    onInstallationHelpChanged: {
        helpLoader.source = "";
        helpLoader.source = "../dynamic.qml";
    }
}

Loader {
    id: helpLoader

    anchors.fill: parent
    anchors.margins: 60
    source: "../dynamic.qml"
}



I think that QML Engine caches dynamic.qml file. So whenever I want to reload Loader, it shows old content. Any suggestion?

S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59

2 Answers2

13

You need to call trimComponentCache() on QQmlEngine after you have set the Loaders source property to an empty string. In other words:

helpLoader.source = "";
// call trimComponentCache() here!!!
helpLoader.source = "../dynamic.qml";

In order to do that, you'll need to expose some C++ object to QML which has a reference to your QQmlEngine (lots of examples in Qt and on StackOverflow to help with that).

trimComponentCache tells QML to forget about all the components it's not current using and does just what you want.

Update - explaining in a bit more detail:

For example, somewhere you define a class that takes a pointer to your QQmlEngine and exposes the trimComponentCache method:

class ComponentCacheManager : public QObject {
    Q_OBJECT
public:
    ComponentCacheManager(QQmlEngine *engine) : engine(engine) { }

    Q_INVOKABLE void trim() { engine->trimComponentCache(); }

private:
    QQmlEngine *engine;
};

Then when you create your QQuickView, bind one of the above as a context property:

QQuickView *view = new QQuickView(...);
...
view->rootContext()->setContextProperty(QStringLiteral("componentCache", new ComponentCacheManager(view->engine());

Then in your QML you can do something like:

helpLoader.source = "";
componentCache.trim();
helpLoader.source = "../dynamic.qml";
ksimons
  • 3,797
  • 17
  • 17
  • Thanks but how can i do that? – S.M.Mousavi Oct 26 '13 at 12:45
  • I added some more details. I hope you can take it from there. – ksimons Oct 26 '13 at 14:42
  • About the only thing I'd do is to use `QPointer` for `QQmlEngine`. Generally speaking, unless you own a `QObject` in a given context, you shouldn't be using a naked pointer to it. – Kuba hasn't forgotten Monica Oct 26 '13 at 14:47
  • @KubaOber sure, that's a fair point. However, in this case a QML app generally creates a QQmlEngine at the beginning of execution and destroys it on shutdown, so the risk of dereferencing a null pointer is quite small. But it's a good point nonetheless. – ksimons Oct 26 '13 at 15:49
  • 2
    Thank you. I implement your suggestion by creating a Qml type using `qmlRegisterSingletonType`. But `trimComponentCache()` not works. Instead `clearComponentCache()` works. Thank you man! – S.M.Mousavi Oct 26 '13 at 20:12
  • @S.M.Mousavi strange as trimComponentCache() worked for me, but glad you got it working either way! – ksimons Oct 27 '13 at 08:22
  • I tried trimming the cache in onSourceChanged and OnLoaded of the Loader, which didn't work. Here only clearing worked. I suspect the components are not yet unloaded when onSourceChanged/Loaded are invoked. However, trimming the cache right bevore setting the new source worked for me, so there's at least a workaround. – SvenS May 20 '14 at 10:30
4

I was hoping for a pure QML solution. I noticed that loader.source is a url (file:///) and remembered how with HTML, you can avoid HTTP caching using ?t=Date.now() in your requests. Tried adding ?t=1234 to the end of loader.source, and sure enough, it works.

import QtQuick 2.0

Item {
    Loader {
        id: loader
        anchors.fill: parent
        property string filename: "User.qml"
        source: filename

        function reload() {
            source = filename + "?t=" + Date.now()
        }
    }

    Timer {
        id: reloadTimer
        interval: 2000
        repeat: true
        running: true
        onTriggered: {
            loader.reload();
        }
    }
}

I also wrote another example that will check for changes in the file contents before triggering a reload using an XMLHttpRequest.

import QtQuick 2.0

Item {
    Loader {
        id: loader
        anchors.fill: parent
        property string filename: "AppletUser.qml"
        property string fileContents: ""
        source: ""

        function reload() {
            source = filename + "?t=" + Date.now()
        }

        function checkForChange() {
            var req = new XMLHttpRequest();
            req.onreadystatechange = function() {
                if (req.readyState === 4) {
                    if (loader.fileContents != req.responseText) {
                        loader.fileContents = req.responseText;
                        loader.reload();
                    }
                }
            }
            req.open("GET", loader.filename, true);
            req.send();
        }

        onLoaded: {
            console.log(source)
        }

        Timer {
            id: reloadTimer
            interval: 2000
            repeat: true
            running: true
            onTriggered: loader.checkForChange()
        }

        Component.onCompleted: {
            loader.checkForChange()
        }
    }

}
Zren
  • 759
  • 7
  • 13
  • This doesn't work for me with Qt 5.9.1 32Bit under windows, it accepts the new filename with appended timestamp but changes in the qml don't show up. – Janosch Sep 04 '17 at 17:33