Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BusyIndicator does not show up

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?

like image 835
astre Avatar asked Dec 01 '14 05:12

astre


3 Answers

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});
}
like image 156
folibis Avatar answered Oct 10 '22 02:10

folibis


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.

like image 41
Mitch Avatar answered Oct 10 '22 03:10

Mitch


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);
}
like image 27
luffy Avatar answered Oct 10 '22 02:10

luffy