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.
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
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)).
I read the documentation about:
focus property
activeFocus property
forceActiveFocus() method
FocusScope object
and keyboard focus in QtQuick
but it is still not clear when should someone use forceActiveFocus() method over setting the focus property to true or vice versa.
As the documentation states:
For very simple cases simply setting the focus property is sometimes sufficient.
Such a simple case would be, if the Item which gets focus: true is not enclosed by a FocusScope that might have not the focus.
Then it continues with:
> Within each focus scope one object may have Item::focus set to true. If more than one Item has the focus property set, the last type to set the focus will have the focus and the others are unset, similar to when there are no focus scopes.
> When a focus scope receives active focus, the contained type with focus set (if any) also gets the active focus. If this type is also a FocusScope, the proxying behavior continues. Both the focus scope and the sub-focused item will have the activeFocus property set.
From where we learn, about the fact that setting focus: true is not sufficient, if it is for an Item that is a successor to a FocusScope as this FocusScope would need to have activeFocus su that the successor Item will receive activeFocus. This is recursive, and means, that the FocusScope will need to have focus: true and a possible predecessor FocusScope needs the activeFocus and so on. Which results in some kind of focus tree
This focus tree consists out of inner nodes that are the FocusScopes and leafs that are Items. A FocusScope might be a leaf as well, but I don't know why it should.
In this tree, each FocusScope may have up to one child node (either Item (leaf) or FocusScope (inner node) that has focus === true. Traversing this tree, following the path where all traversed nodes have focus === true the traversed nodes have also activeFocus === true. As each FocusScope may have only up to one child node with focus === true there is only one such path.
Column {
FocusScope {
focus: false
width: 100
height: 100
Text {
focus: true
text: 'has focus ' + focus + '\nhas activeFocus ' + activeFocus
}
}
FocusScope {
focus: true
width: 100
height: 100
Text {
focus: true
text: 'has focus ' + focus + '\nhas activeFocus ' + activeFocus
}
}
}
Here we have two FocusScopes. Both have a child that has focus, but as only the second FocusScope has the focus itself, it's child has activeFocus.
The use of forceActiveFocus() traverses the focus tree, and sets focus to true for each node on the way, so the Item has activeFocus at the end.
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 answer provided by #TheBootroo here: link
provides a way to load and change between QML files/screens/views. But when doing it like this how can one use signal and slots?
One can access the items created by the repeater by using the Repeater::itemAt(index) method, but since I don't know in what order the items are loaded I don't know what index screen2, screen3, screen4 etc. is at.
Is there any way to solve this or do one have to instantiate all the screens in memory at start up?
My code below:
main.qml:
//List of screens
property variant screenList: [
"main",
"screen2",
"screen3",
...
]
//Set this to change screen
property string currentScreen: "main"
Repeater {
id: screens
model: screenList
delegate: Loader {
active: false;
asynchronous: true
anchors.fill: parent
source: "%1.qml".arg(modelData)
visible: (currentScreen === modelData)
onVisibleChanged: {
loadIfNotLoaded()
}
Component.onCompleted: {
loadIfNotLoaded()
}
function loadIfNotLoaded () {
//To load start screen
if(visible && !active) {
active = true;
}
}
}
}
Connections {
target: screens.itemAt(indexHere)
//screen is here the string passed with the signal from screen2.
onChangeScreen: currentScreen = screen
}
Button {
id: button1
text: "Go To Screen 2"
onClicked: {
currentScreen = "screen2"
}
}
And in screen2.qml:
signal changeScreen(string screen)
Button {
text: "Go To Main"
onClicked: {
changeScreen("main")
}
}
One can access the items created by the repeater by using the Repeater::itemAt(index) method, but since I don't know in what order the items are loaded I don't know what index screen2, screen3, screen4 etc. is at.
The order of the items the Repeater instantiates is actually defined - the items will be instantiated in the order of the model, so in your case "main" will be the first, then "screen2" and so on. Now, each item inside of the Repeater is a Loader - the Repeater will create 3 Loaders in a defined order. The Loaders themselves load their source item on demand.
I think the only thing missing in your code is that the Connections refers to the Loader, but you need it to refer to the item the Loader creates. So change the Connections to
Connections {
target: screens.itemAt(indexHere).item
onChangeScreen: currentScreen = screen
}
Notice the additional .item.
After fixing this, there is one additional problem though: The Repeater hasn't yet instantiated its items when the Connections element is created, so screens.itemAt(indexHere) will return null. One way to fix this would be to use a currentIndex property instead of a currentScreen property and use that in the binding for target, and set currentIndex after the Repeater has instantiated its items:
property int currentIndex: -1
...
Connections {
target: screens.itemAt(currentIndex).item
...
}
Component.onCompleted: currentIndex = 0
Even easier would probably be to put the Connections element inside the Repeater's delegate, i.e. have 3 Connections instead of one (assuming you have 3 screens).