In QML, the Loader freezes the UI when loading large/time-consuming objects - qt

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:
Do Loaders really run in the background (for example, if I'm trying to connect to a server in my constructor, can Loader handle this situation or do I have to run it in another thread)?
How should such scenarios be handled so that I do not see any glitches?
window.qml
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
}
}
SplashScreen.qml
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
}
}
}
heavyObject.qml
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:
I changed cumsum from a function to a property
I introduced calcStep for do a calculation for one j iteration
I use Qt.callLater to instantiate the next iteration
I kick off the calculation during Component.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
Page {
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 * 3 / 4
}
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);
} );
}
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)
}
}
Component.onCompleted: calc()
}
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:
https://github.com/stephenquan/qt5-qml-promises
https://github.com/stephenquan/qt5-qml-promises-demo

Related

QML Loading View during function runtime

I am attempting to create a qml button object that displays a screen for the duration of a function's runtime. I plan to use this loading screen when I need to parse through a larger dataset/run a slower function. Currently this is what I have come up with.
//LoadingButton.qml
import QtQuick 2.4
import QtQuick.Controls 1.2
Item
{
id: impl
function callbackFunction() { console.log("This is a dummy funciton and needs to be overwritten in the implementation") } //empty dummy function
property alias style: button.style
Button {
id: button
anchors.fill: parent
onClicked: {
loadingScreen.visible = true;
console.log("Loading should be visible")
impl.callbackFunction();
loadingScreen.visible = false;
console.log("Loading should be hidden")
}
}
Rectangle
{
width: 500
height: 500
x:0
y:0
z: 60
id: loadingScreen
color: "red"
visible: false
}
}
This example runs the callbackFunction once overwritten in the parent object correctly, but the visibility of the Rectangle does not change until the slower function is completed. Also the application freezes until it finishes.
Is there any way to force the Rectangle to show/hide mid-javascript function execution?
the best solution is of course to move your slow function to a background thread. That way the GUI stays responsive.
If you want to keep the callbackFunction in same thread as the GUI, you can use a Timer that will delay the start of the slow function until the loading screen is shown. Please note that the GUI will be blocked during the execution of the slow function.
import QtQuick 2.4
import QtQuick.Controls 1.2
Item
{
id: impl
function callbackFunction() {
console.log("This is a dummy funciton and needs to be overwritten in the implementation")
var cnt = 0
var largeNumber = 1
while (cnt < 99999999) {
largeNumber += largeNumber/3
cnt++
}
//put this at the end of your slow function
loadingScreen.visible = false;
console.log("Loading should be hidden")
}
property alias style: button.style
Button {
id: button
anchors.fill: parent
onClicked: {
loadingScreen.visible = true;
console.log("Loading should be visible")
timer.start()
}
}
Timer {
id: timer
interval: 500
repeat: false
onTriggered: impl.callbackFunction()
}
Rectangle
{
id: loadingScreen
width: 500
height: 500
x:0
y:0
z: 60
color: "red"
visible: false
BusyIndicator {
anchors.centerIn: parent
running: loadingScreen.visible
}
}
}

Is it a known issue that Qt QML function can crash an application?

