QML Loading View during function runtime - qt

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

Related

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

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

How to give sleep in qml

When I press the button now it instantly goes to the next page. Is it possible to give this loading.gif a sleep of 5 seconds?
I have tried to give it a duration: 5000 but then it gives an error
---- FULL CODE UPDATED ----
Login.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.15
Component
{
Rectangle
{
Rectangle
{
anchors.fill: parent
// Timer for Creating delay
Timer
{
id: timer
}
function delay(delayTime,cb)
{
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
ColumnLayout
{
// Some other items.
Button
{
onClicked:
{
backend.inloggen(email.text, wachtwoord.text, dropdown.currentText)
delay(5000, function()
{
loading_container.visible = true
})
stack.push(btnHomepage)
}
}
Rectangle
{
id: loading_container
visible: false
AnimatedImage
{
source: "./images/loading.gif"
}
}
}
}
}
}
Error: Login.qml:170: ReferenceError: delay is not defined
Sadly updating the the QtQuick nad QtQuick.controls update wasn't the solution
you can create delays by using Timer here is your code, I add one function that creates delay it gets duration like 5000 means 5 seconds, and one function that will be connected to Timer.
This function acts like a singleshot.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
// Timer for Creating delay
Timer {
id: timer
}
function delay(delayTime,cb) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
Rectangle
{
id: loading_container
anchors.fill: parent
color: "#d71616"
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 60
visible: false
}
Button {
id: button
x: 0
y: 0
width: 151
height: 62
text: qsTr("Click me ")
onClicked:
{
delay(5000, function() {
loading_container.visible = true
})
}
}
}
As you update the question, Try this :
Component encapsulated QML types with well-defined interfaces.
the way that you use it is wrong.
The way that you use Function in your program is also wrong.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Item {
width: 640
height: 480
Timer
{
id: timer
}
function delay(delayTime,cb)
{
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
Rectangle
{
anchors.fill: parent
// Timer for Creating delay
// ColumnLayout
// {
// Some other items.
Button
{
onClicked:
{
// backend.inloggen(email.text, wachtwoord.text, dropdown.currentText)
delay(5000, function()
{
loading_container.visible = true
})
// stack.push(btnHomepage)
}
}
Rectangle
{
id: loading_container
visible: false
AnimatedImage
{
source: "what ever is your source path"
}
}
}
// }
}
In your code, the button doesn't have access to the Delay function, hence the reference error you get
Simply move the function to the button and the Timer in the root item

QML: Bind loop detected without double assignment

As far as I know the bind loop happens when I try to assign two properties each other. Example:
CheckBox {
checked: Settings.someSetting
onCheckedChanged: {
Settings.someSetting = checked;
}
}
but in my scenario I can't see such a "double assignment". I report here the full code:
import QtQuick 2.7
import QtQuick.Window 2.3
Window {
visible: true;
width: 500
height: 500
Rectangle {
id: main
anchors.fill: parent
color: "black"
property bool spinning: true
property bool stopping: false
Rectangle {
x: 0.5 * parent.width
y: 0.5 * parent.height
width: 10
height: 200
radius: 5
color: 'red'
transformOrigin: Item.Top
rotation: {
if (main.stopping)
{
main.spinning = false;
main.stopping = false;
}
return timer.angle
}
}
Timer {
id: timer
interval: 5
repeat: true
running: true
onTriggered: {
if (main.spinning) angle += 1;
}
property real angle
}
MouseArea {
id: control
anchors.fill: parent
onClicked: {
main.stopping = true;
}
}
}
}
When you click with the mouse you will get the warning:
qrc:/main.qml:17:9: QML Rectangle: Binding loop detected for property "rotation"
I don't see my mistake. I'm using flags (bool variables) to control the execution of my code. I know in this case I can just stopping the timer directly, but the actual program is more complex than this example.
The binding is in the following lines:
rotation: {
if (main.stopping)
{
main.spinning = false;
main.stopping = false;
}
return timer.angle
}
The change of rotation is triggered by the change of main.stopping: let's say that change main.stopping is given by the mouseArea, then it will be called a rotation, but inside this there is an if, and in this you are changing back to main.stopping , where he will call rotation back.
If a property in QML changes all the elements that depend on it will change

QML BusyIndicator while loading a heavy qml file

I've been trying to run a BusyIndicator (http://doc.qt.io/qt-5/qml-qtquick-controls-busyindicator.html) while I am loading a qml file (http://doc.qt.io/qt-5/qml-qtquick-loader.html), but the BusyIndicator doesn't appear.
What I am trying to do is:
1- The user emits a "handlerLoader(name)", where "name" is the url of the next qml page.
2- In "onHandlerLoader" I run the busyIndicator.
3- Then, I change the Loader source.
The problem is that no matter the time I spent between steps 2 and 3, the BusyIndicator does not appear.
Moreover, when I comment step 3, the busyIndicator appears correctly.
What I am doing wrong?
Thanks!!
This is the code:
Rectangle {
visible: true
width: 800
height: 480
signal handlerLoader (string name)
Loader {
id: pageLoader;
source: "init.qml";
}
BusyIndicator {
id: busyIndicator_inicio
width: 100
height: 100
anchors.centerIn: parent
running: false
}
Connections {
target: pageLoader.item
onHandlerLoader: {
busyIndicator_inicio.running = true
pageLoader.source = name;
}
}
}
The reason is, that your heavy-loading Loader is blocking the thread.
Set it to asynchronous mode, to allow the rest of the program to run.
Further, I'd recommend to prefer declarative bindings to imperative assignments in handlers. See my example:
main.qml:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
width: 1000
height: 800
visible: true
Button {
text: 'load'
onClicked: {
loader.source = "TestObj.qml"
}
}
Loader {
anchors.fill: parent
id: loader
active: true
asynchronous: true
visible: status == Loader.Ready
}
BusyIndicator {
id: ind
anchors.fill: parent
running: loader.status == Loader.Loading
}
}
TestObj.qml:
import QtQuick 2.0
Item {
Grid {
anchors.fill: parent
columns: width
rows: height
Repeater {
model: 100
Rectangle {
width: { for (var i = 0; i < 10000; i++) console.log(i); return 1 }
height: 1
color: 'green'
}
}
}
}
Since the asynchronous Loader might display incomplete files for some time, I set it to be visible only when its status changes to ready.

