There are several questions on this subject that are unrelated to my question and They did not produce any results for me.
Imagine I have a splash screen with AnimatedImage in QML that I want to display when my heavy components are loading in the background, so I use a Loader to load assets in background, but when the loader starts loading my UI freezes(i.e. that AnimatedImage), I can see that BusyIndicator not freezes.
I have provided the full source code in the github repository so that you may test it more easily.
my questions are:
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts
Window {
id:mainWindow
y:100
width: 640
height: 480
visible: true
flags: Qt.FramelessWindowHint
//splash screen
Popup {
id: popup
width: mainWindow.width
height: mainWindow.height
modal: false
visible: true
Overlay.modeless: Rectangle {
color: "#00000000"
}
//Splash loader
Loader{
id: splash
anchors.fill: parent
source: "qrc:/Splashscreen.qml"
}
}
// Timer that will start the loading heavyObjects
Timer {
id: timer
interval: 2000
repeat: false
running: true
onTriggered: {
loader.source = "qrc:/heavyObjects.qml"
loader.active = true
}
}
//write a loader to load main.qml
Loader {
id: loader
anchors.fill: parent
asynchronous: true
active: false
//when loader is ready, hide the splashscreen
onLoaded: {
popup.visible = false
}
visible: status == Loader.Ready
}
}
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
Item {
Rectangle {
id: splashRect
anchors.fill: parent
color: "white"
border.width: 0
border.color: "black"
AnimatedImage {
id: splash
source: "qrc:/images/Rotating_earth_(large).gif"
anchors.fill: parent
}
}
}
import QtQuick
Item {
function cumsum() {
for(var j=0;j<100;j++){
var p = 0
for (var i = 0; i < 1000000; i++) {
p *= i
}
}
return ""
}
// show dummy text that this is the main windows
Text {
text: "Main Window" + String(cumsum())
anchors.centerIn: parent
}
}
Most things you do in QML are handled in the QML engine thread. If you do something heavy in that thread, it will block everything else. I haven't checked your source code, but, in terms of heavy initialization, we can break it up with Qt.callLater() or similar so that the QML engine thread can catch up on UI/UX events.
For example, in the following:
cumsum
from a function to a propertycalcStep
for do a calculation for one j
iterationQt.callLater
to instantiate the next iterationComponent.onCompleted
property string cumsum
function calcStep(j) {
if (j >= 100) {
cumsum = new Date();
return;
}
for (var i = 0; i < 1000000; i++) {
p *= i
}
Qt.callLater(calcStep, j+1);
}
Component.onCompleted: calcStep(0)
}
If your initialization is more sophisticated, you may want to give Promises a try. This allows you to write asynchronous routines in a synchronous type of way, e.g.
property string cumsum
function calc() {
_asyncToGenerator(function*() {
for(var j=0;j<100;j++){
var p = 0
status = "j: " + j;
yield pass();
for (var i = 0; i < 1000000; i++) {
p *= i
}
}
cumsum = new Date();
})();
}
function pass() {
return new Promise(function (resolve, reject) {
Qt.callLater(resolve);
} );
}
Component.onCompleted: calc()
In the above, the cumsum
calculation has been using a derivative of the async/await pattern. For this, to work I make use of _asyncToGenerator
provided by a transpiler on babeljs.io. This is required since the QML/JS does not support async/await pattern until Qt6.6.
The pass()
function operates similarly to Python pass but has my implementation of Qt.callLater
wrapped in a Promise. Invoking it with yield pass();
does nothing but allows your function to momentarily release control so that the UI/UX events can catch up.
import QtQuick
import QtQuick.Controls
AsyncPage {
property string cumsum
property string status
// show dummy text that this is the main windows
Text {
text: "Main Window: " + cumsum
anchors.centerIn: parent
}
Text {
text: status
anchors.horizontalCenter: parent.horizontalCenter
y: parent.height * 6 / 10
}
Button {
id: heavyCalcButton
anchors.horizontalCenter: parent.horizontalCenter
y: parent.height * 7 / 10
text: "Calculate Now"
onClicked: heavyCalc()
}
function heavyCalc() {
_asyncToGenerator(function*() {
cumsum = "";
heavyCalcButton.enabled = false;
yield pass();
for(var j=0;j<100;j++){
var p = 0
status = "j: " + j;
yield pass();
for (var i = 0; i < 1000000; i++) {
p *= i
}
}
cumsum = new Date();
heavyCalcButton.enabled = true;
})();
}
function pass() {
return new Promise(function (resolve, reject) {
Qt.callLater(resolve);
} );
}
}
// AsyncPage.qml
import QtQuick
import QtQuick.Controls
Page {
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args)
function _next(value) {
_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
}
function _throw(err) {
_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
}
_next(undefined)
})
}
}
function _asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg)
var value = info.value
} catch (error) {
reject(error)
return
}
if (info.done) {
resolve(value)
} else {
Promise.resolve(value).then(_next, _throw)
}
}
}
You can Try it Online!
If you are interested in some of the work I've done with async and QML Promises refer to the following GitHub projects: