Qt State Machine C++-Handbuch
Das State-Machine-Framework stellt Klassen zum Erstellen und Ausführen von Zustandsgraphen bereit. Auf dieser Seite werden die wichtigsten Funktionen des Frameworks in C++ erläutert.
C++-Klassen im State-Machine-Framework
Eine vollständige Liste der C++-Klassen im State-Machine-Framework finden Sie unter Qt State Machine C++ Classes
Eine einfache Zustandsmaschine
Um die Kernfunktionalität der State-Machine-API zu veranschaulichen, betrachten wir ein kleines Beispiel: Eine Zustandsmaschine mit drei Zuständen: „ s1 “, „ s2 “ und „ s3 “. Die Zustandsmaschine wird über eine einzige Schaltfläche „ QPushButton “ gesteuert; beim Klicken auf die Schaltfläche wechselt die Maschine in einen anderen Zustand. Zu Beginn befindet sich die Zustandsmaschine im Zustand „ s1 “. Das Zustandsdiagramm für diese Maschine sieht wie folgt aus:

Der folgende Codeausschnitt zeigt den Code, der zum Erstellen einer solchen Zustandsmaschine benötigt wird. Zunächst erstellen wir die Zustandsmaschine und die Zustände:
QStateMachine machine;
QState *s1 = new QState();
QState *s2 = new QState();
QState *s3 = new QState();Anschließend erstellen wir die Übergänge mithilfe der Funktion ` QState::addTransition()`:
s1->addTransition(button, &QPushButton::clicked, s2);
s2->addTransition(button, &QPushButton::clicked, s3);
s3->addTransition(button, &QPushButton::clicked, s1);Anschließend fügen wir die Zustände zur Zustandsmaschine hinzu und legen den Anfangszustand der Maschine fest:
machine.addState(s1);
machine.addState(s2);
machine.addState(s3);
machine.setInitialState(s1);Schließlich starten wir die Zustandsmaschine:
machine.start();Die Zustandsmaschine wird asynchron ausgeführt, d. h., sie wird Teil der Ereignisschleife Ihrer Anwendung.
Nützliche Aufgaben beim Ein- und Austritt aus Zuständen
Die oben gezeigte Zustandsmaschine wechselt lediglich von einem Zustand in einen anderen, sie führt jedoch keine Operationen aus. Mit der Funktion ` QState::assignProperty()` kann festgelegt werden, dass ein Zustand beim Eintritt in diesen Zustand eine Eigenschaft eines ` QObject ` setzt. Im folgenden Codeausschnitt wird für jeden Zustand der Wert angegeben, der der Text-Eigenschaft eines ` QLabel` zugewiesen werden soll:
s1->assignProperty(label, "text", "In state s1");
s2->assignProperty(label, "text", "In state s2");
s3->assignProperty(label, "text", "In state s3");Wenn einer der Zustände erreicht wird, wird der Text des Labels entsprechend geändert.
Das Signal „ QState::entered()“ wird beim Eintritt in den Zustand ausgelöst, und das Signal „ QState::exited()“ wird beim Verlassen des Zustands ausgelöst. Im folgenden Codeausschnitt wird der Slot „ showMaximized()“ der Schaltfläche aufgerufen, wenn der Zustand „ s3 “ erreicht wird, und der Slot „ showMinimized()“ der Schaltfläche wird aufgerufen, wenn der Zustand „ s3 “ verlassen wird:
QObject::connect(s3, &QState::entered, button, &QPushButton:showMaximized);
QObject::connect(s3, &QState::exited, button, &QPushButton::showMinimized);Benutzerdefinierte Zustände können `QAbstractState::onEntry()` und `QAbstractState::onExit()` neu implementieren.
Zustandsmaschinen, die beendet werden
Die im vorherigen Abschnitt definierte Zustandsmaschine endet nie. Damit eine Zustandsmaschine beendet werden kann, muss sie über einen Endzustand auf oberster Ebene (ein „QFinalState “-Objekt) verfügen. Wenn die Zustandsmaschine in einen Endzustand auf oberster Ebene wechselt, sendet sie das Signal „ QStateMachine::finished()“ aus und hält an.
Um einen Endzustand in den Graphen einzufügen, müssen Sie lediglich ein „ QFinalState “-Objekt erstellen und es als Ziel für einen oder mehrere Übergänge verwenden.
Gemeinsame Nutzung von Übergängen durch Gruppierung von Zuständen
Angenommen, wir möchten, dass der Benutzer die Anwendung jederzeit durch Klicken auf eine „Quit“-Schaltfläche beenden kann. Um dies zu erreichen, müssen wir einen Endzustand erstellen und ihn zum Ziel eines Übergangs machen, der mit dem Signal „ clicked()“ der „Quit“-Schaltfläche verknüpft ist. Wir könnten einen Übergang von jedem der Zustände „ s1 “, „ s2 “ und „ s3 “ hinzufügen; dies erscheint jedoch redundant, und man müsste zudem daran denken, einen solchen Übergang von jedem neuen Zustand hinzuzufügen, der in Zukunft hinzugefügt wird.
Wir können dasselbe Verhalten (nämlich, dass ein Klick auf die Schaltfläche „Quit“ die Zustandsmaschine beendet, unabhängig davon, in welchem Zustand sich die Zustandsmaschine befindet) erreichen, indem wir die Zustände „ s1 “, „ s2 “ und „ s3 “ gruppieren. Dies geschieht, indem ein neuer Zustand auf oberster Ebene erstellt wird und die drei ursprünglichen Zustände zu untergeordneten Zuständen des neuen Zustands gemacht werden. Das folgende Diagramm zeigt die neue Zustandsmaschine.

