Qt State Machine Guide C++
Le framework State Machine fournit des classes permettant de créer et d'exécuter des graphes d'états. Cette page présente les principales fonctionnalités du framework en C++.
Classes C++ du 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 illustrer les fonctionnalités principales de l’API State Machine, examinons un petit exemple : une machine à états comportant trois états, s1, s2 et s3. La machine à états est contrôlée par un seul bouton QPushButton; lorsque l’on clique sur ce bouton, la machine passe à un autre état. Au départ, la machine à états se trouve dans l’état s1. Le diagramme d’états de cette machine est le suivant :

L'extrait de code 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 à l’aide de 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 son état initial :
machine.addState(s1);
machine.addState(s2);
machine.addState(s3);
machine.setInitialState(s1);Enfin, nous lançons la machine à états :
machine.start();La machine à états s'exécute de manière asynchrone, c'est-à-dire qu'elle s'intègre à la boucle d'événements de votre application.
Effectuer des opérations utiles à l’entrée et à la sortie d’un état
La machine à états ci-dessus se contente de passer d’un état à un autre, elle n’effectue aucune opération. La fonction QState::assignProperty() permet à un état de définir une propriété d’un QObject lors de l’entrée dans cet état. Dans l’extrait de code suivant, la valeur à attribuer à 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 activé, le texte de l’étiquette est modifié en conséquence.
Le signal « QState::entered() » est émis lorsque l’on entre dans l’état, et le signal « QState::exited() » est émis lorsque l’on quitte l’état. Dans l’extrait de code suivant, le slot « showMaximized() » du bouton sera appelé lorsque l’on entrera dans l’état « s3 », et le slot « showMinimized() » du bouton sera appelé lorsque l’on quittera l’état « s3 » :
QObject::connect(s3, &QState::entered, button, &QPushButton:showMaximized);
QObject::connect(s3, &QState::exited, button, &QPushButton::showMinimized);Les états personnalisés peuvent réimplémenter les méthodes `QAbstractState::onEntry()` et `QAbstractState::onExit()`.
Machines à états qui se terminent
La machine à états définie dans la section précédente ne se termine jamais. Pour qu’une machine à états puisse se terminer, elle doit disposer d’un état final de niveau supérieur (objet `QFinalState `). Lorsque la machine à états entre dans un état final de niveau supérieur, elle é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 des transitions en regroupant des états
Supposons que nous souhaitions permettre à l’utilisateur de 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 depuis chacun des états s1, s2 et s3; cependant, cela semble redondant, et il faudrait également penser à ajouter une telle transition depuis 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, il suffit de créer un nouvel état de niveau supérieur et de faire des trois états d’origine des enfants de ce nouvel état. Le diagramme suivant illustre la nouvelle machine à états.