BusyIndicator does not show up

I want to show a BusyIndicator while a long process is going on. The problem is it does not show up when I make it run and shows afterwards when the process is completed. According to the docs
The busy indicator should be used to indicate activity while content is being loaded or the UI is blocked waiting for a resource to become available.
I have created a minimal code that based upon the original code
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
}
}
}
}
So when the Rectangle is clicked I want to display the BusyIndicator for the time till the calculations gets completed.
For example purpose I have used the for loop here. In actual scenario I call a function (which inserts some 1000 rows into the Database) through the ContextProperty. But in that case too the BusyIndicator is not displayed.
Am I doing it the right way? Or what would be the best way to do it?
You cannot view your BusyIndicator just because long operation in onClicked handler blocks application GUI and indicator does not update. You should run such operation in a different thread to avoid freezing of GUI. Simple example:
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});
}
There is a way to do this using QQuickWindow's afterSynchronizing signal:
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, ";
}
}
}
}
}
}
The idea is to use a Loader to have control over when the expensive operation happens. You could also use a dynamically loaded component via Qt.createQmlObject(), or Qt.createComponent() to dynamically load a component in a separate file.
If you run the example, you'll see that you get the following output:
qml: 58:12:356 QML loaded
qml: 58:12:608 Window content rendered
We use QQuickWindow's afterSynchronizing signal to know when the content of the window has been displayed, and only act on it the first time (via if (!loader.item)).
When the signal is initially emitted, we can be sure that the BusyIndicator has started its animation, so the user will actually see a spinning icon.
Once the Loader has finished loading the text, its item property will become non-null and the BusyIndicator will disappear.
Run into the same problem today! I will assume you are controlling your BusyIndicator from a C++ property called busy. And you are setting busy to true just before your calculations and to false just after. Doing this solved it for me. It's not a very elegant solution but it works:
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);
}

Resources