DevDays lessons

How to use a state machine to drive a QML UI

One of the more interesting presentations at DevDays 2011 was given by Marius from Cutehacks. After hearing this presentation I had a bit of an epiphany when it comes to state machines. I had been using them in Qt for quite a while, but I was never happy with how they meshed with the rest of the components. Indeed, there was a simpler and clearer way to do it, and that's what I'm going to talk about in this article - the Cutehacks state machine design and how I used it in a real app.

The theory

The actual presentation slides are here and they have some sample code if you're interested, but off the top of my head, here's how Cutehacks does it:

  1. First of all, they noticed that implementing a state machine in pure QML was not the best idea. To tell you the truth, I was also surprised when first encountering QML states - sure they were very flexible, but the result wasn't really a formal state machine. In QML you can switch from any state to any other state and even to ones that don't exist, because the current state is just a name stored in a string property.
  2. One solution to the above problem is implementing the state machine in C++. Cutehacks inherited their state machine from the QStateMachine class and declared all the states as private members. That state machine had a signal corresponding to each state that shared that state's name. e.g: state mFoo matched signal foo() inside the state machine.
  3. Each state had an isActive property that was exposed together with the state to QML. In order to make isActive work, they inherited from QState and reimplemented the onEntry and onExit events to toggle the property.

So far we have our own state machine with states and transitions neatly defined in C++ in the constructor, we have signals that can be used to transition between states and each state has an isActive property. How is this connected to QML? Each C++ state was matched with a QML state having the "when" property set to "cppState.isActive".

It turns out that in QML you can call the signals of any C++ object that has been exposed to QML! So in the UI files when pressing a button you could say something like cppStateMachine.mainState() and then the mainState() signal of the cppStateMachine C++ object would be called. If the current state had a transition to mainState, then the transition would happen, mainState.isActive would become true and the corresponding QML state for mainState would be entered.

Finally, the entire point is that in the QML state you could then change some random QML properties on entry or even run some JavaScript code.

The practice

When coming back from DevDays, I attempted to use this state machine design in a C++/QML app that I was working on. Previously I was using only QML states, but it was reasonably easy to refactor the code to use a C++ state machine in the background. The implementation felt more robust, as I could just take a look in the C++ state machine implementation to see how the app behaved, instead of hunting for the elusive assignments to the "state" property. It was also easier to make changes, since the UI changes were well defined in one place now.

Soon after that, I ran into quite a big issue though... In the app I was working on, transitions were important - for instance when going from A -> B something was supposed to happen, but when going from C -> B, or even B -> A something different had to happen. My first thought was to use the Transition QML element. After adding a few transitions for testing, I noticed that the Transition animations and code were not being executed.

Immediately I suspected that "when" was the problem. If you'll remember, state transitions were made in a specific way: each QML state had "when" set to the C++ state's isActive property. In a C++ transition from A -> B, the QML transitions were A -> ""-> B instead of A -> B. The empty string is the default QML state. Since A.isActive became false, the state machine transitioned to the default state automatically. Then it transitioned to B when B.isActive became true.

As one can see QML state machines are quite peculiar...

The way I solved this was by integrating the C++ state machine more tightly with the QML state machine:

  1. I removed the isActive property for the C++ states and the "when" property for all QML states.
  2. For each qmlState I set its name to cppState.objectName, which is just the the QState's QObject name.
    State {
               name: cppStateMachine.initState.objectName [...]
  3. In MyState::onEntered I emitted a signal called enteredWithObjectName(QString) and passed the objectName() to it.
    void MyState::onEntry(QEvent *event)
        emit enteredWithObjectName(objectName());
  4. When setting up the state machine, I connected the enteredWithObjectName for each state to the C++ state machine's stateChanged(QString) signal.
    connect(mInit, SIGNAL(enteredWithObjectName(QString)), SIGNAL(stateChanged(QString)));
  5. In QML I wrote a signal handler called onStateChanged and in there I set the QML "state" property to the name parameter passed to the signal.
    Connections {
            target: cppStateMachine
            onStateChanged: {
                console.debug("changing state to " + objectName);
                rootWindow.state = objectName;

The end result was that now transitions were done directly from on state to another without going through the default "" state. When a QML object called cppStateMachine.mainState(), a C++ state transition happened, the C++ state mainState was entered and it emitted its QObject.objectName. The state machine then passed that on to QML and the QML set the state property to that name, effectively triggering a QML state transition. Since each QML state had the same name as the C++ QState object, it worked.

This implementation allowed for a lot of flexibility with state changes. If some transitions were similar, they could be grouped in a QML Transition with from set to ''(star/any state). If there was a special case, from could be set to specialState.objectName, and the QML transition would only execute for that state, while '' would work for the rest.

The advantage with C++ is that the state machine API is already implemented, but my assumption is that a similar state machine API can be written in JavaScript, so that you could have a pure QML/JS project if that's what you want. The important ideas when building such an API are to respect the definition of a FSM in your design and to encapsulate operations such as state and transition set up and transition triggers.

Posted on Dec 18 2011
Written by raz