Is there a defined order of execution of changes when an Item changes state?
For clarity, my question is not about the order of execution of changes within a single state as asked elsewhere, for which the order is undefined. Instead I am asking about the order of execution between states.
Take the following example:
Item {
id: root
state: ""
states: [
State {
name: alpha
PropertyChanges {
target: objectA
visible: true
}
},
State {
name: beta
PropertyChanges {
target: objectB
visible: true
}
},
State {
name: gamma
extends: alpha
PropertyChanges {
target: objectB
visible: true
}
},
]
Item {
id: objectA
visible: false
...
}
Item {
id: objectB
visible: false
...
}
}
If state changes from alpha to beta, do the PropertyChanges of alpha get undone before the PropertyChanges of beta get applied? i.e. does objectA become hidden before objectB becomes visible?
And what's the situation when changing from beta to gamma? Does objectB temporarily get hidden and if so, does this happen before or after objectA becomes visible?
Transitions between states are "linear," meaning only the differences are applied. The State Machine Framework has all the documentation I believe (this is what QML States and Transitions use behind the scenes). You may find the section Using Restore Policy... especially relevant, and it states (emphasis mine):
When this restore policy is set, the machine will automatically restore all properties. If it enters a state where a given property is not set, it will first search the hierarchy of ancestors to see if the property is defined there. If it is, the property will be restored to the value defined by the closest ancestor. If not, it will be restored to its initial value (i.e. the value of the property before any property assignments in states were executed.)
Restore policy is set in the different *Changes QML types (e.g. PropertyChanges::restoreEntryValues) and is usually true by default.
Behavior can be controlled more specifically by using Transition(s) which correspond to the State(s).
You could easily test this for your specific use case by implementing event listeners and printing the output (e.g. onVisibleChanged: console.log(visibile)).
Related
I'd like to create a widget that has a value that can be bound to a value outside of itself, but can also internally set this value. A scenario I'd like to be able to support:
Developer using the widget binds the widget's value to some external value
At run-time, widget value follows any external value changes via this binding
User interacts with widget, setting their own value
Some time later, external value is updated
Ideally, widget value would then return to being bound to the external value
Point 5 does not seem possible when using only bindings. Here is an example where 'textItem' is our imaginary widget:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
property real externalValue: (Math.random()* 50).toFixed(0)
Timer {
running: true
repeat: true
interval: 1000
onTriggered: {
// Simulate externalValue changes out of our control
externalValue = (Math.random()* 50).toFixed(0)
}
}
Component.onCompleted: {
// Unknown external binding set by developer using textItem widget
textItem.text = Qt.binding(function(){return externalValue})
}
Column {
Text {
id: textItem
text: ""
property real internalValue: (Math.random()* 50).toFixed(0)
Binding on text {
id: binding
value: textItem.internalValue
when: false
}
}
Button {
text: "Change Internal Value"
onClicked: {
textItem.internalValue = (Math.random()* 50).toFixed(0)
binding.when = true
}
}
}
}
So textItem.text listens to the externalValue binding until a user interacts with the button, which then binds textItem.text to the user-set internal value. Assuming textEdit is a black-box widget, and it has no concept of externalValue before its runtime binding, is there any way for textEdit to internally listen to the overridden externalValue binding and restore it (by setting binding.when = false) the next time that externalValue is updated by the timer?
One way to make the scenario work would be to use direct assignments in place of all of the bindings, but this seems like it would cause a confusing widget API since I can't stop users from trying to use bindings which would confusingly get broken by the widget's black-box internal assignments...
You can temporarily override bindings using States, like in the code below.
The real problem here is detecting when the external value has changed, in my solution I'm using a Timer for this, but your requirements ask for the external value to change again. Since you are externally binding to property text and also overriding the binding to text you temporarily loose the change signals from the external binding, hence cannot undo the temporary binding.
If you have control over the widget, I would implement a property which should be set externally and internally assign that value to where it should go. This gives you the ability to receive the changes and for example stop the tempBoundedTimer (Since I still think you should have a timer to not indefinitely override the binding in case the external value fails to update).
If you don't have control over the widget, I would settly down on a suitable interval for tempBoundedTimer
(In any case, I don't adding a Timer in each instance of the widget is very nice)
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
property real externalValue: (Math.random()* 50).toFixed(0)
Timer {
running: true
repeat: true
interval: 1000
onTriggered: {
// Simulate externalValue changes out of our control
externalValue = (Math.random()* 50).toFixed(0)
}
}
Component.onCompleted: {
// Unknown external binding set by developer using textItem widget
textItem.text = Qt.binding(function(){return externalValue})
}
Column {
Text {
id: textItem
text: ""
property real internalValue: (Math.random()* 50).toFixed(0)
Timer {
id: tempBoundedTimer
repeat: false
interval: 2000
}
states: [
State {
when: tempBoundedTimer.running
PropertyChanges {
target: textItem
text: "internal:" + internalValue
}
}
]
}
Button {
text: "Change Internal Value"
onClicked: {
textItem.internalValue = (Math.random()* 50).toFixed(0)
tempBoundedTimer.start()
}
}
}
}
BTW, I think your Binding object should actually also work if the when is bound to tempBoundedTimer.running, but I couldn't get it to play nice. It seems the Qt.binding has priority
I'm a C++ developer, now studying about GUI development using QML in QtQuick.
In GUI creation, only one screen is visible to the user.
And based on user interaction, the screens are switched.
But what actually happens behind?
There are lot of info only on how to design a single screen, but very less resource for how to manage the transitions of their states.
Are all the screens and components loaded when starting the application and change the layer order to display once screen,
OR
after an user action, the new screen is built, loaded and old is destroyed ( only one screen is in memory at a time)
What is the term for this type of handling.
It would be so helpful to point to where i can find such information.
If you can't understand my question,please let me know. I will rewrite again!!
There is a convenient ready-made solution available: StackView. It provides built-in transitions for pages that slide/fade in and out.
StackView {
id: stack
initialItem: Page {
Button {
text: "Push"
anchors.centerIn: parent
onClicked: stack.push(Qt.resolvedUrl("OtherPage.qml"))
}
}
}
StackView allows you to push items, URLs and components. When pushing either of the latter two, StackView automatically creates and destroys the instance when appropriate. For example, if you push multiple URLs or components, it will only instantiate the top-most one that becomes the current item on the stack. Once you pop items off the stack, it creates an instance of the item underneath on demand once it becomes the current top-most item on the stack. StackView also allows you to replace one or more items in the stack. When popping or replacing dynamically created items off the stack, it automatically destroys the instances after the respective transitions are finished.
One of the possible options to switch between different screens using states:
ColumnLayout {
id: controls
states: [
State {
id: state1
name: "STATE1"
property list<Item> content: [
Loader {
...
},
MyItem {
...
}
]
PropertyChanges {
target: controls
children: state1.content
}
},
State {
id: state2
name: "STATE2"
property list<Item> content: [
MyHud {
...
}
]
PropertyChanges {
target: controls
children: state2.content
}
}
]
}
You can use Loader to load different qml-files or qml-components.
Example:
import QtQuick 2.0
Item {
width: 200; height: 200
Loader { id: pageLoader }
MouseArea {
anchors.fill: parent
onClicked: pageLoader.source = "Page1.qml"
}
}
I have a QML Loader, which can have two states. Depending on the state, different content is to be loaded.
Loader
{
id: mainLoader
anchors.fill: parent
state: "state1"
states:
[
State
{
name: "state1"
PropertyChanges {target: mainLoader; source: "page1.qml"}
},
State
{
name: "state2"
PropertyChanges {target: mainLoader; source: "page2.qml"}
}
]
}
There are two buttons elsewhere in the application, which have onClicked: mainLoader.state = "state1" and onClicked: mainLoader.state = "state2", respectively.
Although the state does indeed get changed (verified by printing a debug message in the StateChangeScript), it is always page 1 which remains loaded at all times, page 2 never appeares.
What I don't understand, are the following:
if I don't assign a default state (so I remove state: "state1"), the two buttons work, and both pages are displayed correctly according to which button I pressed. Of course, before any button is pressed, the Loader is empty.
if I add asynchronous: true to the loader, the two buttons work fine.
There are several simple, but not very elegant solutions, for example
just assign a default source instead of a default state. I don't like it as then I'll have 3 states, including the empty one, and I need to refer to the state of the loader at other places.
stick to asynchronous operation.
use Component.onCompleted: state = "state1" instead of state: "state1"
However, I would like to understand what lies behind this behavior. I know that properties are not filled in the order they are written in the qml files, but by the time I press the buttons, the Loader was already completed a long time ago.
The when property of a QML State type can be used to control when the state should be applied by a boolean expression.
When I set the state property of an QML Item explicitly that seems to override the when properties of all state objects as in the following code:
Item {
id: item
anchors.fill: parent
states: [
State {
name: "when state"
when: 1 === 1
},
State {
name: "explicit state"
}
]
onStateChanged: console.log("state = " + state)
MouseArea {
anchors.fill: parent
onPressed: item.state = "explicit state"
onReleased: item.state = ""
}
}
The initial state of item is the "when state" as it is determined by the when expression. When the MouseArea is pressed the state turns to the "explicit state". After release of the mouse button the state turns to the default state (""). It seems that the when state evaluation is ceased.
Is there any way to "recover" to the "when" state evaluation somehow?
The when properties on states are evaluated:
When a group of states is initially created
When a state's when is altered (either by being set, or when any of the properties that one of the when bindings rely on change)
So no, you can't really "reset" it. I'd suggest not mixing the two methods if you can avoid it.
I'm trying to implement a fitness application that displays the current exercise name then shows a progress bar during the resting periods. When the progress bar is fully filled, the name of the next exercise is displayed, then a progress bar is shown when it is completed, and so on.
Note that I'm using a progress bar for this example but I'll have my own widget in the real application.
My system has two states :
exercise the value of the progress bar is 0 and stays 0
rest the value of the progress bar goes from 0 to maximumValue over the duration of the resting period
So it goes like this :
the application is in "exercise" state and shows the name of the current exercicse
the user execute the exercise and click on the progress bar when he's done
the application switches to the "rest" state
the user rests while the progress bar is being completed
after the progress bar is completed the application switches back to the "exercise" state and displays the name of the next exercise
My issue is with step 5 : I do not know how to switch back to another state at the end of a transition. I've tried to change the "state" property during a PropertyChanges and time it at the end of a transition with a SequentialAnimation, but I get this error message :
QML StateGroup: Can't apply a state change as part of a state definition.
Here is some sample code :
import QtQuick 2.0
import QtQuick.Controls 1.1
ProgressBar {
id: root
width: 200
height: 48
minimumValue: 0
maximumValue: 100
value: 76
function switchState() {
if (state == "exercise")
{
state = "rest"
return
}
if (state == "rest")
{
state = "exercise"
return
}
}
state: "exercise"
states: [
State {
name: "exercise"
PropertyChanges {
target: root
value: 0
}
},
State {
name: "rest"
PropertyChanges {
target: root
value: maximumValue
// error: QML StateGroup: Can't apply a state change as part of a state definition.
// state: "exercise"
}
}
]
transitions: [
Transition {
to: "rest"
PropertyAnimation {
target: root
properties: "value"
duration: 1000
}
}
]
MouseArea {
anchors.fill: parent
onClicked: parent.switchState()
}
}
How can I switch to another state at the end of a state transition ?
I believe you can do this by using the RunningChanged signal from the transition:
transitions: [
Transition {
to: "rest"
PropertyAnimation {
target: root
properties: "value"
duration: 1000
}
onRunningChanged: {
if ((state == "rest") && (!running))
switchState();
}
}
]
QML Objects have an associated signal for property changes (on<Property>Changed). The associated handlers are usually not documented in the objects' references, but are implicitly available due to the property's existence. See this.
UPDATE
I proposed a wrong solution because a mistake, so excuse me please.
Fixing its problem results same solution as #GabrielF one. So I deleted it to avoid repeating.
However below solution works great and gives you fine control within a Transition/Animaiton.
Also another way!
transitions: [
Transition {
to: "rest"
SequentialAnimation {
PropertyAnimation {
target: root
properties: "value"
duration: 1000
}
ScriptAction { script: switchState() } //<----
}
}
]
Please note that with SequentialAnimation within a transition animation, you don't need to define a middle state, if you like to just ensure that an specific animation is completed.
Goodluck - S.M.Mousavi