Sur cette page

Qt State Machine Guide C++

Le framework State Machine fournit des classes pour la création et l'exécution de graphes d'état. Cette page illustre les principales caractéristiques du framework en C++.

Classes C++ dans le framework State Machine

Pour la liste complète des classes C++ du framework State Machine, voir Qt State Machine C++ Classes

Une machine à états simple

Pour démontrer les principales fonctionnalités de l'API State Machine, examinons un petit exemple : Une machine à états avec trois états, s1, s2 et s3. La machine à états est contrôlée par un seul QPushButton; lorsque le bouton est cliqué, la machine passe à un autre état. Initialement, la machine à états est dans l'état s1. Le diagramme d'état de cette machine est le suivant :

Trois états s1, s2, s3 se succèdent en fonction des transitions cliquées sur le bouton.

L'extrait suivant montre le code nécessaire pour créer une telle machine à états. Tout d'abord, nous créons la machine à états et les états :

    QStateMachine machine;
    QState *s1 = new QState();
    QState *s2 = new QState();
    QState *s3 = new QState();

Ensuite, nous créons les transitions en utilisant la fonction QState::addTransition() :

    s1->addTransition(button, &QPushButton::clicked, s2);
    s2->addTransition(button, &QPushButton::clicked, s3);
    s3->addTransition(button, &QPushButton::clicked, s1);

Ensuite, nous ajoutons les états à la machine et définissons l'état initial de la machine :

    machine.addState(s1);
    machine.addState(s2);
    machine.addState(s3);
    machine.setInitialState(s1);

Enfin, nous démarrons la machine à états :

    machine.start();

La machine à états s'exécute de manière asynchrone, c'est-à-dire qu'elle fait partie de la boucle d'événements de votre application.

Effectuer un travail utile à l'entrée et à la sortie de l'état

L'automate à états ci-dessus ne fait que transiter d'un état à l'autre, il n'effectue aucune opération. La fonction QState::assignProperty() peut être utilisée pour qu'un état définisse une propriété d'un QObject lors de l'entrée dans l'état. Dans l'extrait suivant, la valeur qui doit être attribuée à la propriété text d'un QLabel est spécifiée pour chaque état :

    s1->assignProperty(label, "text", "In state s1");
    s2->assignProperty(label, "text", "In state s2");
    s3->assignProperty(label, "text", "In state s3");

Lorsque l'un des états est saisi, le texte de l'étiquette est modifié en conséquence.

Le signal QState::entered() est émis lors de l'entrée dans l'état et le signal QState::exited() est émis lors de la sortie de l'état. Dans l'extrait suivant, le slot showMaximized() du bouton sera appelé lorsque l'état s3 sera entré, et le slot showMinimized() du bouton sera appelé lorsque l'état s3 sera quitté :

    QObject::connect(s3, &QState::entered, button, &QPushButton:showMaximized);
    QObject::connect(s3, &QState::exited, button, &QPushButton::showMinimized);

Les états personnalisés peuvent réimplémenter QAbstractState::onEntry() et QAbstractState::onExit().

Machines à é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 (objetQFinalState ). Lorsque l'automate entre dans un état final de premier niveau, il émet le signal QStateMachine::finished() et s'arrête.

Pour introduire un état final dans le graphe, il suffit de créer un objet QFinalState et de l'utiliser comme cible d'une ou plusieurs transitions.

Partager les transitions en regroupant les états

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 Quitter. Nous pourrions ajouter une transition à partir de chacun des états s1, s2 et s3; cependant, cela semble redondant, et il faudrait également se souvenir d'ajouter une telle transition à partir de chaque nouvel état ajouté à l'avenir.

