Instantiating State Machines

Both the dynamically created and the compiled state machines behave in the same way, have the same properties, states, data model, and so on. They only differ in the way they are instantiated. To dynamically create one in C++ from an SCXML file, you can use:


  auto *stateMachine = QScxmlStateMachine::fromFile("MyStatemachine.scxml");

Or, in QML:


  import QtScxml 5.7

  Item {
      property QtObject stateMachine: scxmlLoader.stateMachine

      StateMachineLoader {
          id: scxmlLoader
          filename: "statemachine.scxml"
      }
  }

A compiled state machine can be instantiated the same way as any C++ object:


  auto *stateMachine = new MyStatemachine;

Or:


  MyStatemachine stateMachine;

To use a compiled state machine in QML, you can assign it to a context property:


  MyStatemachine stateMachine;
  QQmlApplicationEngine engine;
  engine.rootContext()->setContextProperty("stateMachine", &stateMachine);

To compile a state machine, the following lines have to be added to a .pro file:


  QT += scxml
  STATECHARTS = MyStatemachine.scxml

This will tell qmake to run qscxmlc which generates MyStatemachine.h and MyStatemachine.cpp, and adds them to HEADERS and SOURCES variables.

After instantiating a state machine, you can connect to any state's active property as follows. For example, if the state machine for a traffic light has a state indicating that the light is red (which has the convenient id "red" in the scxml file), you can write:


  QObject::connect(stateMachine, &TrafficLightStateMachine::redChanged,
      [](bool active) {
     qDebug() << (active ? "entered" : "exited") << "the red state";

And in QML:


  Light {
      id: greenLight
      color: "green"
      visible: stateMachine.green
  }

If you want to be notified when a state machine sends out an event, you can connect to the corresponding signal. For example, for a media player state machine which indicates that playback has stopped by sending an event, you can write:


  QObject::connect(stateMachine, &MediaPlayer::playbackStopped, [](){
      qDebug() << "Stopped!";
  });

And in QML:


  Connections {
      target: stateMachine
      onPlaybackStopped: console.log("Stopped!")
  }

Sending events to a state machine is equally simple. You can call (or connect to) the slot:


  stateMachine->tap(QVariantMap({
      std::make_pair("artist", "Fatboy Slim"),
      std::make_pair("title", "The Rockafeller Skank")
  });

This will generate a "tap" event with the map contents available in _event.data inside the state machine. In QML:


  stateMacine.tap({
      "artist": "Fatboy Slim"
      "title": "The Rockafeller Skank"
  })

Any invoked state machine with a name property will also show up as a property on its parent state machine.