I'm trying to create a QML based application that performs an action at intervals. It seemed that states would be the ideal way you do this but in fact the QML meaning of the word state does not seem to accord with that used in the finite state machine literature. In particular the when property doesn't cause the state to be entered at the moment that the when expression becomes true, instead it means that the state is valid only while the expression is true. This means that if the expression becomes false then the UI exits from that state and enters whatever other state has a true when expression, if there are none then it enters the default state.
In an attempt to avoid this I decided to change from state to state explicitly by assigning values to the state property. This works a little better but one odd behaviour remained unchanged and that is that the text of a button suddenly changed to its default value even though my code had not, or so I thought, caused a state change.
At first I thought this might be a peculiarity of the Harmattan Button element so I recreated just enough of the UI in pure QML to exercise the code and found that the problem still exists.
The code is two files. The main file is qml-test.qml which contains the state definitions and uses an element defined in Controls.qml. There is a single button implemented as a Rectangle, Text, and MouseArea. The states are IDLE, INITIAL_DELAY, and RUNNING. Here is the console log:
Code:
Starting /home/whitefoot/QtSDK/Desktop/Qt/4.8.1/gcc/bin/qmlviewer -I /home/whitefoot/QtSDK/Desktop/Qt/4.8.1/gcc/imports /home/whitefoot/qt-projects/qml-test/qml-test.qml
Qml debugging is enabled. Only use this in a safe environment!
Text changed to: Start
state: IDLE
become checked
change state to initial
Text changed to: Stop
Text changed to: Start
Text changed to: Stop
state: INITIAL_DELAY
Text changed to:
Text changed to: Stop
state: RUNNING
Text changed to:
Do timed action
Do timed action
Do timed action
Do timed action
Do timed action
Do timed action
become unchecked
change state to IDLE
Text changed to: Start
state: IDLE
become checked
change state to initial
Text changed to: Stop
Text changed to: Start
Text changed to: Stop
state: INITIAL_DELAY
Text changed to:
Text changed to: Stop
state: RUNNING
Text changed to:
Do timed action
Do timed action
Do timed action
become unchecked
change state to IDLE
Text changed to: Start
state: IDLE
/home/whitefoot/QtSDK/Desktop/Qt/4.8.1/gcc/bin/qmlviewer exited with code 0
Notice the lines beginning "Text changed to:". The only explicit changes are to "Start" and "Stop". Everything works as expected:
- Click start: timer begins, enter INITIAL_DELAY state, change button text to "Stop"
- Timer times out after 3 seconds, enters RUNNING state, executes timed action
- Timer times out after 1 second, executes timed action
- ...
- Click button, enter IDLE state, timer stops, button text updated to "Start"
except that the button text is changed to an empty string, just after the state changes from INITIAL_DELAY to RUNNING. This empty string isn't even the default value. If this were merely a momentary change it might be acceptable but it persists until the next explicit state change.
I'm new to QML so I'm quite ready to believe that I have missed something obvious.
It seems that there are ways around this using C++ but really I'd like to avoid that if I can as I haven't used it in so many years. See http://blog.codeimproved.net/tag/qml/.
Can anyone suggest a fix, workaround, correction, or clarification?
Here is the code:
Code:
// qml-test.qml
import QtQuick 1.1
Rectangle {
width: 360
height: 360
state: "IDLE"
states: [
State {
name: "IDLE"
PropertyChanges {
target: controls;
timer.running: false
startButton.text: "Start"
}
StateChangeScript {
name: "idle"
script: {
console.log("state: " + state.toString())
}
}
},
State {
name: "INITIAL_DELAY"
PropertyChanges {
target: controls;
startButton.text: "Stop"
timer.interval: controls.initialDelay
timer.running: true
}
StateChangeScript {
name: "initial_delay"
script: {
console.log("state: " + state.toString())
}
}
},
State {
name: "RUNNING";
PropertyChanges {
target: controls;
timer.interval: controls.interval;
timer.repeat: true;
timer.running: true
}
StateChangeScript {
name: "running";
script: {
console.log("state: " + state.toString())
}
}
}
]
Controls{
id: controls
x:0
y:0
onTriggered: {
if (parent.state != "RUNNING"){
parent.state = "RUNNING"
}
console.log("Do timed action")
}
onStart: {
console.log("change state to initial")
parent.state = "INITIAL_DELAY"
}
onStop: {
console.log("change state to IDLE")
parent.state = "IDLE"
}
}
}
//Controls.qml
import QtQuick 1.1
Column{
//width: 100
//height: 62
id: controls
property int initialDelay: 3000
property int interval: 1000
property alias startButton: startButton
property alias timer: timer
//property bool tick: false;
signal start();
signal triggered();
signal stop();
Timer{
id: timer
//interval: 3000
repeat: true
triggeredOnStart: false
onTriggered: {
//tick = true;
parent.triggered()
}
}
Rectangle {
id: startButton
property bool checked: false
property string text
onTextChanged: {
buttonText.text = text
}
border.width: 1
border.color:"black"
visible: true
color: "white"
width: 80
height: 20
Text {
id: buttonText
anchors.fill: parent
text: "default"
onTextChanged: {
console.log("Text changed to: " + text)
}
}
MouseArea{
anchors.fill: parent
onClicked: {
if (parent.checked) {
console.log("become unchecked");
parent.checked = false
parent.color = "white"
buttonText.color = "black"
controls.stop();
}else{
console.log("become checked");
parent.checked = true
parent.color = "blue"
buttonText.color = "white"
controls.start();
}
}
}
}
}