Nous pouvons obtenir le même comportement (à savoir que cliquer 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 se relayant lors des transitions de clic sur le bouton

Les trois états originaux ont été renommés s11, s12 et s13 pour refléter le fait qu'ils sont désormais des 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 aussi 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 quel état enfant est l'état initial (c'est-à-dire l'état enfant dans lequel la machine à états doit entrer lorsque l'état parent est la cible d'une transition).

    QState *s1 = new QState();
    QState *s11 = new QState(s1);
    QState *s12 = new QState(s1);
    QState *s13 = new QState(s1);
    s1->setInitialState(s11);
    machine.addState(s1);
    QFinalState *s2 = new QFinalState();
    s1->addTransition(quitButton, &QPushButton::clicked, s2);
    machine.addState(s2);
    machine.setInitialState(s1);

    QObject::connect(&machine, &QStateMachine::finished,
                     QCoreApplication::instance(), &QCoreApplication::quit);

Dans ce cas, nous voulons que l'application quitte l'état machine lorsque celui-ci est terminé, le signal finished() de la machine est donc connecté au slot quit() de l'application.

Un état enfant peut remplacer 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.

    s12->addTransition(quitButton, &QPushButton::clicked, s12);

Une transition peut avoir n'importe quel état comme cible, c'est-à-dire que l'état cible n'a pas besoin d'être au même niveau dans la hiérarchie des états que l'état source.

Utilisation des états de l'historique pour sauvegarder et restaurer l'état actuel

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 la machine à états exécute une tâche sans rapport, après quoi la machine à états devrait reprendre ce qu'elle faisait auparavant (c'est-à-dire revenir à l'ancien état, qui est l'un des états s11, s12 et s13 dans le cas présent).

Un tel comportement peut facilement être modélisé à l'aide d'états historiques. Un état historique (objetQHistoryState ) est un pseudo-état qui représente l'état enfant dans lequel se trouvait l'état parent la dernière fois que ce dernier a été quitté.

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 est quitté. Une transition vers l'état historique est en fait une transition vers l'état enfant que l'automate à états avait précédemment enregistré ; l'automate à états "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.

    QHistoryState *s1h = new QHistoryState(s1);

    QState *s3 = new QState();
    s3->assignProperty(label, "text", "In s3");
    QMessageBox *mbox = new QMessageBox(mainWindow);
    mbox->addButton(QMessageBox::Ok);
    mbox->setText("Interrupted!");
    mbox->setIcon(QMessageBox::Information);
    QObject::connect(s3, &QState::entered, mbox, &QMessageBox::exec);
    s3->addTransition(s1h);
    machine.addState(s3);

    s1->addTransition(interruptButton, &QPushButton::clicked, s3);

Utilisation d'états parallèles pour éviter une explosion combinatoire d'états

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 pouvoir représenter toutes les combinaisons possibles et s'y déplacer librement.

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

Si nous ajoutons une troisième propriété (par exemple, Rouge vs Bleu), le nombre total d'états double, passant à huit ; et si nous ajoutons une quatrième propriété (par exemple, Fermé vs Convertible), le nombre total d'états double à nouveau, passant à 16.

En utilisant des états parallèles, le nombre total d'états et de transitions croît de façon linéaire au fur et à mesure que nous ajoutons des propriétés, et non de façon exponentielle. En outre, des états peuvent être ajoutés ou supprimés de l'état parallèle sans affecter aucun de leurs états frères.

É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, passez QState::ParallelStates au constructeur QState.

    QState *s1 = new QState(QState::ParallelStates);
    // s11 and s12 will be entered in parallel
    QState *s11 = new QState(s1);
    QState *s12 = new QState(s1);

Lorsqu'un groupe d'états parallèles est saisi, tous ses états enfants le sont simultanément. Les transitions au sein des différents états enfants fonctionnent normalement. Cependant, n'importe quel état enfant peut prendre une transition qui quitte 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, puisque la machine elle-même est monotâche. A titre d'exemple : Considérons la situation où deux transitions quittent le même groupe d'états parallèles et où leurs conditions deviennent vraies simultanément. Dans ce cas, l'événement traité en dernier n'aura aucun effet, puisque le premier événement aura déjà fait sortir la machine de l'état parallèle.

Détection de la fin d'un état composite

Un état enfant peut être final (un objet QFinalState ) ; lorsqu'un état enfant final est entré, l'état parent émet le signal QState::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 le signal finished(). Nous utilisons une transition de signal pour que cet événement déclenche un changement d'état :

  s1->addTransition(s1, &QState::finished, s2);

