我有一个qml应用程序,它根据用户请求执行相当长的动作。在此期间,我想在整个屏幕上显示一个叠加层,以便用户知道应用程序正在工作,基本上是一个繁忙指示器。
我的问题是,应用程序在更新 UI 组件之前从任务开始。下面是一个演示问题的最小示例:
import QtQuick 2.9
import QtQuick.Window 2.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Ui Demo")
Rectangle {
id: rectangle
anchors.fill: parent
color: "green"
}
MouseArea {
id: action
anchors.fill: parent
onClicked: {
rectangle.color = "red"
for(var i = 0; i < 10000; i++)
console.log(i)
}
}
}
我想要的是,当for
循环运行时,Rectangle
的颜色变为红色,但我看到的行为是,颜色仅在循环完成后才会更改。
我也尝试了以下方法,没有区别:
Rectangle {
id: rectangle
anchors.fill: parent
color: "green"
onColorChanged: {
for(var i = 0; i < 10000; i++)
console.log(i)
}
}
我知道,最干净的解决方案是在不同的线程上执行繁重的工作,以免阻塞 UI 线程。但我不想这样做,因为在我的实际应用程序中,阻塞工作是更新 ListModel,它(例如如此处所述(
不幸的是,Qt视图不知道如何处理[当它们]在外部线程中
。
因此,我需要实现一个新的异步模型类,这是我的客户目前不愿意支付的工作量和时间。
因此,我的问题是:如何确保在设置属性后立即重新绘制/更新 UI?
一种可能的方法是使用将"for">的顺序逻辑转换为异步逻辑,方法是通过Timer
:
import QtQuick 2.9
import QtQuick.Window 2.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Ui Demo")
Rectangle {
id: rectangle
anchors.fill: parent
color: "green"
}
MouseArea {
id: action
anchors.fill: parent
onClicked: {
rectangle.color = "red"
timer.start()
}
}
Timer{
id: timer
interval: 1
property int counter: 0
repeat: true
onTriggered: {
counter += 1
console.log(counter)
if(counter > 100000)
timer.stop()
}
}
}
可靠的解决方法
多亏了eyllanesc的回答,我想出了一个可能的解决方案。 我使用单次计时器来开始我的工作,因为在实际代码中,我无法使用重复计时器调用不同的步骤 - 但无论如何我都不需要,因为我不想显示任何动画 UI 元素。这段代码适用于我的目的:
import QtQuick 2.9
import QtQuick.Window 2.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Ui Demo")
Rectangle {
id: rectangle
anchors.fill: parent
color: "green"
}
MouseArea {
id: action
anchors.fill: parent
onClicked: {
rectangle.color = "red"
timer.start()
}
}
Timer {
id: timer
interval: 1
repeat: false
onTriggered: {
for(var i = 0; i < 10000; i++)
console.log(i)
rectangle.color = "green"
}
}
}
添加计时器 - 即使只有 1 毫秒的间隔 - 也会在开始实际工作之前授予具有更改颜色的处理时间的逻辑。虽然这看起来像是一个有点黑客的解决方法,但它工作得很好。
更优雅但不太可靠的方法
有一个更清洁,但不太可靠的解决方案: QtscallLater()
功能似乎是我一直在寻找的。尽管官方文档看起来不完整,但我在其源代码中找到了函数文档:
使用此函数可以消除对函数或信号的冗余调用。
作为第一个参数传递给 Qt.callLater(( 的函数 将在 QML 引擎返回到事件循环后调用。
当使用 与其第一个参数相同的函数,该函数将只调用一次。
例如: \snippet qml/qtLater.qml 0
传递给 Qt.callLater(( 的任何其他参数都将 传递给调用的函数。请注意,如果冗余呼叫 被消除,则只有最后一组参数将被传递给 功能。
使用稍后调用函数会将大部分时间对工作代码的调用延迟足够长的时间,以便 UI 得到更新。然而,大约三分之一的时间,这将失败并显示与问题中描述的相同的行为。
此方法可以按如下方式实现:
import QtQuick 2.9
import QtQuick.Window 2.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Ui Demo")
Rectangle {
id: rectangle
anchors.fill: parent
color: "green"
}
MouseArea {
id: action
anchors.fill: parent
onClicked: {
rectangle.color = "red"
Qt.callLater(action.doWork)
}
function doWork() {
for(var i = 0; i < 10000; i++)
console.log(i)
rectangle.color = "green"
}
}
}