Qt introduced SCXML as a new feature in 5.7, which serves as a notation format for building a sophisticated state machine for your application. A state machine is a mechanism for which a program or a widget of a program changes its properties based on the current state you defined for it. We have seen how a Push Button changes its appearance when the mouse hovers on it or when pressed by the user. These are different states of Push Button and its behavior is determined and executed by the state machine.
With SCXML, you can define a more sophisticated state machine for your program and save it in the human-readable SCXML file format for Qt to parse and process. Qt will then generate C++ classes according to the content of the SCXML file to drive the state machine you defined earlier. Qt also provides a graphical editor for you to easily define your state machine without manually writing the content of the SCXML file. The editor looks something like this:
The editor consists of four different parts:
- Common States Panel (A): This is where you pick and choose the type of state you want to add to the state editor.
- State Editor (B): This is where you define the state machine. You can drag and drop different types of states from the Common States panel here and link them together to form state transitions.
- Structure Panel (C): The panel here shows the structure of your state machine in the form of a tree list. All of the states that you placed in the state editor will be shown here along with its hierarchical relationship.
- Attributes Panel(D): This window shows the attributes of the selected state, which you can view and edit to suit your needs.
To create an SCXML file, do the following:
- Go to File | New File or Project.
- Then, pick the State Chart option under Files and Classes | Modeling and press the Choose... button.
- Once you've created the file, Qt Creator will open it up automatically with the SCXML editor. You will see an empty SCXML file just like that in the preceding screenshot.
- You can start creating your first state machine by dragging the Initial node to the state editor to kick-start the state machine.
- After that, you can drag some State nodes to the state editor and link them together by first selecting one of the nodes and click on the arrow icon. Then, a dashed-line arrow will follow your cursor whenever you go.
- Left-click on the state editor to create a breakpoint to change the direction of the arrow, or right-click on one of the other nodes to establish a transition from the previous node that you have selected. When a State node links with multiple other nodes, it creates an option for the state machine to pick and choose. The state machine will choose an appropriate path by following the condition that you set later in your code. For now, you just define what are the states available for the state machine. The following diagram shows an example of what a state machine looks like inside the SCXML editor:
The preceding diagram shows the new graphical editor for the
SCXML section, we see that, starting from the top, there is an Initial node, which represents the initial state of the program, then it will travel down to a State node called State_1. From there onward, it might go to a different state depending on the condition that you set in your code. The arrows indicate the direction in which the state can travel. Lastly, the state will eventually end up at the Final node, at which point the state machine will cease its operation.
- Finally, place a Final node to define the final state the state machine can reach. Once it reaches this state, the state machine has accomplished its task and ceases to operate anymore.
Other than that, there is a Parallel node that you can use if it fits your needs. Unlike the State node, which only goes to one other node at once, the Parallel node can travel to multiple other states simultaneously, hence its name.
The History node is much more complicated than the other nodes. It represents a history state that has been saved previously. You can use this node to travel back to a previous state from a distant state that is not linked directly. To save a state to the history stack, create a History node as a child of a State node. When the state machine detects the existence of the History node at runtime, it will automatically record the current state. The state machine will travel back to this state whenever it reaches the History node during transition. The following diagram shows an example of how to use the History node:
From the preceding diagram, we see that when traveling from State_1 to the History state through Transition_20, your program's state will go return to State_5, which is where the other History node is located before State_1. If you have multiple History states in your state machine, then it will pick the last one that it stumbled upon.
Other than adding a History node into a State node, you can also add State nodes into another State node to establish a parent-children relationship. Recursive states like these increase the complexity of your state machine and you should be extra careful when going for such an approach.
For each State node, you can apply additional elements to it, called Executable Content. Following are the list of Executable Content elements you can add to your states:
- raise: raise events.
- send: communicate with external entities.
- script: run scripts and do something with the values in the data model.
- assign: add or modify a value in the data model.
- cancel: cancel an action execution.
- log: record information into a log.
- if: execute actions based on a condition.
- foreach: loop through a set of values and execute an action for each of them.
You can add these elements to a state by right-clicking on its node and select one of the preceding from the onentry or onexit option on the pop-up menu. Most of these Executable Content elements are methods used to manipulate your state's data model when entering or exiting the state. Therefore, you can only add these elements to State or Parallel nodes that have datamodel assigned to it. If you have not done so, right-click on the State node you want to apply and select datamodel.
To add data to datamodel, right-click on it at the Structure Panel and select data on the pop-up menu. You can now assign data to datamodel and manipulate it through the Executable Content element we talked about earlier.
Let's try it out ourselves:
- First, create a new Qt Quick project and add the scxml module to our project:
QT += quick scxml
- Then, set up our GUI like this:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: window
visible: true
width: 640
height: 480
title: qsTr("State Machine")
Rectangle {
id: rectangle
width: 200
height: 200
color: "red"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
- There's nothing fancy here, just a simple rectangle that stays in the middle of the window:
- After that, create an SCXML file and name it statemachine.scxml.
- Once you have created the file, please make sure you also add this file into the project resource as we're going to use it later in our QML code.
- After that, open up the file with Qt Creator and create a simple state machine that looks like this:
As can be seen from the preceding diagram, the preceding state machine consists of three different states: red, green, and blue. We will use these states to change the color of the rectangle we defined in our QML code earlier. Other names that you can see from the state machine such as start, goGreen, goBlue, and goRed are the names of the transitions.
- Once you're done, right-click on each of the State nodes (red, green, and blue) and select onentry | send from the pop-up menu.
- Set the event attribute of the send executable content of red as goGreen and set its delay as 2s. This will allow the state machine to automatically trigger the goGreen transition two seconds after entering the red state. The following screenshot shows you where to set the values on the Attributes window:
Repeat the preceding steps for the green and blue states, except change the event attributes to goBlue and goRed respectively. This means that the state machine will keep running in a circle until we stop the program.
- Next, open up our main.qml file and import the QtScxml module to our QML code:
import QtScxml 5.8
- After that, add the following code under our window object:
StateMachineLoader {
source: "qrc:///statemachine.scxml"
stateMachine.onReachedStableState: {
if (stateMachine.red)
rectangle.color = "red"
else if (stateMachine.green)
rectangle.color = "green"
else if (stateMachine.blue)
rectangle.color = "blue"
}
}
We created a StateMachineLoader object, which is used to load the SCXML file we created earlier and automatically generate the state machine code for us. However, we must write the logic ourselves, which can be seen under the onReachedStableState slot function. This function will be triggered when our state machine has successfully changed its state. Then, we check whether each of the states is the current state (it will return true if it is the current state) and change the color of the rectangle shape accordingly.
If we run the code now, we will see the rectangle changes its color every two seconds. That's it—we have learned how we can make use of the new SCXML editor provided by Qt and create a simple state machine for our application.