L'utilisation d'états finaux dans les états composites est utile lorsque vous souhaitez masquer les détails internes d'un état composite, c'est-à-dire que la seule chose que le monde extérieur doit pouvoir faire est d'entrer dans l'état et de recevoir une notification lorsque l'état a terminé son travail. 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 QState::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 est que lorsqu'une transition sans cible est déclenchée, 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. Exemple :

QStateMachine machine;
QState *s1 = new QState(&machine);

QPushButton button;
QSignalTransition *trans = new QSignalTransition(&button, &QPushButton::clicked);
s1->addTransition(trans);

QMessageBox msgBox;
msgBox.setText("The button was clicked; carry on.");
QObject::connect(trans, QSignalTransition::triggered, &msgBox, &QMessageBox::exec);

machine.setInitialState(s1);

La boîte de message sera affichée chaque fois que l'on cliquera sur le bouton, mais la machine à états restera dans son état actuel (s1). Toutefois, si l'état cible était explicitement défini comme étant s1, cet état serait quitté et réintroduit à chaque fois (les signaux QAbstractState::entered() et QAbstractState::exited() seraient émis).

Événements, transitions et gardes

Un site QStateMachine exécute sa propre boucle d'événements. Pour les transitions de signaux (objetsQSignalTransition ), QStateMachine s'envoie automatiquement un message QStateMachine::SignalEvent lorsqu'il intercepte le signal correspondant ; de même, pour les transitions d'événements QObject (objetsQEventTransition ), un message QStateMachine::WrappedEvent est envoyé.

Vous pouvez envoyer vos propres événements à la machine à états en utilisant QStateMachine::postEvent().

Lorsque vous envoyez un événement personnalisé à l'automate à états, vous disposez généralement d'une ou plusieurs transitions personnalisées qui peuvent être déclenchées par des événements de ce type. Pour créer une telle transition, vous sous-classez QAbstractTransition et réimplémentez eventTest(), où vous vérifiez si un événement correspond à votre type d'événement (et éventuellement à d'autres critères, par exemple les attributs de l'objet événement).

Nous définissons ici notre propre type d'événement personnalisé, StringEvent, pour l'envoi de chaînes de caractères à la machine d'état :

struct StringEvent : public QEvent
{
    StringEvent(const QString &val)
    : QEvent(QEvent::Type(QEvent::User+1)),
      value(val) {}

    QString value;
};

Ensuite, nous définissons une transition qui ne se déclenche que lorsque la chaîne de l'événement correspond à une chaîne particulière (une transition gardée ) :

class StringTransition : public QAbstractTransition
{
    Q_OBJECT

public:
    StringTransition(const QString &value)
        : m_value(value) {}

protected:
    bool eventTest(QEvent *e) override
    {
        if (e->type() != QEvent::Type(QEvent::User+1)) // StringEvent
            return false;
        StringEvent *se = static_cast<StringEvent*>(e);
        return (m_value == se->value);
    }

    void onTransition(QEvent *) override {}

private:
    QString m_value;
};

Dans la réimplémentation de eventTest(), nous vérifions d'abord si le type d'événement est celui souhaité ; si c'est le cas, nous transformons l'événement en StringEvent et effectuons la comparaison des chaînes.

Voici un diagramme d'état qui utilise l'événement et la transition personnalisés :

États s1 et s2 avec StringTransition pour la gestion d'événements personnalisés

Voici à quoi ressemble la mise en œuvre du diagramme d'état :

    QStateMachine machine;
    QState *s1 = new QState();
    QState *s2 = new QState();
    QFinalState *done = new QFinalState();

    StringTransition *t1 = new StringTransition("Hello");
    t1->setTargetState(s2);
    s1->addTransition(t1);
    StringTransition *t2 = new StringTransition("world");
    t2->setTargetState(done);
    s2->addTransition(t2);

    machine.addState(s1);
    machine.addState(s2);
    machine.addState(done);
    machine.setInitialState(s1);

Une fois que la machine est démarrée, nous pouvons lui envoyer des événements.

    machine.postEvent(new StringEvent("Hello"));
    machine.postEvent(new StringEvent("world"));

Un événement qui n'est pas traité par une transition pertinente sera consommé silencieusement par la machine à états. Il peut être utile de regrouper les états et de fournir une gestion par défaut de ces événements, comme illustré dans le diagramme d'états suivant :

