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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.