Die drei ursprünglichen Zustände wurden in „ s11 “, „ s12 “ und „ s13 “ umbenannt, um zu verdeutlichen, dass sie nun Kinder des neuen Zustands der obersten Ebene „ s1 “ sind. Untergeordnete Zustände erben implizit die Übergänge ihres übergeordneten Zustands. Das bedeutet, dass es nun ausreicht, einen einzigen Übergang von „ s1 “ zum Endzustand „ s2 “ hinzuzufügen. Neue Zustände, die zu „ s1 “ hinzugefügt werden, erben diesen Übergang ebenfalls automatisch.
Um Zustände zu gruppieren, muss lediglich bei der Erstellung des Zustands der richtige übergeordnete Zustand angegeben werden. Außerdem muss festgelegt werden, welcher der untergeordneten Zustände der Anfangszustand ist (d. h., in welchen untergeordneten Zustand die Zustandsmaschine wechseln soll, wenn der übergeordnete Zustand das Ziel eines Übergangs ist).
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);In diesem Fall soll die Anwendung beendet werden, wenn die Zustandsmaschine fertig ist; daher wird das Signal „ finished()“ der Maschine mit dem Slot „ quit()“ der Anwendung verbunden.
Ein untergeordneter Zustand kann einen geerbten Übergang überschreiben. Der folgende Code fügt beispielsweise einen Übergang hinzu, der bewirkt, dass die Schaltfläche „Quit“ ignoriert wird, wenn sich die Zustandsmaschine im Zustand „ s12 “ befindet.
s12->addTransition(quitButton, &QPushButton::clicked, s12);Ein Übergang kann jeden beliebigen Zustand als Ziel haben, d. h., der Zielzustand muss sich in der Zustandshierarchie nicht auf derselben Ebene befinden wie der Quellzustand.
Verwendung von Verlaufszuständen zum Speichern und Wiederherstellen des aktuellen Zustands
Stellen Sie sich vor, wir wollten dem im vorherigen Abschnitt besprochenen Beispiel einen „Unterbrechungsmechanismus“ hinzufügen; Der Benutzer soll durch Klicken auf eine Schaltfläche die Zustandsmaschine dazu bringen können, eine andere Aufgabe auszuführen, woraufhin die Zustandsmaschine ihre vorherige Tätigkeit fortsetzen soll (d. h. in den alten Zustand zurückkehren, der in diesem Fall einer der Zustände „ s11 “, „ s12 “ oder „ s13 “ ist).
Ein solches Verhalten lässt sich leicht mithilfe von Verlaufszuständen modellieren. Ein Verlaufszustand (QHistoryState -Objekt) ist ein Pseudozustand, der den untergeordneten Zustand repräsentiert, in dem sich der übergeordnete Zustand beim letzten Verlassen des übergeordneten Zustands befand.
Ein Verlaufszustand wird als Kind des Zustands erstellt, für den wir den aktuellen Kindzustand aufzeichnen möchten; wenn die Zustandsmaschine zur Laufzeit das Vorhandensein eines solchen Zustands erkennt, zeichnet sie beim Verlassen des übergeordneten Zustands automatisch den aktuellen (tatsächlichen) Kindzustand auf. Ein Übergang zum Verlaufszustand ist faktisch ein Übergang zu dem Unterzustand, den die Zustandsmaschine zuvor gespeichert hatte; die Zustandsmaschine „leitet“ den Übergang automatisch an den tatsächlichen Unterzustand weiter.
Das folgende Diagramm zeigt die Zustandsmaschine nach dem Hinzufügen des Interrupt-Mechanismus.

