The properties are assigned on state transitions only, so if you check the value of the property before the machine was started and the event loop had a chance to run, you won't get a valid value.
Additionally, by using the name of the property multiple times, you're liable to commit a typo. Instead of string literals, you should be using named constants for property names - and they should be sufficiently different that random typos will get caught. I.e. property names like kStateA
and kStateB
are probably too similar - a typo would be silent.
So, I'd change the code to be:
static const char kState[] = "state";
QStatemachine m_machine;
idle->assignProperty(&m_machine, kState, Z_IDLE);
cool->assignProperty(&m_machine, kState, Z_COOL);
start_z->assignProperty(&m_machine, kState, Z_START);
qDebug() << m_machine->property(kState).toString();
Also note that this notion of a single current state only applies if your machine is non-hierarchical. A general HSM, as implemented by QStateMachine
, is always in a set of states, available from configuration()
. Most realistic machines should be hierarchical, so your approach won't work.
It is also unclear that there's any use for the _exec
methods, as well as the explicit signals used to transition between states.
Below is a complete example of approaches that you might find useful.
First, let's start with how one might concisely specify your MeasController
:
class MeasController : public QObject {
Q_OBJECT
QStateMachine m_machine{this};
NamedState
m_idle {"Idle", &m_machine},
m_start_z {"Start Z", &m_machine},
m_active_z{"Active Z", &m_machine},
m_downMove{"DownMove", &m_machine};
Transition
m_toStartZ{&m_idle, &m_start_z},
m_toDownMove{&m_active_z, &m_downMove};
Delay
m_d1{&m_start_z, &m_active_z, 1000},
m_d2{&m_downMove, &m_active_z, 2000};
QList<QState*> states() const { return m_machine.findChildren<QState*>(); }
public:
MeasController(QObject * parent = 0) : QObject(parent) {
for (auto state : states())
connect(state, &QState::entered, [state]{ qDebug() << state->objectName(); });
m_machine.setInitialState(&m_idle);
m_machine.start();
}
Q_SLOT void startZ() { m_toStartZ.trigger(); }
Q_SLOT void moveDown() { m_toDownMove.trigger(); }
};
This is a complete specification of the state machine. The states and their transitions and other behaviors are given as members of MeasController
, and are thus easy to find in one place. For ease of debugging, debug output is provided upon each state being entered. Slots are provided to trigger behaviors from outside of the class without having to expose the internals.
Let's now look at the idioms defined by the NamedState
, Transition
and Delay
classes.
A named state idiom, similar to Qt 3's QObject
taking name in the constructor, is useful for debugging at the very least. If you want to assign any other properties of the state, you can do so in this class as well. Since the states have unique variable names and are members of the parent class any integer identifiers aren't necessary:
// https://github.com/KubaO/stackoverflown/tree/master/questions/state-properties-36745219
#include <QtWidgets>
class NamedState : public QState { Q_OBJECT
public:
NamedState(const char * name, QStateMachine * parent) : QState(parent) {
setObjectName(QString::fromUtf8(name));
}
};
A transition class is useful to give a concise way of specifying the structure of your state machine as simple member definitions of Transition
instances:
struct Transition : public QObject { Q_OBJECT
public:
Transition(QState * source, QState * destination) : QObject(source->machine()) {
source->addTransition(this, &Transition::trigger, destination);
}
Q_SIGNAL void trigger();
};
I'm sure that your state machine has more complex behaviors, but generally it helps to factor out a certain behavior into its own class. E.g. say you want a state transition that occurs after a delay:
class Delay : public Transition { Q_OBJECT
int m_delay;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) {
if (m_timer.timerId() != ev->timerId()) return;
m_timer.stop();
trigger();
}
public:
Delay(QState * s, QState * d, int ms) : Transition(s, d), m_delay(ms) {
connect(s, &QState::entered, this, [this]{ m_timer.start(m_delay, this);});
}
};
Finally, we can offer a simple UI to test and visualize the behavior of the machine. The debug output is duplicated in a QPlainTextEdit
for ease of use.

int main(int argc, char ** argv) {
QApplication app{argc, argv};
MeasController ctl;
QWidget w;
QGridLayout layout{&w};
QPushButton start{"Start"};
QPushButton moveDown{"Move Down"};
QPlainTextEdit log;
log.setReadOnly(true);
layout.addWidget(&start, 0, 0);
layout.addWidget(&moveDown, 0, 1);
layout.addWidget(&log, 1, 0, 1, 2);
QObject::connect(&start, &QPushButton::clicked, &ctl, &MeasController::startZ);
QObject::connect(&moveDown, &QPushButton::clicked, &ctl, &MeasController::moveDown);
static QtMessageHandler handler = qInstallMessageHandler(
+[](QtMsgType t, const QMessageLogContext& c, const QString & msg){
static QPointer<QPlainTextEdit> log{[]{
for (auto w : qApp->topLevelWidgets())
for (auto log : w->findChildren<QPlainTextEdit*>()) return log;
Q_ASSERT(false);
}()};
if (log) log->appendPlainText(msg);
handler(t, c, msg);
});
w.show();
return app.exec();
}
#include "main.moc"
This concludes the complete example. This is a longer example in a somewhat similar style.