BusyIndicator не отображается

Я хочу показать BusyIndicator, пока идет длительный процесс. Проблема в том, что он не появляется, когда я запускаю его, и показывает потом, когда процесс завершен. Согласно документам

Индикатор занятости следует использовать для обозначения активности во время загрузки содержимого или блокировки пользовательского интерфейса в ожидании доступности ресурса.

Я создал минимальный код, основанный на исходном коде.

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
            }
        }
    }
}

Поэтому, когда нажимается Rectangle, я хочу отображать BusyIndicator за время до завершения вычислений.

Например, здесь я использовал цикл for. В реальном сценарии я вызываю функцию (которая вставляет около 1000 строк в базу данных) через ContextProperty. Но и в этом случае BusyIndicator не отображается.

Я делаю это правильно? Или как лучше всего это сделать?


person astre    schedule 01.12.2014    source источник


Ответы (3)


Это можно сделать с помощью сигнала QQuickWindow afterSynchronizing:

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, ";
                    }
                }
            }
        }
    }
}

Идея состоит в том, чтобы использовать Loader, чтобы иметь контроль над тем, когда происходит дорогостоящая операция. Вы также можете использовать динамически загружаемый компонент через Qt.createQmlObject() или Qt.createComponent() для динамической загрузки компонента в отдельный файл.

Если вы запустите пример, вы увидите, что вы получите следующий вывод:

qml: 58:12:356 QML загружен
qml: 58:12:608 Отображено содержимое окна

Мы используем сигнал QQuickWindow afterSynchronizing, чтобы узнать, когда содержимое окна было отображается и действует только в первый раз (через if (!loader.item)).

Когда сигнал исходит изначально, мы можем быть уверены, что BusyIndicator начал свою анимацию, поэтому пользователь действительно увидит вращающуюся иконку.

Как только Loader завершит загрузку текста, его свойство item станет ненулевым, а BusyIndicator исчезнет.

person Mitch    schedule 02.12.2014

Вы не можете просмотреть свой BusyIndicator только потому, что долгая работа в обработчике onClicked блокирует графический интерфейс приложения и индикатор не обновляется. Вы должны запустить такую ​​операцию в другом потоке, чтобы избежать зависания графического интерфейса. Простой пример:

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});
}
person folibis    schedule 01.12.2014
comment
Согласен с этим. В реальном сценарии у меня есть функция C++, которая вызывается onClick. Можно ли использовать там WorkerScript? - person astre; 01.12.2014
comment
Если ваш обработчик на C++, вы можете легко сделать это с помощью QThread, я думаю. An отправить некоторое уведомление, когда расчет завершен. - person folibis; 01.12.2014
comment
Хорошо, я хотел этого избежать :) Любой возможный способ сделать что-то похожее на processEvents из самого QML? Или можно запустить сам BusyIndicator в отдельном потоке? - person astre; 01.12.2014
comment
Вместо использования QThreads вы можете использовать QtConcurrent::run(). Он создает поток в пуле потоков для длительных процессов и возвращает QFuture‹T›. Затем вы можете использовать сигнал QFutureWatcher‹T›::finished, чтобы посмотреть, когда задача завершена, и получить любой результат, если он существует. Готовые сигналы генерируются в том же потоке, в котором был создан QFutureWatcher, поэтому вам не нужно иметь дело с какой-либо синхронизацией. - person Jairo; 01.12.2014
comment
@Jairo, что-нибудь кроме этого? Пожалуйста, обратитесь к другой части моего предыдущего комментария. - person astre; 01.12.2014
comment
@folibis, это просто альтернатива QThreads, как вам предложил astred, и потому что вы сказали, что хотите их избегать. - person Jairo; 01.12.2014
comment
Насколько я знаю, единственный способ запустить выполнение в другом потоке - это WorkerScript. - person folibis; 01.12.2014

Сегодня столкнулся с той же проблемой! Я предполагаю, что вы управляете своим BusyIndicator из свойства C++, называемого busy. И вы устанавливаете busy на true непосредственно перед расчетами и на false сразу после. Это решило это для меня. Это не очень элегантное решение, но оно работает:

QML

BusyIndicator {
    running: CPPModule.busy
}

Цена за тысячу показов

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);
}
person luffy    schedule 25.02.2015
comment
Busyindicator крутится? Потому что, если вы используете непоточную версию средства визуализации QML (например, в Windows), я не вижу, как BusyIndicator может вращаться без дальнейших вызовов processEvents() в разделе очень длительной операции. Для длительных и трудоемких задач просто используйте потоки. Вот и все. - person BaCaRoZzo; 25.02.2015
comment
да крутится! Код, который я предоставил, работал у меня отлично, но я действительно не знаю, почему, особенно потому, что msleep (50) является блокирующей функцией, насколько мне известно. - person luffy; 25.02.2015
comment
Это блокирует. На какой платформе? Если бэкенд является многопоточным, это имеет смысл, поскольку вы блокируете этот поток, а не поток пользовательского интерфейса. В Qt 5.5 Windows также будет иметь многопоточный рендеринг... но не сейчас. В любом случае processEvents() это действительно плохая практика. - person BaCaRoZzo; 25.02.2015