繁忙指示器未显示

5

当长时间的进程在运行时,我想展示一个BusyIndicator。问题是,在我让它运行时它并没有显示出来,而是在进程完成后才显示。根据文档:

当内容正在加载或UI正在等待资源变得可用时,应使用忙指示器来指示活动。

我已经创建了一个基于原始代码的最小代码。

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循环以示例为目的。在实际场景中,我通过ContextProperty调用一个函数(将大约1000行插入到数据库中)。但即使是这种情况下,BusyIndicator也不会显示。
我这样做对吗?或者有更好的方法吗?
3个回答

6

如果在 onClicked 处理程序中进行的长时间操作会导致应用程序 GUI 阻塞且指示器未更新,那么您将无法查看 BusyIndicator。 您应该在不同的线程中运行此类操作以避免 GUI 冻结。 以下是一个简单的示例:

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

1
同意这一点。在实际场景中,我有一个在点击时被调用的C++函数。可以在那里使用WorkerScript吗? - astre
如果您的处理程序是用C++编写的,那么使用QThread应该很容易实现。并且在计算完成时发送一些通知。 - folibis
你可以使用QtConcurrent::run()代替QThreads。它会在线程池中生成一个线程来运行长时间的进程,并返回一个QFuture<T>。然后,你可以使用QFutureWatcher<T>::finished信号来查看任务是否完成,并获取任何结果(如果存在)。"Finished"信号在创建QFutureWatcher的同一线程中发出,因此你不必处理任何同步问题。 - Jairo
@folibis,这只是_astred_建议你使用QThreads的另一种选择,因为你说你想避免它们。 - Jairo
据我所知,运行不同线程中的执行的唯一方法是使用WorkerScript - folibis
显示剩余2条评论

3

使用QQuickWindowafterSynchronizing信号可以实现此功能:

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 loaded qml: 58:12:608 Window content rendered
我们使用QQuickWindowafterSynchronizing信号来知道窗口内容何时被显示,并且仅在第一次(通过if (!loader.item))对其进行操作。
当信号最初被发出时,我们可以确定BusyIndicator已开始其动画,因此用户实际上将看到旋转图标。
一旦Loader完成加载文本,其item属性将变为非空,BusyIndicator将消失。

1
今天遇到了同样的问题!我假设您正在使用名为busy的C++属性来控制BusyIndicator。在计算之前将busy设置为true,在计算完成后将其设置为false。这样做对我有用,虽然不是很优雅,但可以解决问题。 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);
}

BusyIndicator 是否会旋转?因为如果您正在使用非线程化版本的 QML 渲染器(例如在 Windows 上),我不清楚 BusyIndicator 如何在 "非常长时间操作" 部分中旋转而没有进一步的 processEvents() 调用。对于耗时的长时间任务,只需使用线程即可。就这样。 - BaCaRoZzo
是的它跑起来了!我提供的代码完美地运行了,但我不太清楚为什么,尤其是 msleep(50) 是一个阻塞函数 AFAIK。 - luffy
它是阻塞的。在哪个平台上?如果后端是多线程的,那么阻塞那个线程就是完全合理的,而不是 UI 线程。在 Qt 5.5 中,Windows 将具有线程渲染...但目前不支持。无论如何,processEvents() 这种做法真的很糟糕。 - BaCaRoZzo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接