A Named QStateMachine

Building GUIs Using State Machines

Developing UIs is a complex business. To make your life easier you should consider using state machines for your screens. State machines allow for enabling and disabling user interface elements depending on the state your screen is in, e.g. selection of a certain radio button or fullscreen mode. Keeping track of when to enable or disable which UI element should not be your concern. That’s why in Qt there’s a State Machine Framework available.
Qt’s Signals And Slots in combination with The Property System and the state machine framework form a powerful tool at your hand.

Tools State Machine

Closing the Gap

The state machine framework provides a QStateMachine and QState class in a very simplistic way. Contrary to your nice state chart from your favorite UML modeling tool it lacks any naming. Hence, debugging a state machine is quite cumbersome as you’re confronted with pointer values let alone to know what state your state machine currently is in. The QStateMachine is just a black box if you don’t keep the references to all your states.

Black Box

So, I started with subclassing QStateMachine and QState to introduce naming and tracking.
Now, my NamedState class takes a name in its constructor and stores it’s address as the latter is sometimes all you get when debugging.

1
2
3
4
5
6
7
NamedState::NamedState(const QString &name, QState* parent) :
    QState(parent),
    m_name(tr("%1").arg(name)),
    m_address(tr("%1").arg(QString("").sprintf("%p", this)))
{
    initializeConnections(parent);
}

The method initializeConnections(QState*) redirects in the upper part QState‘s signals to internal slots where a signal with the same name and a NamedState pointer is emitted. But in the bottom part it configures signal chaining to its parent state if that is a NamedState, too. By doing this, the state changes are propagated upwards in the tree of states. Later, the NamedStateMachine and the NamedStateMachineInspector will make use of that concept.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void NamedState::initializeConnections(QState* parent)
{
    connect(this, SIGNAL(entered()),
            SLOT(enteredState()));
    connect(this, SIGNAL(exited()),
            SLOT(exitedState()));
    connect(this, SIGNAL(finished()),
            SLOT(finishedState()));
    connect(this, SIGNAL(propertiesAssigned()),
            SLOT(propertiesAssignedState()));
 
    NamedState* nParent = dynamic_cast<NamedState*>(parent);
    if (nParent)
    {
        connect(this, SIGNAL(entered(NamedState*)),
                nParent, SIGNAL(entered(NamedState*)));
        connect(this, SIGNAL(exited(NamedState*)),
                nParent, SIGNAL(exited(NamedState*)));
        connect(this, SIGNAL(finished(NamedState*)),
                nParent, SIGNAL(finished(NamedState*)));
        connect(this, SIGNAL(propertiesAssigned(NamedState*)),
                nParent, SIGNAL(propertiesAssigned(NamedState*)));
        connect(this, SIGNAL(log(QString)),
                nParent, SIGNAL(log(QString)));
    }
    emit log(tr("Initialized %1 (%2)").arg(m_name).arg(m_address).toAscii());
}
 
void NamedState::enteredState()
{
    emit entered(this);
}
 
void NamedState::exitedState()
{
    emit exited(this);
}
 
void NamedState::finishedState()
{
    emit finished(this);
}
 
void NamedState::propertiesAssignedState()
{
    emit propertiesAssigned(this);
}

With my NamedStateMachine class you can create a named state machine:

1
2
m_toolStateMachine(QSharedPointer<NamedStateMachine>(
                   new NamedStateMachine(tr("Tools State Machine"))))

I have a name!

The class overrides addState(QAbstractState* state) to hook on signals provided by my NamedState class. Basically the signals are just propagated via signal chaining. By doing that you only need to connect to a NamedStateMachine’s signals in order to get notified upon any state change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void NamedStateMachine::addState(QAbstractState* state)
{
    NamedState* nState = dynamic_cast<NamedState*>(state);
    if (nState)
    {
        connect(nState, SIGNAL(entered(NamedState*)),
                SIGNAL(enteredState(NamedState*)));
        connect(nState, SIGNAL(exited(NamedState*)),
                SIGNAL(exitedState(NamedState*)));
        connect(nState, SIGNAL(finished(NamedState*)),
                SIGNAL(finishedState(NamedState*)));
        connect(nState, SIGNAL(propertiesAssigned(NamedState*)),
                SIGNAL(propertiesAssignedState(NamedState*)));
        connect(nState, SIGNAL(log(QString)),
                SIGNAL(log(QString)));
        emit log(tr("state machine %1 (%3) registered state %2 (%4)")
               .arg(m_name).arg(nState->name())
                 .arg(m_address).arg(nState->address()).toAscii());
    }
    QStateMachine::addState(nState);
}

Additionally, I wrote a NamedStateMachineInspector class that can hook onto a NamedStateMachine in order to inspect signals and transitions, which is great for debugging and Unit-Testing.
The inspector class’ initialize() method optionally takes a QListWidget pointer as input. If it is provided the tracked state changes and log events are added as new items otherwise they are send to qDebug().

1
2
void initialize(QSharedPointer<NamedStateMachine> nStateMachine,
                QListWidget* logList = 0);

Download My Solution

You can download a ready to go ZIP of a Qt-Creator demo project. The sources are made available under the terms of the Eclipse Public License. File header comments must not be removed. The NamedStateMachineInspector should be placed in your unit test project.

Named State Machine Demo Application

The demo project contains a screen to demonstrate the NamedStateMachine. On left side you can choose an entry in a list. The selection changes the state of the state machine. Each state changes some properties of the tool buttons in the middle of the screen. On the right hand side you’ll find a text box listing the inspector’s tracking results of the state machine and its states.

Named State Machine Demo Application

Check out the main.cpp to get an idea of how to use the NamedStateMachineInspector:

1
2
3
4
NamedStateMachineInspector namedStateMachineInspector;
// with logListWidget will add messages as items to list
namedStateMachineInspector.initialize(w.namedStateMachine(),
				      w.logListWidget());

Thanks for reading and enjoy.

2 comments to A Named QStateMachine

Leave a Reply

  

  

  

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>