5

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?

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
astre
  • 798
  • 6
  • 14

3 Answers3

6

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});
}
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
folibis
  • 12,048
  • 6
  • 54
  • 97
  • 1
    Agreed to that. In actual scenario I have a C++ function which gets called onClick. Can a WorkerScript be used there ? – astre Dec 01 '14 at 06:22
  • If your handler in C++ so you can easy do that with QThread I guess. Ans send some notification when a calculation finished. – folibis Dec 01 '14 at 06:29
  • Ok, I wanted to avoid that :) Any possible way to do something similar to processEvents from QML itself? Or is it possible run the BusyIndicator itself on a separate thread ? – astre Dec 01 '14 at 06:40
  • Instead of use QThreads, you can use [QtConcurrent::run()](http://doc.qt.io/qt-5/qtconcurrentrun.html). It spawns a thread in threadpool for long running processes, and returns a [QFuture](http://doc.qt.io/qt-5/qfuture.html). Then you can use a [QFutureWatcher::finished](http://doc.qt.io/qt-5/qfuturewatcher.html) signal to look when the task is finished, and get any result if it exists. "Finished" signals is emited in the same thread where QFutureWatcher has been created, so you haven't to deal with any synchronization. – Jairo Dec 01 '14 at 08:08
  • @Jairo, anything other than that ? Please refer other part of my previous comment. – astre Dec 01 '14 at 08:29
  • @folibis, it is just an alternative to QThreads as _astred_ suggested you, and because you said you want to avoid them. – Jairo Dec 01 '14 at 09:57
  • As I know the only way to run execution in different thread is `WorkerScript`. – folibis Dec 01 '14 at 11:27
3

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.

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
Mitch
  • 23,716
  • 9
  • 83
  • 122
1

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);
}
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
luffy
  • 2,246
  • 3
  • 22
  • 28
  • Does the `Busyindicator` spin? Because if you are using a non threaded version of the QML renderer (e.g. on Windows) I don't see how the `BusyIndicator` can spin without further `processEvents()` calls in the "very Long Operation" section. For long, time-consuming tasks just use threads. That's it. – BaCaRoZzo Feb 25 '15 at 13:11
  • yes it spins ! The Code I provided worked for me perfectly but I don't really know why, especially that msleep(50) is a blocking function AFAIK. – luffy Feb 25 '15 at 15:00
  • It is blocking. On which platform? If the backend is threaded it makes perfectly sense as you are blocking *that* thread, not the UI thread. In Qt 5.5 also Windows will have threaded rendering...but not currently. In any case `processEvents()` it's really bad practice. – BaCaRoZzo Feb 25 '15 at 15:10