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.
Related
I am using QT v5.12.6 and in my application i am planning to use Progress Bar during application bootup for around 15secs. In this 15secs operations like:
QML components creation.
connection with the server.
Other bootup operations will be running in the background.
Once, all the bootup operation is done i will hide my splash screen which is just a Rectangle which includes Loading text with a progress bar. For this 15 secs i will be incrementing the progress bar but the progress bar doesn't increment/move for some 10 secs. It looks like it is hanged, but if i use a busy indicator it starts to rotate. Unfortunately, i can't use busy indicator component as i need a look and feel like a progress bar.
My application runs on a embedded platform which has low end processor and very less RAM speed. I am assuming this problem is due to the Load on the UI as many components is getting created.
Is there any difference between busy indicator and Progress bar and also any suggestions on how to handle UI load on the bootup ?
Edit 1: Added an example. I have tried my level best to mimic the problem. In this example both the busyindicator and Progress bar is getting stuck for sometime. But in the embedded device Busy indicator works but no idea how. After running the application Please click on Click Me button.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3
Window {
property int pbValue:0
visible: true
width: 500
height: 400
title: qsTr("Hello World")
Rectangle{
id: mySplashScreen
anchors.fill: parent
ProgressBar{
id: pBar
height: 20
width: parent.width
anchors.bottom: parent.bottom
from:0
value : pbValue
to:30
}
BusyIndicator{
anchors.right: parent.right
running: (pbValue < pBar.to +1)
}
Button{
text: "click me"
onClicked: {
//Create component equivalent to my application which has "n"
//number of components like buttons, combobox, chart view etc.
//Here just creating the rectangle more number of times.
for(var i = 0 ; i < 15000 ;i++) //HINT : Increase/decrease the value if problem is not seen
{
var comp = mycomp.createObject(mySplashScreen)
}
}
}
}
Timer{
id:timer
interval: 250
running: (pbValue < pBar.to +1)
onTriggered: {
pbValue += 1;
}
}
Component{
id:mycomp
Rectangle{
width: 200
height: 200
color: "green"
}
}
}
Move your object creation code into a separate Timer with a small interval, and rather than creating all of the objects at once, create them maybe 50 at a time every 25 MS or something.
This will allow for the main event loop to process other things like the animations for busy indicator while its loading.
Here's one way to go about implementing this
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3
Window {
property int pbValue: 0
visible: true
width: 500
height: 400
title: qsTr("Hello World")
Rectangle {
id: mySplashScreen
anchors.fill: parent
ProgressBar {
// changed handling of progress bar
id: pBar
height: 20
width: parent.width
anchors.bottom: parent.bottom
from: 0
value: compList.length // bind this value
to: 15000
}
BusyIndicator {
anchors.right: parent.right
running: (pbValue < pBar.to + 1)
}
Button {
text: "click me"
onClicked: {
timer.running = true
}
}
}
property var compList: [] // created property to store all created components to track what has been done
Timer {
id: timer
interval: 25
running: false
repeat: true
onTriggered: {
for (var i = 0; i < 50; i++) {
var comp = mycomp.createObject(mySplashScreen) // moved component into timer
compList.push(comp) // added component to huge list of components for tracking
}
pbValue = compList.length
if ((pbValue >= 15000)) {
timer.running = false
console.log("All components completed")
}
}
}
Component {
id: mycomp
Rectangle {
width: 200
height: 200
color: "green"
}
}
}
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
}
}
}
I have created the following MWE (Qt 5.13.0):
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 3;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Loader
{
sourceComponent: theSingleComp;
Component
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " +
(theSwipeView.currentIndex - 1);
}
}
}
}
}
}
}
In my program, I have an unique component (theSingleComp) and multiple components behind him (theMultiComp). As for now, I need to implement the following functionality:
In case the model used for theMultiComp has only 1 item, display only this item and not the theSingleComp. In case the are more theMultiComp items, display it like now. It seems to me that there is no possibility for this to work if I keep the items defined statically. But on the other hand, I don't know how to do this dynamically, since there is a case in which one of the components should not be displayed at all. I tried an approach like this:
sourceComponent: (itemsNo > 1) ? theSingleComp : null;
But then the page for this null component is still created.
Your problem is that Loader is an Item and SwipeView creates a page for it even if it doesn't have a source component.
To solve this problem you can use Repeater instead with a model of 1 (or 0 to disable it). Repeater is also an Item but it has some special code under the hood to be ignored by containers.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
id: window
property int itemsNo: 0
visible: true
width: 480
height: 480
SwipeView {
id: theSwipeView
anchors.fill: parent
Repeater {
model: window.itemsNo > 1 ? 1 : 0
Page {
Text {
text: "The single one"
}
}
}
Repeater {
model: window.itemsNo
Page {
Text {
text: "The multi one " + model.index
}
}
}
}
}
(I've simplified your code to remove the explicit Components and the Loaders)
I have come up with the following solution but I am not happy with it. It's very hacky and the user can see how the page index changes.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 2;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Component.onCompleted:
{
if (itemsNo > 1)
insertItem(0, theSingleComp);
set0IndexTimer.start();
}
Timer
{
id: set0IndexTimer;
interval: 1;
running: false;
repeat: false;
onTriggered: theSwipeView.setCurrentIndex(0);
}
onCurrentIndexChanged: console.log("page: ", currentIndex);
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " + theSwipeView.currentIndex;
}
}
}
}
}
}
Item
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
I am still seeking some other examples.
Demo:
Window {
visible: true
width: 640
height: 480
Component.onCompleted: {
test.visible = true // 1. Show rect
for(var i = 0; i < 5000000000; i++){var t = i * i} // 2. Slow process, Sleep here
}
Rectangle {
id: test
color: "red"
width: 100; height: 100
visible: false
}
}
The visible property works when function completed. In the demo, the test rectangle couldn't show at the moment after 1., have to wait until function finished.
I understand it should cause by the process blocks rendering. But is there any trick to solve this problem?
The heavy tasks should not be executed in the GUI thread, but in another thread so that they do not get blocked. QML offers to WorkerScript, this allows you to execute tasks in another thread:
slow_process.js
WorkerScript.onMessage = function() {
for(var i = 0; i < 5000000000; i++){
var t = i * i
console.log(t)
}
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
Component.onCompleted: {
test.visible = true // 1. Show rect
ws.sendMessage()
}
WorkerScript {
id: ws
source: "slow_process.js"
}
Rectangle {
id: test
color: "red"
width: 100; height: 100
visible: false
}
}
Here is the code of the window I wanna be opened in file PopUpFreeCoins.qml:
import QtQuick 2.0
import QtQuick.Controls 2.1
Item {
property int t
property int c
ListModel{
id:ff
ListElement {
name: "ByFollow"
s: "Images/follow.png"
}
ListElement {
name: "ByLike"
s: "Images/care.png"
}
ListElement {
name: "ByComment"
s: "Images/chat.png"
}
}
ListView{
width:t-t/10
height: c/5
layoutDirection:Qt.LeftToRight
orientation: ListView.Horizontal
model: ff
spacing:50
delegate: Button{
contentItem: Image{
source: s
}}
}
}
property t is set equal to window width in main file and property c is set to window height. This is code of my Button.qml:
Button{//Below Right
width:profilePicture.width/2
height:profilePicture.width/2
x:profilePicture.x+profilePicture.width
y:profilePicture.y+profilePicture.height
contentItem: Image {
source: "Images/freecoins.png"
anchors.fill: parent
}
onClicked: PopUp{height:100;width:300;PopUpFreeCoins{t:a;c:b;}}
}
property a is window width and b is window height.
this line onClicked: PopUp{height:100;width:300;PopUpFreeCoins{t:a;c:b;}} has an error I don't know how to handle!
Here is the error:
Cannot assign object type PopUpFreeCoins_QMLTYPE_0 with no default
method
You need to create the Object somehow. You have multiple ways for dynamically create Objects. One way is to use Component.createObject(parent) which requires you to have a Component instantiated in your file.
Here you can also pass a Object ({property0 : value, property1:value ... }) as second argument, to set the properties of the Component to be instantiated. You should not set the parent to null as it might happen, that the JS-garbage collector is too aggressive once again.
Alternatively you can use the Loader to load it from either a source (QML-file) or sourceComponent. Here you won't have problems with the garbage collector.
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
width: 1024
height: 800
visible: true
Button {
text: 'create'
onClicked: test.createObject(this)
}
Button {
x: 200
text: 'load'
onClicked: loader.active = !loader.active
}
Loader {
id: loader
source: 'TestObj.qml'
active: false
}
Component {
id: test
TestObj {}
}
}
TestObj.qml includes the Window to be opened.
Alternatively you can have the Window created from the beginning, and just change the visible to true or false.