Sur cette page

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.

Trois états s1, s2, s3 cyclant sur les transitions button.clicked

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.

État s1 contenant trois sous-états s11, s12, s13 cyclant sur les transitions bouton.cliqué

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.

L'état s1 avec les sous-états et l'état historique, le bouton d'interruption permet de passer à l'état s3, qui revient à l'état historique s1 lorsqu'il est terminé.

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.

Quatre états : propre/pas en mouvement, propre/mouvement, sale/pas en mouvement, sale/mouvement avec transitions démarré, arrêté, nettoyé, souillé.

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.

État parallèle s1 avec les régions s11 (propre/sale) et s12 (mobile/non mobile)

Pour créer un groupe d'états parallèles, définissez childMode sur QState.ParallelStates.

State {
    id: s1
    childMode: QState.ParallelStates
    State {
        id: s11
    }
    State {
        id: s12
    }
}

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 :

État s1 avec transition du sous-état de traitement vers l'état final, déclenchant le passage à l'état s2

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).

© 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.