Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Reload a Loader on button click



  • Hi,

    I have a problem to reload some Loader in my application.
    I have a StackLayout with all my loaders :

    StackLayout {
    	id: mainLayout
    		
    	y: backgroundTab.height
    		
            currentIndex: 0
    
            Loader{
    	    id: loaderApplication
                source: "Application/Application.qml"
            }
            Loader{
    	    id: loaderRecord
                source: "Record/Record.qml"
            }
            [...]
        }
    

    Within my loaders, I have 2 buttons : the first one is a counter (that should be reset after the reload), and the second the reload button.

    The thing is I'm using QML within RTMaps (an external software), so I'm very limited : I can't use C++ in addition to QML.
    Solutions I found all use C++, so I'm a little bit lost.

    If someone has some clues, that will be helpfull !

    Thanks


  • Moderators

    Simplest way to reload a loader:

    // In some JS function, like button click handler:
    loaderRecord.source = ""
    loaderRecord.source = "Record/Record.qml"
    

    But I suspect you are asking about something else, right?


  • Moderators

    @sierdzio I haven't tested it, but shouldn't this be the same, as setting the active property to false, and to true again ?


    Edit:
    jup made a small test:

    ApplicationWindow {
        id:root
        visible:true
        width:500; height:500
    
        Button {
            id:btn
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            height:  50
    
            onClicked: {
                load.active = !load.active
    
                load.active = !load.active
            }
        }
    
        Loader{
            id:load
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            anchors.top: btn.bottom
    
            source: "TestFile.qml"
        }
    }
    
    //TestFile.qml
    import QtQuick 2.0
    
    Rectangle {
        color: "blue"
    
        Component.onCompleted: console.log("completed")
        Component.onDestruction: console.log("Destructed")
    
    }
    

    works fine enough, completed and destructed is printed each time



  • Thanks for your response !

    Both of your solution work, but it seems that the application keeps the initial qml file in memory. If I change my code live and reload it, my app will still load the file without the new modification.


  • Moderators

    @Dylan_Alt. that's because the component cache is not cleared.

    But I don't know how to force this from QML side. Maybe forcing the garbage collector to run in-between ? gc()

    usually you would call clearComponentCache on your QQmlApplicationEngine inside you main.cpp for that.

    Or that's where I do it ;)


  • Moderators

    Note that the cached properties will be cleared if the source or sourceComponent is changed after calling this function but prior to setting the loader active.

    That's from https://doc.qt.io/qt-5/qml-qtquick-loader.html. So the solution might be to get the right combination of active: false, then set source, then 'active: true`, but it sounds very hacky. What @J-Hilk proposed seems better.



  • Would creating dynamic QML objects work better than using a Loader?
    https://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html



  • Hi,

    sorry for the late response, I had a lot of other stuff to do.
    I tried with gc() and the hacky way, and a combination of both, and the result is the same as before.
    I haven't try fcarney's solution yet, I will do it when I will have a some time, and tell you if it worked.

    Thanks


  • Moderators

    Here's how I do it,
    maybe it's of help for your case:

    //QuickWidget.h
    #ifndef QUICKWINDOW_H
    #define QUICKWINDOW_H
    
    #include <QQuickWindow>
    #include <QIcon>
    
    class QuickWindow : public QQuickWindow
    {
        Q_OBJECT
    public:
        explicit QuickWindow(QQuickWindow *parent = nullptr) : QQuickWindow(parent) {}
    
    signals:
        Q_INVOKABLE void reloadQML();
    
    };
    
    #endif // QUICKWINDOW_H
    
    //main.cpp
    #include <QApplication>
    #include <QQmlApplicationEngine>
    
    #include "quickwindow.h"
    
    void clearAndReload( QQmlApplicationEngine &engine){
        for(QObject *obj : engine.rootObjects()){
            engine.rootObjects().removeOne(obj);
            obj->deleteLater();
        }
        engine.clearComponentCache();
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        for(QObject *obj : engine.rootObjects()){
            QuickWindow *window = qobject_cast<QuickWindow*>(obj);
            if(window)QObject::connect(window, &QuickWindow::reloadQML, &engine,[&engine]{clearAndReload(engine);});
        }
    }
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
    
    
        qmlRegisterType<QuickWindow>("QuickWindow", 1, 0, "QuickWindow");
        QQmlApplicationEngine engine;
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        for(QObject *obj : engine.rootObjects()){
            QuickWindow *window = qobject_cast<QuickWindow*>(obj);
            if(window) QObject::connect(window, &QuickWindow::reloadQML, &engine,[&engine]{clearAndReload(engine);});
        }
    
    
        return app.exec();
    }
    
    //main.qml
    import QtQuick 2.12
    import QtQuick.Controls 2.5
    
    import QuickWindow 1.0
    
    QuickWindow {
        id:root
        visible:true
        width:500; height:500
    
        Component.onCompleted: console.log("Window created")
    
        Shortcut{
            sequence: "F5"
            onActivated: {
                console.log("Reload")
                reloadQML()
            }
        }
    }
    
    


  • @J.Hilk I cannot use any C++ code :( I'm using QML in an external software, I can't link both C++ and QML.

    I tried to create / delete my objects dynamically like that :

    main.qml

    [...]
    StackLayout {
    [...]
            Loader{
                id: loaderTestReload
                source: "tab1.qml"
            }
    [...]
        }
    

    tab1.qml is use to create my object :

    Item {
        id: tab1
    
        property var compo
    
        Component.onCompleted: init()
        
        function init()
        {
            var component = Qt.createComponent("tab1_qml.qml")
            component.createObject(tab1)
            compo = component
        }
        function reload()
        {
            compo.destroy()
            init()
        }
    }
    

    and then I have the main component, with 2 buttons and a text :

    Item {
        id: tab1qml
    
        property int value: 0
        Button{
            id: button1
            text: tab1qml.value
            onClicked: value++
        }
        Button{
            y: button1.height
            text: "reload"
            onClicked: tab1.reload()
        }
        Text{
            y: button1.height*2
            text: "reset ?"
        }
    }
    

    When I press the reload button, the component is reloaded, but as before, my app keeps the file in memory. If I change my text, it will remain as it was.
    I will take a look at the example, it uses createQmlObject() instead of createComponent(). Maybe I can store the content of the file and send it via createQmlObject().


  • Moderators

    @Dylan_Alt.

    Indulge me for a bit, as I have not done this before. How do you start a QML application without a main- function?



  • @J.Hilk I don't how it's made behind. The software uses blocks to code (like Unreal Engine for example, but at a much higher level than simple instructions). One of these blocks is a QML Viewer, where I can set a main.qml file, up to 32 input property and as many outputs as I want. I don't have any control of what happens when the full application is started.


  • Moderators

    @Dylan_Alt.
    alight,

    that's unfortunate, as it limits your options quite heavily.



  • I took my time to test several things and I found something that worked :
    I used createQmlObject(), to load the content of the file, instead of createComponent(). Here is how it looks like :

    Tab1.qml :

    Item {
        id: tab1
    
        property var compo
    
        Component.onCompleted: init()
    
        function init()
        {
            var xhr = new XMLHttpRequest;
            var response
            xhr.open("GET", "tab1_qml.qml");
            xhr.onreadystatechange = function() {
                if (xhr.readyState == XMLHttpRequest.DONE) {
                    response = xhr.responseText;
                    compo = Qt.createQmlObject(response, tab1,"tab1_qml.qml")
                }
            };
            xhr.send(); // begin the request
        }
        function reload()
        {
            compo.destroy()
            init()
        }
    }
    

    tab1_qml.qml :

    Item {
        id: tab1qml
    
        property int value: 0
        Button{
            id: button1
            text: tab1qml.value
            onClicked: value++
        }
        Button{
            y: button1.height
            text: "reload"
            onClicked: tab1.reload()
        }
        Text{
            y: button1.height*2
            text: "reset ?"
        }
    }
    
    

    When I modify my tab1_qml.qml and clic the reload button, it works.
    But bad news, it works well on my software, but it doesn't work on QtCreator. As far as I understand, it's because the file is loaded from the ressource, the solution may be to take an external qml file.
    Anyway, my problem is solved.

    Thanks everyone who responsed.



  • @Dylan_Alt I realize this is a pretty old thread and this is a pretty hacky response, but it appears to work for both local and remote files.

    onClicked {
      myLoader.source = "MyLocalOrRemoteFile.qml?"+Math.random()
    }
    

Log in to reply