I want to show a BusyIndicator
while a long process is going on. The problem is it does not show up when I make it run and shows afterwards when the process is completed. According to the docs
The busy indicator should be used to indicate activity while content is being loaded or the UI is blocked waiting for a resource to become available.
I have created a minimal code that based upon the original code
Window {
id: win
width: 300
height: 300
property bool run : false
Rectangle {
anchors.fill: parent
BusyIndicator {
anchors.centerIn: parent
running: run
}
MouseArea {
anchors.fill: parent
onClicked: {
run = true
for(var a=0;a<1000000;a++) { console.log(a) }
run = false
}
}
}
}
So when the Rectangle
is clicked I want to display the BusyIndicator
for the time till the calculations gets completed.
For example purpose I have used the for loop here. In actual scenario I call a function (which inserts some 1000 rows into the Database) through the ContextProperty
. But in that case too the BusyIndicator
is not displayed.
Am I doing it the right way? Or what would be the best way to do it?
There is a way to do this using QQuickWindow
's afterSynchronizing
signal:
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
width: 400
height: 400
visible: true
Component.onCompleted: print(Qt.formatDateTime(new Date(), "mm:ss:zzz"), "QML loaded")
onAfterSynchronizing: {
print(Qt.formatDateTime(new Date(), "mm:ss:zzz"), "Window content rendered")
if (!loader.item) {
loader.active = true
}
}
Item {
anchors.fill: parent
BusyIndicator {
running: !loader.item
anchors.centerIn: parent
}
Loader {
id: loader
active: false
anchors.fill: parent
sourceComponent: Text {
wrapMode: Text.Wrap
Component.onCompleted: {
for (var i = 0; i < 500; ++i) {
text += "Hello, ";
}
}
}
}
}
}
The idea is to use a Loader
to have control over when the expensive operation happens. You could also use a dynamically loaded component via Qt.createQmlObject()
, or Qt.createComponent()
to dynamically load a component in a separate file.
If you run the example, you'll see that you get the following output:
qml: 58:12:356 QML loaded
qml: 58:12:608 Window content rendered
We use QQuickWindow
's afterSynchronizing
signal to know when the content of the window has been displayed, and only act on it the first time (via if (!loader.item)
).
When the signal is initially emitted, we can be sure that the BusyIndicator
has started its animation, so the user will actually see a spinning icon.
Once the Loader
has finished loading the text, its item
property will become non-null and the BusyIndicator
will disappear.
You cannot view your BusyIndicator
just because long operation in onClicked
handler blocks application GUI and indicator does not update. You should run such operation in a different thread to avoid freezing of GUI. Simple example:
QML
Window {
id: win
width: 300
height: 300
property bool run : false
Rectangle {
anchors.fill: parent
BusyIndicator {
id: busy
anchors.centerIn: parent
running: win.run
}
MouseArea {
anchors.fill: parent
onClicked: {
win.run = true
thread.sendMessage({run : true});
}
}
WorkerScript {
id: thread
source: "handler.js"
onMessage: {
win.run = messageObject.run;
}
}
}
}
handle.js
WorkerScript.onMessage = function(message) {
if(message.run === true) {
for(var a=0;a<1000000;a++) { console.log(a) }
}
WorkerScript.sendMessage({run : false});
}
Run into the same problem today! I will assume you are controlling your BusyIndicator
from a C++ property called busy
. And you are setting busy
to true
just before your calculations and to false
just after. Doing this solved it for me. It's not a very elegant solution but it works:
QML
BusyIndicator {
running: CPPModule.busy
}
CPP
void CPPModule::setBusy(const bool &busy)
{
m_busy = busy;
emit busyChanged();
}
void CPPModule::InsertIntoDB()
{
setBusy(true);
QThread::msleep(50);
QCoreApplication::processEvents();
/*
very Long Operation
*/
setBusy(false);
}