Les trois états d’origine ont été renommés s11, s12 et s13 afin d’indiquer 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 également automatiquement de cette transition.
Pour regrouper des états, il suffit de spécifier le parent approprié lors de la création de l’état. Vous devez également indiquer lequel des états enfants est l’état initial (c’est-à-dire dans quel état enfant 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 souhaitons que l’application se ferme lorsque la machine à états a terminé son exécution ; ainsi, le signal « finished() » de la machine est connecté au slot « quit() » de l’application.
Un état enfant peut redéfinir une transition héritée. Par exemple, le code suivant ajoute une transition qui a pour effet d’ignorer le bouton Quitter lorsque la machine à états se trouve 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 ne doit pas nécessairement se trouver au même niveau dans la hiérarchie des états que l’état source.
Utilisation des états d’historique pour enregistrer et restaurer l’état actuel
Imaginons que nous souhaitions ajouter un mécanisme d’« interruption » à l’exemple abordé dans la section précédente ; l’utilisateur devrait pouvoir cliquer sur un bouton pour que la machine à états effectue une tâche sans rapport avec ce qu’elle fait, après quoi elle devrait reprendre ce qu’elle faisait auparavant (c’est-à-dire revenir à l’ancien état, qui est l’un des états suivants : s11, s12 et s13 dans ce cas).
Un tel comportement peut facilement être modélisé à l’aide d’états d’historique. Un état d’historique (objetQHistoryState ) est un pseudo-état qui représente l’état enfant dans lequel se trouvait l’état parent lors de sa dernière sortie.
Un état d’historique est créé en tant qu’enfant de l’état pour lequel on souhaite enregistrer l’état enfant actuel ; lorsque la machine à états détecte la présence d’un tel état lors de l’exécution, elle enregistre automatiquement l’état enfant actuel (réel) au moment de la sortie de l’état parent. Une transition vers l’état d’historique est en fait une transition vers l’état enfant que la machine à états avait précédemment enregistré ; la machine à états « transmet » automatiquement la transition vers l’état enfant réel.
Le diagramme suivant illustre la machine à états après l’ajout du mécanisme d’interruption.

Le code suivant montre comment cela peut être implémenté ; dans cet exemple, nous affichons simplement une boîte de message lorsque l’ s3 est activé, puis nous revenons immédiatement à l’état enfant précédent de s1 via l’état d’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 des é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 « Propre » vs « Sale » et « En mouvement » vs « À l’arrêt ». Il faudrait quatre états mutuellement exclusifs et huit transitions pour pouvoir représenter toutes les combinaisons possibles et passer librement de l’une à l’autre.

Si nous ajoutions une troisième propriété (par exemple, « rouge » ou « bleu »), le nombre total d’états doublerait, pour atteindre huit ; et si nous ajoutions une quatrième propriété (par exemple, « coupé » ou « cabriolet »), le nombre total d’états doublerait à nouveau, pour atteindre seize.
Grâce aux états parallèles, le nombre total d’états et de transitions augmente de manière linéaire à mesure que l’on ajoute des propriétés, et non de manière exponentielle. De plus, on peut ajouter ou supprimer des états de l’état parallèle sans affecter aucun de ses états frères.

Pour créer un groupe d’états parallèles, transmettez ` 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’on entre dans un groupe d’états parallèles, tous ses états enfants sont activés simultanément. Les transitions au sein des états enfants individuels fonctionnent normalement. Cependant, n’importe quel état enfant peut emprunter une transition qui quitte l’état parent. Lorsque cela se produit, l’état parent et tous ses états enfants sont quittés.
Le parallélisme dans le framework de machine à états suit une sémantique entrelacée. Toutes les opérations parallèles seront exécutées en une seule étape atomique du traitement des événements ; ainsi, aucun événement ne peut interrompre les opérations parallèles. Cependant, les événements continueront d’être traités de manière séquentielle, puisque la machine elle-même est monothread. À titre d’exemple : considérons le cas où deux transitions permettent de quitter 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 parmi les deux n’aura aucun effet, puisque le premier événement aura déjà provoqué la sortie de 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 atteint, l’état parent émet le signal ` QState::finished()`. Le diagramme suivant illustre un état composite s1 qui effectue un 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 :
s1->addTransition(s1, &QState::finished, s2);L’utilisation d’états finaux dans des é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 devrait 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 d’implémentation de s1 et d’en faire une dépendance).
Pour les groupes d’états parallèles, le signal QState::finished() est émis lorsque tous les états enfants sont entrés dans des états finaux.
Transitions sans cible
Une transition n’a pas nécessairement besoin d’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 n’entraîne aucun changement d’état. Cela vous permet de réagir à un signal ou à un événement lorsque votre machine se trouve 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 s'affichera à chaque fois que l'on cliquera sur le bouton, mais la machine à états restera dans son état actuel (s1). Si l'état cible était explicitement défini sur s1, cependant, s1 serait quitté puis réintégré à chaque fois (par exemple, les signaux QAbstractState::entered() et QAbstractState::exited() seraient émis).
Événements, transitions et gardes
Une machine à états ( QStateMachine ) exécute sa propre boucle d’événements. Pour les transitions de signaux (objets `QSignalTransition `), ` QStateMachine ` envoie automatiquement un événement « QStateMachine::SignalEvent » à elle-même lorsqu’elle intercepte le signal correspondant ; de même, pour les transitions d’événements « QObject » (objets `QEventTransition `), un événement « QStateMachine::WrappedEvent » est envoyé.
Vous pouvez envoyer vos propres événements à la machine à états à l’aide de la méthode QStateMachine::postEvent().
Lorsque vous envoyez un événement personnalisé à la machine à états, vous disposez généralement d’une ou plusieurs transitions personnalisées pouvant être déclenchées par des événements de ce type. Pour créer une telle transition, vous devez créer une sous-classe de ` QAbstractTransition ` et réimplémenter la méthode ` eventTest()`, dans laquelle 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 envoyer des chaînes de caractères à la machine à états :
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 caractères de l’événement correspond à une chaîne particulière (une transition conditionnelle ) :
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 la fonction ` eventTest()`, nous vérifions d'abord si le type d'événement correspond à celui souhaité ; si c'est le cas, nous convertissons l'événement en ` StringEvent ` et effectuons la comparaison de chaînes.
Voici un diagramme d’états qui utilise l’événement et la transition personnalisés :

Voici à quoi ressemble l’implémentation du diagramme d’états :
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 la machine lancée, nous pouvons y envoyer des événements.
machine.postEvent(new StringEvent("Hello"));
machine.postEvent(new StringEvent("world"));Un événement qui n’est géré par aucune transition pertinente sera consommé en silence par la machine à états. Il peut être utile de regrouper des états et de fournir une gestion par défaut de ces événements ; par exemple, comme illustré dans le diagramme d’états suivant :

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 des propriétés dans les états, plutôt que 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 lui attribue pas explicitement de valeur, vous pouvez définir la politique de restauration globale sur QStateMachine::RestoreProperties.
QStateMachine machine;
machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);Lorsque cette politique de restauration est définie, la machine restaurera automatiquement toutes les propriétés. Si elle entre dans un état où une propriété donnée n’est pas définie, elle recherchera d’abord dans la hiérarchie des ancêtres pour voir 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. Sinon, 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 l’exemple de 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 soit égale à 0,0 au démarrage de la machine. Lorsque la machine se trouve dans l’état s1, la propriété prendra la valeur 1,0, puisque l’état lui attribue explicitement cette valeur. Lorsque la machine se trouve dans l’état s2, aucune valeur n’est explicitement définie pour la propriété ; celle-ci sera donc implicitement rétablie à 0,0.
Si l’on utilise des états imbriqués, l’état parent définit une valeur pour la propriété, qui est héritée par tous les états descendants qui n’attribuent pas explicitement de valeur à cette 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 possède deux états enfants : s2 et s3. Lorsque l’on entre dans l’état s2, la propriété fooBar prendra la valeur 2,0, car celle-ci est explicitement définie pour cet état. Lorsque la machine se trouve dans l’état s3, aucune valeur n’est définie pour cet état, mais s1 définit la propriété à 1,0 ; c’est donc cette valeur qui sera attribuée à fooBar.
Animations et machines à états
L’API de la machine à états s’interface avec le framework d’animation pour permettre l’animation automatique des propriétés au fur et à mesure de leur attribution dans les états.
La machine à états fournit un état spécial capable de lire une animation. Un ` QState ` peut également définir des propriétés lors de l’entrée ou de la sortie d’un état, et cet état d’animation spécial effectuera une interpolation entre ces valeurs lorsqu’un ` QPropertyAnimation` lui est fourni.
Il est possible d’associer une ou plusieurs animations à une transition entre états à l’aide d’une classe « QSignalTransition » ou « QEventTransition ». Ces deux classes dérivent de « QAbstractTransition », qui définit la fonction pratique « addAnimation() » permettant d’ajouter une ou plusieurs animations déclenchées lors de la transition.
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, l’ button e 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 dès l’entrée dans un état donné. Si nous souhaitons toutefois que la transition soit fluide, il suffit de créer une 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 lors de l’entrée dans l’état. Au lieu de cela, l’animation commencera à être lue dès l’entrée dans l’état et animera en douceur l’affectation de la propriété. Comme nous ne définissons ni la valeur de départ ni la valeur finale de l’animation, celles-ci seront définies implicitement. La valeur de départ de l’animation correspondra à la valeur actuelle de la propriété au moment où l’animation démarre, et la valeur finale 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 également 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 se trouve dans l’état donné. Pendant l’exécution de l’animation, la propriété peut potentiellement prendre n’importe quelle valeur, en fonction de l’animation.
Dans certains cas, il peut être utile de pouvoir détecter à quel moment la propriété s’est effectivement vu attribuer 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 l’on clique sur « button », la machine passe à l’état « s2 », ce 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ù aucune animation n’est utilisée, cela fonctionnera comme prévu. Cependant, si une animation pour la propriété « geometry » de « button » est définie lors de la transition entre « s1 » et « s2 », l’animation démarrera lorsque l’on accèdera à « s2 », mais la propriété « geometry » n’aura pas encore atteint sa valeur définie avant la fin de l’animation. Dans ce cas, la boîte de message s'affichera avant que la géométrie du bouton n'ait été effectivement définie.
Pour garantir que la boîte de message n’apparaisse pas avant que la géométrie n’ait effectivement atteint sa valeur finale, nous pouvons utiliser le signal ` propertiesAssigned()` de l’état. Le signal ` propertiesAssigned()` sera émis lorsque la propriété se verra attribuer sa valeur finale, que cela se fasse 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 passe à 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 à l’état « s3 ». Lorsqu’elle entre dans l’état « s3 », la boîte de message s’affiche. Si la transition vers l’état « s2 » comporte une animation pour la propriété « geometry », la machine restera alors dans l’état « s2 » jusqu’à la fin de la lecture de l’animation. S’il n’y a pas d’animation de ce type, la propriété sera simplement définie et la machine passera immédiatement à l’état s3.
Dans les deux cas, lorsque la machine se trouve dans l’état « s3 », vous avez la garantie que la propriété « geometry » s’est vu attribuer la valeur définie.
Si la politique de restauration globale est définie sur QStateMachine::RestoreProperties, l’état n’émettra pas le signal propertiesAssigned() tant que ces opérations n’auront pas été exécutées également.
Que se passe-t-il si l’on quitte un état avant la fin de l’animation ?
Si un état comporte des affectations de propriétés et que la transition vers cet état comporte des animations pour ces propriétés, il est possible que l’on quitte l’état 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 sortantes de l’état qui ne dépendent pas du signal ` propertiesAssigned()`, comme décrit dans la section précédente.
L’API de la machine à états garantit qu’une propriété attribuée par la machine à états :
- dispose d’une valeur explicitement attribuée à la propriété ;
- est en cours d’animation vers une valeur explicitement attribué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 pu se terminer). Toutefois, si une politique de restauration globale est définie, celle-ci prévaudra 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 afin de garantir que les affectations de propriétés dans l’état cible soient 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. Cela s’avère particulièrement utile lorsque les propriétés attribué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 se trouve dans l’état « s2 », elle lancera l’animation par défaut pour la propriété « fooBar », car cette propriété est attribuée par « s2 ».
Notez que les animations explicitement définies lors des transitions auront la priorité sur toute animation par défaut pour la propriété concernée.
Imbrication des machines à états
QStateMachine est une sous-classe de ` QState`. Cela permet à une machine à états 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 à états enfant est activée, elle se lance automatiquement.
La machine à états parente traite la machine enfant comme un état atomique dans l’algorithme de la machine à états. La machine à états enfant est autonome ; elle gère sa propre file d’attente d’événements et sa propre configuration. Notez en particulier que la méthode ` configuration()` de la machine enfant ne fait pas partie de la configuration de la machine parente (seule la machine enfant elle-même en fait partie).
Les états de la machine à états enfant ne peuvent pas être spécifiés comme cibles de transitions dans la machine à états parente ; seule la machine à états enfant elle-même peut l’être. Inversement, les états de la machine à états parente ne peuvent pas être spécifiés comme cibles de transitions dans la machine à états enfant. Le signal ` finished()` de la machine à états enfant peut être utilisé pour déclencher une transition dans la machine parente.
Voir également la présentation de `Qt State Machine ` et le guide QML «Qt State Machine ».
© 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.