How to make visible property work immediately in QML? - qt

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

Related

How to make a QML animation work only when a property value is increasing not decreasing?

From Qt official documentation here I understand that I can animate the the change x coordinate of a QML item.
Following works but the issue is that I want the animation to be not activate when value of x is decrementing:
Rectangle {
id: some_item
x: somePropertyValueThatKeepsChanging
Behavior on x {
NumberAnimation {
duration: 50
}
}
}
Question:
How do I make the animation Behavior on x execute only when x is increasing, not when it is decreasing?
I am using Qt commercial version 5.15.1.
This is possible by using the targetValue (added in 5.12) and the targetProperty (added in 5.15) of Behavior:
Behavior on x {
enabled: targetValue > targetProperty.object[targetProperty.name]
NumberAnimation { duration: 200 }
}
(Note than you can just do targetValue > some_item.x instead of using targetProperty, but targetProperty lets you define generic reusable Behaviors)
Small example:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
id: some_item
x: 0
y: 100
height: 100
width: 100
color: "salmon"
Behavior on x {
enabled: targetValue > targetProperty.object[targetProperty.name]
NumberAnimation { duration: 200 }
}
}
Row {
Button {
text: "increase"
onClicked: some_item.x += 10
}
Button {
text: "decrease"
onClicked: some_item.x -= 10
}
}
}
This is possible, but I don't think this is a built-in feature of QML behaviors/animations, so it needs a little bit of custom implementation.
Rather than bind x directly to the changing value, use an intermediate listener to determine if the new value is greater or less than the current x value, and set the enabled flag on the Behavior prior to setting x. Here is a complete working example:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
id: root
width: 640
height: 480
visible: true
property real somePropertyThatChanges // simulated externally changing value using timer below
Timer {
interval: 500
running: true
repeat: true
onTriggered: {
somePropertyThatChanges = Math.round(Math.random()* rect.parent.height) // simulate value changes every 500ms
}
}
Rectangle {
id: rect
color: "lightgrey"
width: 50
height: 50
property real intermediateValueChecker: root.somePropertyThatChanges
onIntermediateValueCheckerChanged: {
if (root.somePropertyThatChanges > rect.x) { // check if new value is greater or less than current x, set behavior.enabled accordingly
xBehavior.enabled = true
} else {
xBehavior.enabled = false
}
rect.x = somePropertyThatChanges // set x after the animation is enabled or disabled
}
Behavior on x {
id: xBehavior
NumberAnimation {
duration: 500
}
}
}
}
If x is going to be set to a greater value than previously, it will enable the animation, otherwise disable it.
Other note on alternative implementation: If you don't want to use a new property real for this intermediate listener, you could bind a Connections object directly to somePropertyThatChanges instead. I didn't include this because I believe they changed the syntax of these between my Qt version (5.12) and yours, but for me it would look like:
Connections {
target: root
onSomePropertyThatChangesChanged: { // in QT 5.15 this may need to instead be: function onSomePropertyThatChangesChanged() {
if (root.somePropertyThatChanges > rect.x) { // same logic
xBehavior.enabled = true
} else {
xBehavior.enabled = false
}
rect.x = somePropertyThatChanges
}
}

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

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.

QML Subwindow does not close as expected

I'm trying to implement a subwindow as a pop up that should disappear when the user clicks outside of it. Following the example set by this question, I came up with this:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
id: win
width: 360
height: 360
color: "black"
Rectangle {
id: block
width: 20
height: 20
color: "green"
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
console.log( "Entered" );
menu.visible = true;
menu.requestActivate();
}
}
Window {
id: menu
width: 100
height: 100
x: win.x + block.width
y: win.y + block.height
flags: Qt.Popup
color: "red"
visible: false
onActiveChanged: {
console.log( "Pop up:", active );
if ( !active ) {
visible = false;
}
}
}
}
onActiveChanged: {
console.log( "Main win:", active );
}
}
However the popup does not disappear when clicked outside of, the debug output is:
// Main win opens
qml: Main win: true
// Green square entered
qml: Entered
qml: Main win: true
qml: Pop up: true
// Clicked outside of the pop up
qml: Pop up: true
qml: Main win: true
As you can see the main window does not lose focus when the pop up becomes active, so when the user clicks outside of it the overall focus does not change. So how is this approach supposed to work!?
The solution I found feels quite hackish, but it works, it's self-contained, and it's scalable.
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
id: win
width: 360
height: 360
color: "black"
Rectangle {
id: block
width: 20
height: 20
color: "green"
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
menu.visible = true;
}
}
Rectangle {
id: menu
width: 100
height: 100
anchors.top: parent.bottom
anchors.left: parent.right
color: "red"
visible: false
Loader {
id: exitAreaLoader
Component {
id: exitArea
MouseArea {
x: 0
y: 0
parent: menu.getRoot()
width: if ( parent !== null ) { return parent.width }
height: if ( parent !== null ) { return parent.height }
onPressed: {
var pnt = Qt.point( mouse.x - menu.parent.width,
mouse.y - menu.parent.height );
if ( !menu.contains( pnt ) )
{
console.log( "Closing", pnt );
menu.visible = false;
exitAreaLoader.sourceComponent = undefined;
} else {
mouse.accepted = false;
}
}
}
}
}
onVisibleChanged: {
if ( visible ) {
console.log( "Opening" );
exitAreaLoader.sourceComponent = exitArea;
}
}
function getRoot() {
var par = parent;
while ( par.parent !== null ) {
par = par.parent;
}
return par;
}
}
}
}
The general principle is that when the menu/popup/drop down is made visible a Loader creates a MouseArea as the most descendent child in the entire QObject tree (this guarantees that all mouse events within the window will be received by it). However this means that mouse events that are destined for whatever the popup contains are also captured by the MouseArea, so we have to explicitly test for that and reject the event.
It's a real shame that there's not an official object for this, one was requested years ago and it seems to have adopted by Ubuntu, but that's not very useful for cross-platform development.

Resources