Der folgende Code zeigt, wie dies implementiert werden kann; in diesem Beispiel zeigen wir einfach ein Meldungsfeld an, wenn „ s3 “ aufgerufen wird, und kehren dann sofort über den Verlaufszustand zum vorherigen untergeordneten Zustand „ s1 “ zurück.
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);Verwendung paralleler Zustände zur Vermeidung einer kombinatorischen Explosion von Zuständen
Angenommen, Sie möchten eine Reihe sich gegenseitig ausschließender Eigenschaften eines Autos in einer einzigen Zustandsmaschine modellieren. Nehmen wir an, die Eigenschaften, die uns interessieren, sind „Sauber vs. Schmutzig“ und „In Bewegung vs. Nicht in Bewegung“. Es wären vier sich gegenseitig ausschließende Zustände und acht Übergänge erforderlich, um alle möglichen Kombinationen darzustellen und frei zwischen ihnen zu wechseln.

Würden wir eine dritte Eigenschaft hinzufügen (z. B. „rot“ vs. „blau“), würde sich die Gesamtzahl der Zustände auf acht verdoppeln; und würden wir eine vierte Eigenschaft hinzufügen (z. B. „Kupé“ vs. „Cabrio“), würde sich die Gesamtzahl der Zustände erneut auf 16 verdoppeln.
Bei der Verwendung paralleler Zustände wächst die Gesamtzahl der Zustände und Übergänge linear, wenn wir weitere Eigenschaften hinzufügen, anstatt exponentiell. Darüber hinaus können Zustände zum parallelen Zustand hinzugefügt oder aus diesem entfernt werden, ohne dass dies Auswirkungen auf die zugehörigen Geschwisterzustände hat.

