Reload a Loader on button click
-
wrote on 7 May 2019, 09:17 last edited by
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
-
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?
-
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?
@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
-
wrote on 7 May 2019, 11:48 last edited by
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.
-
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.
@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 yourQQmlApplicationEngine
inside you main.cpp for that.Or that's where I do it ;)
-
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. -
wrote on 7 May 2019, 19:17 last edited by
Would creating dynamic QML objects work better than using a Loader?
https://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html -
wrote on 10 May 2019, 07:42 last edited by
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
-
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() } } }
-
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() } } }
wrote on 14 May 2019, 12:34 last edited by@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(). -
@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().Indulge me for a bit, as I have not done this before. How do you start a QML application without a main- function?
-
Indulge me for a bit, as I have not done this before. How do you start a QML application without a main- function?
wrote on 14 May 2019, 13:04 last edited by Dylan_Alt.@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.
-
@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.
@Dylan_Alt.
alight,that's unfortunate, as it limits your options quite heavily.
-
wrote on 24 May 2019, 06:50 last edited by
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.
-
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.
wrote on 13 Jul 2020, 19:44 last edited by@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() }
-
@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() }