Im developing an QT qml application that is crashing the application on a Windows machine, after running a qml function.
To illustrate that i'm going to post here an example that crashes the application, after running a function:
import QtQuick 2.0
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import AsyncWorker 1.0
ApplicationWindow {
id: window
title: "Stack"
visible: true
width: 600
height: 500
Page {
id: page
anchors {
fill: parent
margins: 10
}
ColumnLayout {
anchors.fill: parent
spacing: 10
RowLayout {
id: testRowLayout
function bigfunction() {
var teste = 0
var arrayTeste = []
for(var i=0; i< 100000; i++)
teste +=i
arrayTeste.push(i)
for(var j=0; j<100000;j++) {
teste +=j
arrayTeste.push(j)
for(var z=0; z<10000; z++) {
teste +=z
arrayTeste.push(z)
}
}
console.log(teste)
spinner.running = false
}
BusyIndicator {
id: spinner
anchors.centerIn: parent
running: false
}
Button {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Run function")
onClicked: {
spinner.running = true
testRowLayout.bigfunction()
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
Anyone knows what can be causing this and how to fix it? Is there a way to fix without the use of threads?
You are running a very long function in the GUI thread. So, your UI is freezing and Windows task manager thinks that your app is not responding anymore.
If you wait for the end of the function, the app will work again.
You have to run your JS function in another thread by using WorkerScript.
For example:
ApplicationWindow {
id: window
title: "Stack"
visible: true
width: 600
height: 500
WorkerScript {
id: worker
source: "worker.mjs"
onMessage: {
spinner.running = !messageObject.finished
console.log(messageObject.result)
}
}
Button {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Run function")
onClicked: {
worker.sendMessage({});
}
}
BusyIndicator {
id: spinner
anchors.centerIn: parent
running: false
}
}
// worker.mjs
WorkerScript.onMessage = function(message) {
var teste = 0
WorkerScript.sendMessage({'finished': false, 'result': teste});
var arrayTeste = []
for(var i=0; i< 10000; i++) {
teste +=i
arrayTeste.push(i)
for(var j=0; j<10000;j++) {
teste +=j
arrayTeste.push(j)
for(var z=0; z<10000; z++) {
teste +=z
arrayTeste.push(z)
}
}
}
WorkerScript.sendMessage({'finished': true, 'result': teste});
}
What #Romhakorev said makes sense, it is never cool to have a blocked application.
But the real problem on the windows OS might be related to the 32 MinGW 32-bit compiler.
To solve this we can switch to Qt 5.12.2/3 to use MinGW 64-bit which solves this specific problem.
I posted a similar question on:
https://forum.qt.io/topic/103670/qml-is-crashing-when-the-ui-is-blocked-for-some-time-in-windows

how to regenerate an UI stochastically in QML using a Button on the interface?

I am working on the following QML framework for music apps user-interface prototyping:
https://github.com/tiagmoraismorgado/TMM_QML_UI_UX_FRAMEWORK_WIP in the file instiationTest, i need to add a type Button1_1 and then use it to regenerate the whole gui based on combostochasticselector.
i know i have to instantiate the randomPicking() function, from the instantiationtest file, on top of the mouseArea Pressed statement of Button1_1, but i am not being able to do so.
if anyone could provide me any insight i would be pretty thankful
here is the code of the main file
(...)
Window {
id: root
width: Screen.width
height: Screen.height
visible: true
visibility: "FullScreen"
title: qsTr("instantiationTest")
color: "black"
CombosStochasticSelector {}
}
on the combostochasticselector, you can see the following:
Item {
property var model: model1
anchors.fill: parent
id: randomMIDIkeyboardSelector;
property var random: 0;
function randomSelection(min, max) {(...)}
function createMidiKeyboard(itemToBeInstantiated) {
var component = Qt.createComponent(itemToBeInstantiated)
if (component.status === Component.Ready) {
var midiKeyboard = component.createObject(randomMIDIkeyboardSelector, {model: model});
}
else if (component.status === Component.Error) {
}
}
function randomPicking() {
random = parseInt(randomSelection(1, 13));
if(random == 1) {createMidiKeyboard("./../_Combos/Combo1.qml");}
(...)
return random;
}
Component.onCompleted: {
randomPicking();
}
}
on the Button1_1 you can see the following:
import QtQuick 2.6
QMLOpenGLToggleButtonPrimitive {}
It instantiates a press released kind of button made out of Rectangle QML Primitives
kind regards
Tiago

QML XmlListModel messes sorting when delegated

I'm trying to get a Canvas to draw lines in the same order as presented in this xml-file:
<root>
<doc><nopeus>80.0</nopeus><aika>40.0</aika></doc>
<doc><nopeus>110.0</nopeus><aika>80.0</aika></doc>
<doc><nopeus>120.0</nopeus><aika>120.0</aika></doc>
<doc><nopeus>190.0</nopeus><aika>160.0</aika></doc><doc><nopeus>243.0</nopeus><aika>200.0</aika></doc><doc><nopeus>260.0</nopeus><aika>240.0</aika></doc><doc><nopeus>300.0</nopeus><aika>280.0</aika></doc><doc><nopeus>350.0</nopeus><aika>320.0</aika></doc>
</root>
QML-file with XmlListModel:
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtQuick.XmlListModel 2.0
Page {
id: page
property alias startx : coords.mX
property alias starty : coords.mY
Item {
id: coords
property int mX: 0
property int mY: 0
}
XmlListModel {
id: myxml
source: "/home/nemo/filename.xml"
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
}
ListView {
model: myxml
anchors.fill: page
delegate:
Item {
Chart {
xc: coords.mX;
yc: coords.mY;
xd: aika;
yd: nopeus;
}
}
}
}
Chart.qml:
import QtQuick 2.0
Rectangle {
id: myrect
width: 540
height: 960
color: "transparent"
property int xd: 0
property int yd: 0
property int xc: 0
property int yc: 0
Canvas {
id: mycanvas
width: myrect.width; height: myrect.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(xc,yc)
context.lineTo(xd,yd)
context.stroke()
startx = xd
starty = yd
}
}
}
The question is why is the resulting path messed up when inserted in the ListView via delegates? I have tried to sort the path items separately, via a function and another ListModel but the result is the same.
Here is a screenshot:
Delegates are created for each item in a model. Your model contains eight items (as of your input). Hence, you create eight Canvases (each one as a ListView item, i.e. at (theoretical) increasing y w.r.t. ListView origin coordinates).
Combile these problems with the (probably wrongly set) starting points...and you get a random mess! You can't see that, since the Canvases tend to overlap due to sizing/constraints set on the component.
In this case you just need one Canvas on which each myxml item is painted. Here is a (naive) adaptation of your code which correctly shows the path stored in the xml file:
// main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.XmlListModel 2.0
Window {
visible: true
width: 600
height: 600
XmlListModel {
id: myxml
source: "qrc:/filename.xml" // added to the resources
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
onStatusChanged: {
if(status === XmlListModel.Ready)
comp.mod = myxml // set the model ASA it is ready to be used
}
}
Chart {
id: comp
anchors.fill: parent
mod: myxml
}
}
// Chart.qml
import QtQuick 2.4
import QtQuick.XmlListModel 2.0
Item {
property var mod: undefined
onModChanged: {
if(mod)
mycanvas.requestPaint() // repaint only when the model is available
}
Canvas {
id: mycanvas
width: parent.width; height: parent.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(0,0)
// iterate over all the point to print them
for(var i = 0; i < mod.count; i++)
{
var point = mod.get(i)
context.lineTo(point.aika, point.nopeus)
}
context.stroke()
}
}
}
The resulting path rendered:

Dynamically create QML ListElement and content

So I am trying to dynamically create ListElements in a ListModel. This works fine until I try writing some content in the ListElements to be loaded dynamically.
I tried making an own file with the ListElement within and the hour as a property, but the model then I got an error saying that ListElements can not be nested.
The error for running the code below is:
Cannot assign to non-existent property "hour"
How can I solve this?
Code:
import QtQuick 2.0
ListModel
{
id: listModel
Component.onCompleted:
{
for (var i = 0; i < 24; i++)
{
var object = createListElement(listModel)
}
}
function createListElement(parent)
{
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { hour: "01" }', parent);
return object;
}
}
EDIT:
Change the code line in the function to:
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { property string hour: "23" }', parent);
Now I get no errors, but the elements are still not showing in the list.
I'm not sure why that doesn't work, but using plain old JavaScript objects does the job:
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
width: 400
height: 400
ListView {
id: listView
anchors.fill: parent
model: listModel
delegate: Rectangle {
width: listView.width
height: listView.height / 4
Text {
text: hour
anchors.centerIn: parent
}
}
}
ListModel {
id: listModel
Component.onCompleted: {
for (var i = 0; i < 24; i++) {
append(createListElement());
}
}
function createListElement() {
return {
hour: "01"
};
}
}
}

Resources