État s1 avec les sous-états s11 et s12, transitions d'événements personnalisées et état de célébration à l'achèvement.

Pour les diagrammes d'états profondément imbriqués, vous pouvez ajouter de telles transitions de "repli" au niveau de granularité le plus approprié.

Utilisation de la politique de restauration pour restaurer automatiquement les propriétés

Dans certaines machines à états, il peut être utile de se concentrer sur l'attribution de propriétés dans les états, et non sur leur restauration lorsque l'état n'est plus actif. Si vous savez qu'une propriété doit toujours être restaurée à sa valeur initiale lorsque la machine entre dans un état qui ne donne pas explicitement une valeur à la propriété, vous pouvez définir la politique de restauration globale à QStateMachine::RestoreProperties.

QStateMachine machine;
machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);

Lorsque cette politique de restauration est définie, la machine restaure automatiquement toutes les propriétés. Si elle entre dans un état où une propriété donnée n'est pas définie, elle cherchera d'abord dans la hiérarchie des ancêtres si la propriété y est définie. Si c'est le cas, la propriété sera restaurée à la valeur définie par l'ancêtre le plus proche. Dans le cas contraire, elle sera restaurée à sa valeur initiale (c'est-à-dire la valeur de la propriété avant l'exécution de toute affectation de propriété dans les états).

Prenons le code suivant :

    QStateMachine machine;
    machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);

    QState *s1 = new QState();
    s1->assignProperty(object, "fooBar", 1.0);
    machine.addState(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState();
    machine.addState(s2);

Supposons que la propriété fooBar vaille 0,0 au démarrage de la machine. Lorsque la machine est dans l'état s1, la propriété vaut 1,0, puisque l'état lui attribue explicitement cette valeur. Lorsque la machine est dans l'état s2, aucune valeur n'est explicitement définie pour la propriété, qui sera donc implicitement ramenée à 0.0.

Si nous utilisons des états imbriqués, le parent définit une valeur pour la propriété qui est héritée par tous les descendants qui n'assignent pas explicitement une valeur à la propriété.

    QStateMachine machine;
    machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);

    QState *s1 = new QState();
    s1->assignProperty(object, "fooBar", 1.0);
    machine.addState(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(s1);
    s2->assignProperty(object, "fooBar", 2.0);
    s1->setInitialState(s2);

    QState *s3 = new QState(s1);

Ici, s1 a deux enfants : s2 et s3. Lorsque s2 est saisi, la propriété fooBar aura la valeur 2.0, puisqu'elle est explicitement définie pour l'état. Lorsque la machine est dans l'état s3, aucune valeur n'est définie pour l'état, mais s1 définit la propriété comme étant 1.0, et c'est donc cette valeur qui sera attribuée à fooBar.

Animations et machines à états

L'API State Machine se connecte à The Animation Framework pour permettre d'animer automatiquement les propriétés au fur et à mesure qu'elles sont affectées à des états.

La machine à états fournit un état spécial qui peut jouer une animation. Un site QState peut également définir des propriétés lors de l'entrée ou de la sortie de l'état, et cet état d'animation spécial interpolera entre ces valeurs lorsqu'il recevra un site QPropertyAnimation.

Nous pouvons associer une ou plusieurs animations à une transition entre états à l'aide d'une classe QSignalTransition ou QEventTransition. Ces classes sont toutes deux dérivées de QAbstractTransition, qui définit la fonction de commodité addAnimation() permettant d'ajouter une ou plusieurs animations déclenchées lorsque la transition se produit.

Nous avons également la possibilité d'associer des propriétés aux états plutôt que de définir nous-mêmes les valeurs de début et de fin.

Supposons que nous ayons le code suivant :

    QState *s1 = new QState();
    QState *s2 = new QState();

    s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));
    s2->assignProperty(button, "geometry", QRectF(0, 0, 100, 100));

    s1->addTransition(button, &QPushButton::clicked, s2);