Um eine Gruppe paralleler Zustände zu erstellen, übergeben Sie ` QState::ParallelStates ` an den Konstruktor ` 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);Beim Eintritt in eine parallele Zustandsgruppe werden alle ihre untergeordneten Zustände gleichzeitig betreten. Übergänge innerhalb der einzelnen untergeordneten Zustände verlaufen normal. Jeder der untergeordneten Zustände kann jedoch einen Übergang ausführen, der den übergeordneten Zustand verlässt. In diesem Fall werden der übergeordnete Zustand und alle seine untergeordneten Zustände verlassen.
Die Parallelität im State-Machine-Framework folgt einer verschachtelten Semantik. Alle parallelen Operationen werden in einem einzigen, atomaren Schritt der Ereignisverarbeitung ausgeführt, sodass kein Ereignis die parallelen Operationen unterbrechen kann. Ereignisse werden jedoch weiterhin sequenziell verarbeitet, da die Maschine selbst single-threaded ist. Ein Beispiel: Betrachten wir die Situation, in der es zwei Übergänge gibt, die aus derselben parallelen Zustandsgruppe herausführen, und deren Bedingungen gleichzeitig wahr werden. In diesem Fall hat das Ereignis, das von den beiden zuletzt verarbeitet wird, keine Auswirkung, da das erste Ereignis bereits dazu geführt hat, dass die Maschine den parallelen Zustand verlassen hat.
Erkennen, dass ein zusammengesetzter Zustand beendet ist
Ein untergeordneter Zustand kann final sein (ein „ QFinalState “-Objekt); wenn ein finaler untergeordneter Zustand erreicht wird, sendet der übergeordnete Zustand das Signal „ QState::finished()“ aus. Das folgende Diagramm zeigt einen zusammengesetzten Zustand „ s1 “, der vor dem Eintritt in einen finalen Zustand einige Verarbeitungsschritte durchführt:

Wenn der Endzustand von „ s1 “ erreicht wird, sendet „ s1 “ automatisch das Signal „ finished()“ aus. Wir verwenden einen Signalübergang, damit dieses Ereignis einen Zustandswechsel auslöst:
s1->addTransition(s1, &QState::finished, s2);Die Verwendung von Endzuständen in zusammengesetzten Zuständen ist nützlich, wenn Sie die internen Details eines zusammengesetzten Zustands verbergen möchten; d. h., das Einzige, was die Außenwelt tun können sollte, ist, in den Zustand einzutreten und eine Benachrichtigung zu erhalten, wenn der Zustand seine Arbeit abgeschlossen hat. Dies ist ein sehr leistungsfähiger Abstraktions- und Kapselungsmechanismus beim Aufbau komplexer (tief verschachtelter) Zustandsmaschinen. (Im obigen Beispiel könnten Sie natürlich einen Übergang direkt aus dem Zustand „ done “ von „ s1 “ erstellen, anstatt sich auf das Signal „ finished()“ von „ s1 “ zu verlassen – allerdings mit der Folge, dass Implementierungsdetails von „ s1 “ offengelegt werden und man von diesen abhängig ist.)
Bei parallelen Zustandsgruppen wird das Signal „ QState::finished()“ ausgesendet, wenn alle untergeordneten Zustände in Endzustände übergegangen sind.
Ziellose Übergänge
Ein Übergang muss keinen Zielzustand haben. Ein Übergang ohne Ziel kann auf dieselbe Weise ausgelöst werden wie jeder andere Übergang; der Unterschied besteht darin, dass ein zielloser Übergang, wenn er ausgelöst wird, keine Zustandsänderungen bewirkt. Dies ermöglicht es Ihnen, auf ein Signal oder Ereignis zu reagieren, wenn sich Ihre Maschine in einem bestimmten Zustand befindet, ohne diesen Zustand verlassen zu müssen. Beispiel:
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);Das Meldungsfeld wird bei jedem Klick auf die Schaltfläche angezeigt, die Zustandsmaschine verbleibt jedoch in ihrem aktuellen Zustand (s1). Würde der Zielzustand jedoch explizit auf s1 festgelegt, würde s1 jedes Mal verlassen und erneut betreten werden (d. h., die Signale „ QAbstractState::entered “ () und „ QAbstractState::exited “ () würden ausgelöst).
Ereignisse, Übergänge und Guards
Eine „ QStateMachine “ führt eine eigene Ereignisschleife aus. Bei Signalübergängen (Objekte vom Typ „QSignalTransition “) sendet „ QStateMachine “ automatisch ein „ QStateMachine::SignalEvent “ an sich selbst, sobald sie das entsprechende Signal abfängt; analog dazu wird bei „ QObject “-Ereignisübergängen (Objekte vom Typ „QEventTransition “) ein „ QStateMachine::WrappedEvent “ gesendet.
Sie können eigene Ereignisse mithilfe von ` QStateMachine::postEvent()` an die Zustandsmaschine senden.
Wenn Sie ein benutzerdefiniertes Ereignis an die Zustandsmaschine senden, verfügen Sie in der Regel auch über einen oder mehrere benutzerdefinierte Übergänge, die durch Ereignisse dieses Typs ausgelöst werden können. Um einen solchen Übergang zu erstellen, erstellen Sie eine Unterklasse von ` QAbstractTransition ` und implementieren ` eventTest()` neu, wobei Sie prüfen, ob ein Ereignis Ihrem Ereignistyp entspricht (und optional weitere Kriterien erfüllen, z. B. Attribute des Ereignisobjekts).
Hier definieren wir unseren eigenen benutzerdefinierten Ereignistyp, ` StringEvent`, zum Senden von Zeichenketten an die Zustandsmaschine:
struct StringEvent : public QEvent
{
StringEvent(const QString &val)
: QEvent(QEvent::Type(QEvent::User+1)),
value(val) {}
QString value;
};Als Nächstes definieren wir einen Übergang, der nur dann ausgelöst wird, wenn die Zeichenkette des Ereignisses mit einer bestimmten Zeichenkette übereinstimmt (ein bedingter Übergang):
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;
};In der Neuimplementierung von ` eventTest()` prüfen wir zunächst, ob es sich um den gewünschten Ereignistyp handelt; ist dies der Fall, wandeln wir das Ereignis in einen ` StringEvent ` um und führen den Zeichenfolgenvergleich durch.
Im Folgenden ist ein Zustandsdiagramm dargestellt, das das benutzerdefinierte Ereignis und den Übergang verwendet:

So sieht die Implementierung des Zustandsdiagramms aus:
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);Sobald die Maschine gestartet ist, können wir Ereignisse an sie senden.
machine.postEvent(new StringEvent("Hello"));
machine.postEvent(new StringEvent("world"));Ein Ereignis, das von keinem relevanten Übergang verarbeitet wird, wird von der Zustandsmaschine stillschweigend verarbeitet. Es kann sinnvoll sein, Zustände zu gruppieren und eine Standardbehandlung für solche Ereignisse vorzusehen; wie beispielsweise in der folgenden Zustandsdiagramm dargestellt:

Bei tief verschachtelten Zustandsdiagrammen können Sie solche „Fallback“-Übergänge auf der am besten geeigneten Granularitätsebene hinzufügen.
Verwendung der Wiederherstellungsrichtlinie zur automatischen Wiederherstellung von Eigenschaften
In manchen Zustandsmaschinen kann es sinnvoll sein, den Fokus auf die Zuweisung von Eigenschaften in Zuständen zu legen und nicht auf deren Wiederherstellung, wenn der Zustand nicht mehr aktiv ist. Wenn Sie wissen, dass eine Eigenschaft immer auf ihren Anfangswert zurückgesetzt werden soll, wenn die Maschine in einen Zustand wechselt, der der Eigenschaft keinen expliziten Wert zuweist, können Sie die globale Wiederherstellungsrichtlinie auf `QStateMachine::RestoreProperties` setzen.
QStateMachine machine;
machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);Wenn diese Wiederherstellungsrichtlinie festgelegt ist, stellt die Maschine automatisch alle Eigenschaften wieder her. Wenn sie in einen Zustand wechselt, in dem eine bestimmte Eigenschaft nicht gesetzt ist, durchsucht sie zunächst die Hierarchie der Vorfahren, um zu prüfen, ob die Eigenschaft dort definiert ist. Ist dies der Fall, wird die Eigenschaft auf den vom nächsten Vorfahren definierten Wert zurückgesetzt. Ist dies nicht der Fall, wird sie auf ihren Anfangswert zurückgesetzt (d. h. den Wert der Eigenschaft, bevor irgendwelche Eigenschaftszuweisungen in Zuständen ausgeführt wurden).
Betrachten wir den folgenden Code:
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);Nehmen wir an, die Eigenschaft „ fooBar “ ist beim Start der Maschine 0,0. Befindet sich die Maschine im Zustand „ s1 “, ist die Eigenschaft 1,0, da der Zustand ihr diesen Wert explizit zuweist. Befindet sich die Maschine im Zustand „ s2 “, ist für die Eigenschaft kein Wert explizit definiert, sodass sie implizit auf 0,0 zurückgesetzt wird.
Bei der Verwendung verschachtelter Zustände definiert der übergeordnete Zustand einen Wert für die Eigenschaft, der von allen untergeordneten Zuständen geerbt wird, die der Eigenschaft nicht explizit einen Wert zuweisen.
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);Hier hat „ s1 “ zwei untergeordnete Zustände: „ s2 “ und „ s3 “. Beim Eintritt in den Zustand „ s2 “ nimmt die Eigenschaft „ fooBar “ den Wert 2,0 an, da dieser für den Zustand explizit definiert ist. Befindet sich die Zustandsmaschine im Zustand „ s3 “, ist für diesen Zustand kein Wert definiert, aber „ s1 “ legt den Wert der Eigenschaft auf 1,0 fest; daher wird dieser Wert „ fooBar “ zugewiesen.
Animationen und Zustandsmaschinen
Die Zustandsmaschinen-API ist mit dem Animations-Framework verbunden, um Eigenschaften automatisch zu animieren, sobald sie in Zuständen zugewiesen werden.
Die Zustandsmaschine stellt einen speziellen Zustand bereit, der eine Animation abspielen kann. Ein „ QState “ kann zudem Eigenschaften beim Ein- oder Austritt aus dem Zustand festlegen, und dieser spezielle Animationszustand interpoliert zwischen diesen Werten, wenn ein „ QPropertyAnimation “ angegeben wird.
Wir können einem Übergang zwischen Zuständen eine oder mehrere Animationen zuordnen, indem wir die Klassen „ QSignalTransition “ oder „ QEventTransition “ verwenden. Diese Klassen leiten sich beide von „ QAbstractTransition “ ab, das die Hilfsfunktion „ addAnimation()“ definiert, mit der eine oder mehrere Animationen angehängt werden können, die beim Übergang ausgelöst werden.
Wir haben außerdem die Möglichkeit, den Zuständen Eigenschaften zuzuordnen, anstatt die Start- und Endwerte selbst festzulegen.
Nehmen wir an, wir haben den folgenden Code:
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);Hier definieren wir zwei Zustände einer Benutzeroberfläche. In „ s1 “ ist die „ button “ klein, und in „ s2 “ ist sie größer. Wenn wir auf die Schaltfläche klicken, um von „ s1 “ zu „ s2 “ zu wechseln, wird die Geometrie der Schaltfläche sofort festgelegt, sobald der jeweilige Zustand erreicht ist. Wenn wir jedoch einen fließenden Übergang wünschen, müssen wir lediglich eine „ QPropertyAnimation “ erstellen und diese dem Übergangsobjekt hinzufügen.
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"));Das Hinzufügen einer Animation für die betreffende Eigenschaft bedeutet, dass die Eigenschaftszuweisung nicht mehr sofort wirksam wird, sobald der Zustand erreicht ist. Stattdessen beginnt die Animation beim Erreichen des Zustands und animiert die Eigenschaftszuweisung sanft. Da wir weder den Start- noch den Endwert der Animation festlegen, werden diese implizit festgelegt. Der Startwert der Animation entspricht dem aktuellen Wert der Eigenschaft zum Zeitpunkt des Animationsbeginns, und der Endwert wird auf der Grundlage der für den Zustand definierten Eigenschaftszuweisungen festgelegt.
Wenn die globale Wiederherstellungsrichtlinie der Zustandsmaschine auf `QStateMachine::RestoreProperties` gesetzt ist, können auch Animationen für die Wiederherstellung von Eigenschaften hinzugefügt werden.
Erkennen, dass alle Eigenschaften in einem Zustand gesetzt wurden
Wenn Animationen zur Zuweisung von Eigenschaften verwendet werden, definiert ein Zustand nicht mehr die genauen Werte, die eine Eigenschaft hat, wenn sich die Maschine in dem jeweiligen Zustand befindet. Während die Animation läuft, kann die Eigenschaft je nach Animation potenziell jeden beliebigen Wert annehmen.
In manchen Fällen kann es nützlich sein, erkennen zu können, wann der Eigenschaft tatsächlich der durch einen Zustand definierte Wert zugewiesen wurde.
Nehmen wir an, wir haben den folgenden Code:
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);Wenn auf „ button “ geklickt wird, wechselt die Maschine in den Zustand „ s2 “, wodurch die Geometrie der Schaltfläche festgelegt wird, und es erscheint anschließend ein Meldungsfeld, um den Benutzer darauf hinzuweisen, dass die Geometrie geändert wurde.
Im Normalfall, in dem keine Animationen verwendet werden, funktioniert dies wie erwartet. Wenn jedoch für den Übergang von „ s1 “ zu „ s2 “ eine Animation für die Eigenschaft „ geometry “ von „ button “ festgelegt ist, wird die Animation beim Eintritt in den Zustand „ s2 “ gestartet, doch die Eigenschaft „ geometry “ erreicht ihren definierten Wert erst, nachdem die Animation bereits beendet ist. In diesem Fall erscheint das Meldungsfeld, bevor die Geometrie der Schaltfläche tatsächlich festgelegt wurde.
Um sicherzustellen, dass das Meldungsfeld erst dann erscheint, wenn die Geometrie tatsächlich ihren Endwert erreicht hat, können wir das Signal „ propertiesAssigned()“ des Zustands verwenden. Das Signal „ propertiesAssigned()“ wird ausgelöst, sobald der Eigenschaft ihr Endwert zugewiesen wird – unabhängig davon, ob dies sofort oder erst nach Abschluss der Animation geschieht.
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);In diesem Beispiel wechselt die Maschine beim Klicken auf „ button “ in den Zustand „ s2 “. Sie verbleibt im Zustand „ s2 “, bis die Eigenschaft „ geometry “ auf „ QRect(0, 0, 50, 50) “ gesetzt wurde. Anschließend wechselt sie in den Zustand „ s3 “. Beim Eintritt in den Zustand „ s3 “ wird das Meldungsfeld eingeblendet. Wenn der Übergang in den Zustand „ s2 “ eine Animation für die Eigenschaft „ geometry “ beinhaltet, verbleibt die Maschine im Zustand „ s2 “, bis die Animation vollständig abgespielt wurde. Gibt es keine solche Animation, wird die Eigenschaft einfach gesetzt und der Zustand „ s3 “ wird sofort erreicht.
In beiden Fällen gilt: Befindet sich die Maschine im Zustand „ s3 “, ist garantiert, dass der Eigenschaft „ geometry “ der definierte Wert zugewiesen wurde.
Wenn die globale Wiederherstellungsrichtlinie auf „QStateMachine::RestoreProperties“ gesetzt ist, sendet der Zustand das Signal „ propertiesAssigned()“ erst aus, wenn auch diese ausgeführt wurden.
Was passiert, wenn ein Zustand verlassen wird, bevor die Animation beendet ist
Wenn ein Zustand Eigenschaftszuweisungen enthält und der Übergang in den Zustand Animationen für die Eigenschaften aufweist, kann der Zustand möglicherweise verlassen werden, bevor die Eigenschaften auf die vom Zustand definierten Werte gesetzt wurden. Dies gilt insbesondere dann, wenn es Übergänge aus dem Zustand gibt, die nicht vom Signal ` propertiesAssigned()` abhängen, wie im vorherigen Abschnitt beschrieben.
Die State-Machine-API garantiert, dass eine von der State Machine zugewiesene Eigenschaft entweder:
- der Eigenschaft wird explizit ein Wert zugewiesen;
- Derzeit in einen Wert animiert wird, der der Eigenschaft explizit zugewiesen wurde.
Wird ein Zustand vor Abschluss der Animation verlassen, hängt das Verhalten der Zustandsmaschine vom Zielzustand des Übergangs ab. Weist der Zielzustand der Eigenschaft explizit einen Wert zu, werden keine weiteren Maßnahmen ergriffen. Der Eigenschaft wird der vom Zielzustand definierte Wert zugewiesen.
Wenn der Zielzustand der Eigenschaft keinen Wert zuweist, gibt es zwei Möglichkeiten: Standardmäßig wird der Eigenschaft der Wert zugewiesen, der durch den verlassenen Zustand definiert ist (der Wert, der ihr zugewiesen worden wäre, wenn die Animation vollständig abgespielt worden wäre). Ist jedoch eine globale Wiederherstellungsrichtlinie festgelegt, hat diese Vorrang, und die Eigenschaft wird wie gewohnt wiederhergestellt.
Standardanimationen
Wie bereits beschrieben, können Sie Übergängen Animationen hinzufügen, um sicherzustellen, dass die Eigenschaftszuweisungen im Zielzustand animiert werden. Wenn Sie möchten, dass für eine bestimmte Eigenschaft unabhängig vom gewählten Übergang eine bestimmte Animation verwendet wird, können Sie diese als Standardanimation zur Zustandsmaschine hinzufügen. Dies ist insbesondere dann nützlich, wenn die von bestimmten Zuständen zugewiesenen (oder wiederhergestellten) Eigenschaften zum Zeitpunkt der Erstellung der Maschine noch nicht bekannt sind.
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"));Befindet sich die Zustandsmaschine im Zustand „ s2 “, spielt sie die Standardanimation für die Eigenschaft „ fooBar “ ab, da diese Eigenschaft durch „ s2 “ zugewiesen wird.
Beachten Sie, dass Animationen, die explizit bei Übergängen festgelegt werden, Vorrang vor jeglicher Standardanimation für die jeweilige Eigenschaft haben.
Verschachtelung von Zustandsmaschinen
QStateMachine ist eine Unterklasse von ` QState`. Dies ermöglicht es, dass eine Zustandsmaschine ein untergeordneter Zustand einer anderen Maschine ist. ` QStateMachine ` implementiert ` QState::onEntry()` neu und ruft ` QStateMachine::start()` auf, sodass die untergeordnete Zustandsmaschine automatisch ausgeführt wird, sobald der Zustand aufgerufen wird.
Die übergeordnete Zustandsmaschine behandelt die untergeordnete Maschine im Zustandsmaschinenalgorithmus als atomaren Zustand. Die untergeordnete Zustandsmaschine ist in sich geschlossen; sie verwaltet ihre eigene Ereigniswarteschlange und Konfiguration. Beachten Sie insbesondere, dass die Methode „ configuration()“ der untergeordneten Maschine nicht Teil der Konfiguration der übergeordneten Maschine ist (lediglich die untergeordnete Maschine selbst ist Teil davon).
Zustände der untergeordneten Zustandsmaschine können nicht als Ziele von Übergängen in der übergeordneten Zustandsmaschine festgelegt werden; dies ist nur für die untergeordnete Zustandsmaschine selbst möglich. Umgekehrt können Zustände der übergeordneten Zustandsmaschine nicht als Ziele von Übergängen in der untergeordneten Zustandsmaschine festgelegt werden. Das Signal „ finished “ () der untergeordneten Zustandsmaschine kann verwendet werden, um einen Übergang in der übergeordneten Maschine auszulösen.
Siehe auch „Qt State Machine -Übersicht “ und „Qt State Machine -QML-Handbuch“.
© 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.