Qt State Machine Guide QML
Qt State Machine Les API QML fournissent des types permettant de créer et d'exécuter des graphes d'état en QML. Il est similaire au cadre C++ State Machine basé sur les Statecharts de Harel : Un formalisme visuel pour les systèmes complexes de Harel, qui est également la base des diagrammes d'état UML. Comme son site C++ counterpart, le cadre fournit une API et un modèle d'exécution basés sur State Chart XML (SCXML) pour intégrer les éléments et la sémantique des diagrammes d'état dans les applications QML.
Pour les interfaces utilisateur présentant plusieurs états visuels, indépendamment de l'état logique de l'application, envisagez d'utiliser les états et transitions QML.
Pour obtenir la liste complète des types QML fournis par le cadre pour créer des machines d'état pilotées par les événements, voir : Qt State Machine QML Types
Utilisation des importations QtQuick et QtQml.StateMachine
Attention : Si vous essayez d'importer à la fois QtQuick et QtQml. StateMachine dans un seul fichier QML, assurez-vous d'importer QtQml. StateMachine en dernier. De cette manière, le type State est fourni par le Declarative State Machine Framework et non par QtQuick:
import QtQuick import QtQml.StateMachine StateMachine { State { // okay, is of type QtQml.StateMachine.State } }
Vous pouvez également importer QtQml. StateMachine dans un espace de noms distinct afin d'éviter toute ambiguïté avec l'élément State de QtQuick:
import QtQuick import QtQml.StateMachine as DSM DSM.StateMachine { DSM.State { // ... } }
Une machine à états simple
Pour démontrer la fonctionnalité de base de l'API State Machine, prenons un exemple : Une machine à états avec trois états, s1, s2 et s3. L'automate à états est contrôlé par un seul bouton ; lorsque le bouton est cliqué, l'automate passe à un autre état. Initialement, la machine à états est dans l'état s1. Le tableau suivant montre les différents états de notre exemple.

L'extrait suivant montre le code nécessaire pour créer une telle machine à états.
Button { anchors.fill: parent id: button // change the button label to the active state id text: s1.active ? "s1" : s2.active ? "s2" : "s3" } StateMachine { id: stateMachine // set the initial state initialState: s1 // start the state machine running: true State { id: s1 // create a transition from s1 to s2 when the button is clicked SignalTransition { targetState: s2 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s1 entered") onExited: console.log("s1 exited") } State { id: s2 // create a transition from s2 to s3 when the button is clicked SignalTransition { targetState: s3 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s2 entered") onExited: console.log("s2 exited") } State { id: s3 // create a transition from s3 to s1 when the button is clicked SignalTransition { targetState: s1 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s3 entered") onExited: console.log("s3 exited") } }
La machine à états s'exécute de manière asynchrone pour faire partie de la boucle d'événements de votre application.
Les automates à états qui se terminent
L'automate à états défini dans la section précédente ne se termine jamais. Pour qu'un automate à états puisse se terminer, il doit avoir un état final de haut niveau (objetFinalState ). Lorsque l'automate entre dans l'état final de premier niveau, il émet le signal finished et s'arrête.
Pour introduire un état final dans le graphe, il suffit de créer un objet FinalState et de l'utiliser comme cible d'une ou plusieurs transitions.
Partage des transitions
Supposons que nous voulions que l'utilisateur puisse quitter l'application à tout moment en cliquant sur un bouton Quitter. Pour ce faire, nous devons créer un état final et en faire la cible d'une transition associée au signal clicked() du bouton Quit. Nous pourrions ajouter une transition pour chaque état ; cependant, cela semble redondant et il faudrait également se souvenir d'ajouter une telle transition pour chaque nouvel état ajouté à l'avenir.
Nous pouvons obtenir le même comportement (à savoir qu'un clic sur le bouton Quit quitte la machine à états, quel que soit l'état dans lequel elle se trouve) en regroupant les états s1, s2 et s3. Pour ce faire, on crée un nouvel état de niveau supérieur et on fait des trois états d'origine des enfants du nouvel état. Le diagramme suivant montre la nouvelle machine à états.

Les trois états originaux ont été renommés s11, s12 et s13 pour refléter le fait qu'ils sont désormais les enfants du nouvel état de niveau supérieur, s1. Les états enfants héritent implicitement des transitions de leur état parent. Cela signifie qu'il suffit désormais d'ajouter une seule transition de s1 vers l'état final, s2. Les nouveaux états ajoutés à s1 hériteront automatiquement de cette transition.
Pour regrouper les états, il suffit de spécifier le parent approprié lors de la création de l'état. Vous devez également spécifier lequel des états enfants est l'état initial (l'état enfant dans lequel la machine à états doit entrer lorsque l'état parent est la cible d'une transition).
Row { anchors.fill: parent spacing: 2 Button { id: button // change the button label to the active state id text: s11.active ? "s11" : s12.active ? "s12" : "s13" } Button { id: quitButton text: "quit" } } StateMachine { id: stateMachine // set the initial state initialState: s1 // start the state machine running: true State { id: s1 // set the initial state initialState: s11 // create a transition from s1 to s2 when the button is clicked SignalTransition { targetState: s2 signal: quitButton.clicked } // do something when the state enters/exits onEntered: console.log("s1 entered") onExited: console.log("s1 exited") State { id: s11 // create a transition from s11 to s12 when the button is clicked SignalTransition { targetState: s12 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s11 entered") onExited: console.log("s11 exited") } State { id: s12 // create a transition from s12 to s13 when the button is clicked SignalTransition { targetState: s13 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s12 entered") onExited: console.log("s12 exited") } State { id: s13 // create a transition from s13 to s11 when the button is clicked SignalTransition { targetState: s11 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s13 entered") onExited: console.log("s13 exited") } } FinalState { id: s2 onEntered: console.log("s2 entered") onExited: console.log("s2 exited") } onFinished: Qt.quit() }
Dans ce cas, nous voulons que l'application se retire lorsque la machine à états est terminée, donc le signal finished() de la machine est connecté au slot quit() de l'application.
Un état enfant peut surcharger une transition héritée. Par exemple, le code suivant ajoute une transition qui fait que le bouton Quit est ignoré lorsque la machine à états est dans l'état s12.
State { id: s12 // create a transition from s12 to s13 when the button is clicked SignalTransition { targetState: s13 signal: button.clicked } // ignore Quit button when we are in state 12 SignalTransition { targetState: s12 signal: quitButton.clicked } // do something when the state enters/exits onEntered: console.log("s12 entered") onExited: console.log("s12 exited") }
Une transition peut avoir n'importe quel état comme cible, indépendamment de l'endroit où l'état cible se trouve dans la hiérarchie des états.
Utilisation des états de l'historique
Imaginons que nous voulions ajouter un mécanisme d'"interruption" à l'exemple discuté dans la section précédente ; l'utilisateur devrait pouvoir cliquer sur un bouton pour que l'automate à états effectue une tâche sans rapport, après quoi l'automate à états devrait reprendre ce qu'il faisait auparavant (c'est-à-dire revenir à l'ancien état, qui est l'un des trois états dans ce cas).
Un tel comportement peut facilement être modélisé à l'aide d'états historiques. Un état historique (objetHistoryState ) est un pseudo-état qui représente l'état enfant dans lequel se trouvait l'état parent avant sa dernière sortie.
Un état historique est créé en tant qu'enfant de l'état pour lequel nous souhaitons enregistrer l'état enfant actuel ; lorsque la machine à états détecte la présence d'un tel état au moment de l'exécution, elle enregistre automatiquement l'état enfant actuel (réel) lorsque l'état parent se termine. Une transition vers l'état historique est en fait une transition vers l'état enfant que la machine d'état avait précédemment enregistré ; la machine d'état "transfère" automatiquement la transition vers l'état enfant réel.
Le diagramme suivant montre l'automate à états après l'ajout du mécanisme d'interruption.

Le code suivant montre comment il peut être mis en œuvre ; dans cet exemple, nous affichons simplement une boîte de message lorsque s3 est saisi, puis nous retournons immédiatement à l'état enfant précédent de s1 par l'intermédiaire de l'état historique.
Row { anchors.fill: parent spacing: 2 Button { id: button // change the button label to the active state id text: s11.active ? "s11" : s12.active ? "s12" : s13.active ? "s13" : "s3" } Button { id: interruptButton text: s1.active ? "Interrupt" : "Resume" } Button { id: quitButton text: "quit" } } StateMachine { id: stateMachine // set the initial state initialState: s1 // start the state machine running: true State { id: s1 // set the initial state initialState: s11 // create a transition from s1 to s2 when the button is clicked SignalTransition { targetState: s2 signal: quitButton.clicked } // do something when the state enters/exits onEntered: console.log("s1 entered") onExited: console.log("s1 exited") State { id: s11 // create a transition from s1 to s2 when the button is clicked SignalTransition { targetState: s12 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s11 entered") onExited: console.log("s11 exited") } State { id: s12 // create a transition from s2 to s3 when the button is clicked SignalTransition { targetState: s13 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s12 entered") onExited: console.log("s12 exited") } State { id: s13 // create a transition from s3 to s1 when the button is clicked SignalTransition { targetState: s1 signal: button.clicked } // do something when the state enters/exits onEntered: console.log("s13 entered") onExited: console.log("s13 exited") } // create a transition from s1 to s3 when the button is clicked SignalTransition { targetState: s3 signal: interruptButton.clicked } HistoryState { id: s1h } } FinalState { id: s2 onEntered: console.log("s2 entered") onExited: console.log("s2 exited") } State { id: s3 SignalTransition { targetState: s1h signal: interruptButton.clicked } // do something when the state enters/exits onEntered: console.log("s3 entered") onExited: console.log("s3 exited") } onFinished: Qt.quit() }
Utilisation d'états parallèles
Supposons que vous souhaitiez modéliser un ensemble de propriétés mutuellement exclusives d'une voiture dans une seule machine à états. Disons que les propriétés qui nous intéressent sont Propreté vs Saleté, et Déplacement vs Non-déplacement. Il faudrait quatre états mutuellement exclusifs et huit transitions pour représenter les états et se déplacer librement entre toutes les combinaisons possibles, comme le montre le diagramme d'états suivant.

Si l'on ajoute une troisième propriété (par exemple, Rouge vs Bleu), le nombre total d'états double, passant à huit ; et si l'on ajoute une quatrième propriété (par exemple, Fermé vs Convertible), le nombre total d'états double à nouveau, passant à 16.
Cette augmentation exponentielle peut être réduite en utilisant des états parallèles, ce qui permet une croissance linéaire du nombre d'états et de transitions au fur et à mesure que nous ajoutons des propriétés. En outre, des états peuvent être ajoutés ou supprimés de l'état parallèle sans affecter aucun de leurs états frères. Le diagramme d'état suivant montre les différents états parallèles pour l'exemple de la voiture.

Pour créer un groupe d'états parallèles, définissez childMode sur QState.ParallelStates.
Lorsqu'un groupe d'états parallèles est saisi, tous ses états fils sont saisis simultanément. Les transitions au sein des différents états enfants fonctionnent normalement. Cependant, n'importe quel état enfant peut prendre une transition qui sort de l'état parent. Dans ce cas, l'état parent et tous ses états enfants sont quittés.
Le parallélisme dans le cadre de la machine à états suit une sémantique entrelacée. Toutes les opérations parallèles sont exécutées en une seule étape atomique du traitement des événements, de sorte qu'aucun événement ne peut interrompre les opérations parallèles. Cependant, les événements seront toujours traités de manière séquentielle, car la machine elle-même est unitaire. Prenons l'exemple de deux transitions qui quittent le même groupe d'états parallèles et dont les conditions deviennent vraies simultanément. Dans ce cas, l'événement traité en dernier n'aura aucun effet.
Sortie d'un état composite
Un état enfant peut être final (un objet FinalState ) ; lorsqu'un état enfant final est entré, l'état parent émet le signal State::finished. Le diagramme suivant montre un état composite s1 qui effectue un certain traitement avant d'entrer dans un état final :

Lorsque l'état final de s1 est atteint, s1 émet automatiquement finished. Nous utilisons une transition de signal pour que cet événement déclenche un changement d'état :
State { id: s1 SignalTransition { targetState: s2 signal: s1.finished } }
L'utilisation d'états finaux dans des états composites est utile lorsque vous souhaitez masquer les détails internes d'un état composite. Le monde extérieur doit pouvoir entrer dans l'état et recevoir une notification lorsque l'état a terminé son travail, sans avoir besoin de connaître les détails internes. Il s'agit d'un mécanisme d'abstraction et d'encapsulation très puissant lors de la construction de machines à états complexes (profondément imbriquées). (Dans l'exemple ci-dessus, vous pourriez bien sûr créer une transition directement à partir de l'état done de s1 plutôt que de vous appuyer sur le signal finished() de s1, mais cela aurait pour conséquence d'exposer les détails de l'implémentation de s1 et d'en dépendre).
Pour les groupes d'états parallèles, le signal State::finished est émis lorsque tous les états enfants sont entrés dans les états finaux.
Transitions sans cible
Une transition n'a pas besoin d'avoir un état cible. Une transition sans cible peut être déclenchée de la même manière que n'importe quelle autre transition, à la différence qu'elle ne provoque aucun changement d'état. Cela vous permet de réagir à un signal ou à un événement lorsque votre machine est dans un certain état, sans avoir à quitter cet état. Par exemple, le message "bouton enfoncé" sera envoyé à l'utilisateur :
Button { id: button text: "button" StateMachine { id: stateMachine initialState: s1 running: true State { id: s1 SignalTransition { signal: button.clicked onTriggered: console.log("button pressed") } } } }
Le message "bouton enfoncé" sera affiché chaque fois que le bouton sera cliqué, mais la machine à états restera dans son état actuel (s1). Si l'état cible était explicitement défini sur s1, l'état s1 serait quitté et réintroduit à chaque fois (les signaux QAbstractState::entered et QAbstractState::exited seraient émis).
Informations connexes
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.