Nous définissons ici deux états d'une interface utilisateur. Dans s1, la page button est petite, et dans s2, elle est plus grande. Si nous cliquons sur le bouton pour passer de s1 à s2, la géométrie du bouton sera définie immédiatement lorsqu'un état donné aura été atteint. Si nous voulons que la transition se fasse en douceur, il nous suffit de créer une page QPropertyAnimation et de l'ajouter à l'objet de transition.

    QState *s1 = new QState();
    QState *s2 = new QState();

    s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));
    s2->assignProperty(button, "geometry", QRectF(0, 0, 100, 100));

    QSignalTransition *transition = s1->addTransition(button, &QPushButton::clicked, s2);
    transition->addAnimation(new QPropertyAnimation(button, "geometry"));

L'ajout d'une animation pour la propriété en question signifie que l'affectation de la propriété ne prendra plus effet immédiatement lorsque l'état est entré. Au lieu de cela, l'animation commencera à jouer lorsque l'état aura été saisi et animera en douceur l'affectation de la propriété. Étant donné que nous ne définissons pas la valeur de départ ou la valeur de fin de l'animation, celles-ci seront définies implicitement. La valeur de départ de l'animation sera la valeur actuelle de la propriété lorsque l'animation commence, et la valeur de fin sera définie en fonction des affectations de propriétés définies pour l'état.

Si la politique de restauration globale de la machine à états est définie sur QStateMachine::RestoreProperties, il est possible d'ajouter des animations pour les restaurations de propriétés.

Détecter que toutes les propriétés ont été définies dans un état

Lorsque des animations sont utilisées pour attribuer des propriétés, un état ne définit plus les valeurs exactes qu'une propriété aura lorsque la machine sera dans l'état donné. Pendant le déroulement de l'animation, la propriété peut potentiellement avoir n'importe quelle valeur, en fonction de l'animation.

Dans certains cas, il peut être utile de pouvoir détecter quand la propriété a effectivement reçu la valeur définie par un état.

Supposons que nous ayons le code suivant :

    QMessageBox *messageBox = new QMessageBox(mainWindow);
    messageBox->addButton(QMessageBox::Ok);
    messageBox->setText("Button geometry has been set!");
    messageBox->setIcon(QMessageBox::Information);

    QState *s1 = new QState();

    QState *s2 = new QState();
    s2->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));
    connect(s2, &QState::entered, messageBox, SLOT(exec()));

    s1->addTransition(button, &QPushButton::clicked, s2);

Lorsque button est cliqué, la machine passe à l'état s2, qui définit la géométrie du bouton, puis affiche une boîte de message pour avertir l'utilisateur que la géométrie a été modifiée.

Dans le cas normal, où les animations ne sont pas utilisées, cela fonctionnera comme prévu. Toutefois, si une animation pour la propriété geometry de button est définie lors de la transition entre s1 et s2, l'animation sera lancée lorsque s2 sera saisi, mais la propriété geometry n'atteindra pas réellement sa valeur définie avant la fin de l'animation. Dans ce cas, la boîte de message apparaîtra avant que la géométrie du bouton n'ait été définie.

Pour s'assurer que la boîte de message n'apparaîtra pas avant que la géométrie n'atteigne sa valeur finale, nous pouvons utiliser le signal propertiesAssigned() de l'état. Le signal propertiesAssigned() est émis lorsque la valeur finale est attribuée à la propriété, que ce soit immédiatement ou après la fin de l'animation.

    QMessageBox *messageBox = new QMessageBox(mainWindow);
    messageBox->addButton(QMessageBox::Ok);
    messageBox->setText("Button geometry has been set!");
    messageBox->setIcon(QMessageBox::Information);

    QState *s1 = new QState();

    QState *s2 = new QState();
    s2->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));

    QState *s3 = new QState();
    connect(s3, &QState::entered, messageBox, SLOT(exec()));

    s1->addTransition(button, &QPushButton::clicked, s2);
    s2->addTransition(s2, &QState::propertiesAssigned, s3);

Dans cet exemple, lorsque l'on clique sur button, la machine entre dans l'état s2. Elle restera dans l'état s2 jusqu'à ce que la propriété geometry soit définie sur QRect(0, 0, 50, 50). Elle passera ensuite à s3. Lorsque s3 est saisi, la boîte de message s'affiche. Si la transition vers s2 comporte une animation pour la propriété geometry, la machine restera dans l'état s2 jusqu'à ce que l'animation soit terminée. En l'absence d'une telle animation, la machine définira simplement la propriété et entrera immédiatement dans l'état s3.

Quoi qu'il en soit, lorsque la machine est dans l'état s3, vous avez la garantie que la valeur définie a été attribuée à la propriété geometry.

Si la politique de restauration globale est définie sur QStateMachine::RestoreProperties, l'état n'émettra pas le signal propertiesAssigned() tant que ces propriétés n'auront pas été exécutées.

Que se passe-t-il si un état est quitté avant la fin de l'animation ?

Si un état comporte des affectations de propriétés et que la transition vers l'état comporte des animations pour les propriétés, l'état peut potentiellement être quitté avant que les propriétés n'aient été affectées aux valeurs définies par l'état. C'est notamment le cas lorsqu'il existe des transitions hors de l'état qui ne dépendent pas du signal propertiesAssigned(), comme décrit dans la section précédente.

L'API des machines à états garantit qu'une propriété attribuée par la machine à états soit :

  • A une valeur explicitement assignée à la propriété.
  • Est en cours d'animation dans une valeur explicitement assignée à la propriété.

Lorsqu'un état est quitté avant la fin de l'animation, le comportement de la machine à états dépend de l'état cible de la transition. Si l'état cible attribue explicitement une valeur à la propriété, aucune action supplémentaire ne sera entreprise. La propriété se verra attribuer la valeur définie par l'état cible.

Si l'état cible n'attribue aucune valeur à la propriété, deux options sont possibles : Par défaut, la propriété se verra attribuer la valeur définie par l'état qu'elle quitte (la valeur qui lui aurait été attribuée si l'animation avait été autorisée à terminer sa lecture). Cependant, si une politique de restauration globale est définie, celle-ci sera prioritaire et la propriété sera restaurée comme d'habitude.

Animations par défaut

Comme décrit précédemment, vous pouvez ajouter des animations aux transitions pour vous assurer que les affectations de propriétés dans l'état cible sont animées. Si vous souhaitez qu'une animation spécifique soit utilisée pour une propriété donnée, quelle que soit la transition empruntée, vous pouvez l'ajouter en tant qu'animation par défaut à la machine à états. Ceci est particulièrement utile lorsque les propriétés assignées (ou restaurées) par des états spécifiques ne sont pas connues au moment de la construction de la machine.

QState *s1 = new QState();
QState *s2 = new QState();

s2->assignProperty(object, "fooBar", 2.0);
s1->addTransition(s2);

QStateMachine machine;
machine.setInitialState(s1);
machine.addDefaultAnimation(new QPropertyAnimation(object, "fooBar"));

Lorsque la machine est dans l'état s2, elle jouera l'animation par défaut pour la propriété fooBar puisque cette propriété est assignée par s2.

Notez que les animations explicitement définies sur les transitions auront la priorité sur toute animation par défaut pour la propriété donnée.

Machines à états imbriqués

QStateMachine est une sous-classe de QState, ce qui permet à une machine d'état d'être un état enfant d'une autre machine. QStateMachine réimplémente QState::onEntry() et appelle QStateMachine::start(), de sorte que lorsque la machine d'état enfant est saisie, elle commence automatiquement à fonctionner.

La machine à états parent traite la machine enfant comme un état atomique dans l'algorithme de la machine à états. La machine à états enfant est autonome ; elle maintient sa propre file d'attente d'événements et sa propre configuration. En particulier, notez que le configuration() de la machine enfant ne fait pas partie de la configuration de la machine mère (seule la machine enfant en fait partie).

Les états de la machine d'état enfant ne peuvent pas être spécifiés comme cibles de transitions dans la machine d'état parent ; seule la machine d'état enfant elle-même le peut. Inversement, les états de la machine à états parent ne peuvent pas être spécifiés comme cibles de transitions dans la machine à états enfant. Le signal finished() de la machine d'état enfant peut être utilisé pour déclencher une transition dans la machine mère.

Voir également Qt State Machine Vue d'ensemble et Qt State Machine Guide QML.

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