Qt State Machine QML-Leitfaden
Qt State Machine QML-APIs bieten Typen für die Erstellung und Ausführung von Zustandsdiagrammen in QML. Es ähnelt dem C++ State Machine Framework, das auf Harels Statecharts basiert: Ein visueller Formalismus für komplexe Systeme, der auch die Grundlage für UML-Zustandsdiagramme bildet. Wie sein C++ counterpart bietet das Framework eine API und ein Ausführungsmodell auf Basis von State Chart XML (SCXML), um die Elemente und die Semantik von Statecharts in QML-Anwendungen einzubetten.
Für Benutzeroberflächen mit mehreren visuellen Zuständen, unabhängig vom logischen Zustand der Anwendung, empfiehlt sich die Verwendung von QML States and Transitions.
Eine vollständige Liste der QML-Typen, die das Framework zur Erstellung ereignisgesteuerter Zustandsautomaten bereitstellt, finden Sie unter: Qt State Machine QML Types
Verwendung der Importe von QtQuick und QtQml.StateMachine
Warnung: Wenn Sie versuchen, sowohl QtQuick als auch QtQml. StateMachine in eine einzige QML-Datei zu importieren, stellen Sie sicher, dass Sie QtQml. StateMachine zuletzt importieren. Auf diese Weise wird der State-Typ durch das Declarative State Machine Framework und nicht durch QtQuick bereitgestellt:
import QtQuick import QtQml.StateMachine StateMachine { State { // okay, is of type QtQml.StateMachine.State } }
Alternativ können Sie QtQml. StateMachine in einen separaten Namespace importieren, um jede Mehrdeutigkeit mit dem Element State von QtQuick zu vermeiden:
import QtQuick import QtQml.StateMachine as DSM DSM.StateMachine { DSM.State { // ... } }
Ein einfacher Zustandsautomat
Um die Kernfunktionalität der State Machine API zu demonstrieren, wollen wir uns ein Beispiel ansehen: Ein Zustandsautomat mit drei Zuständen, s1
, s2
und s3
. Der Zustandsautomat wird durch eine einzelne Schaltfläche gesteuert; wenn die Schaltfläche angeklickt wird, geht der Automat in einen anderen Zustand über. Zu Beginn befindet sich der Zustandsautomat im Zustand s1
. Im Folgenden finden Sie ein Zustandsdiagramm, das die verschiedenen Zustände in unserem Beispiel zeigt.
Der folgende Ausschnitt zeigt den Code, der zur Erstellung eines solchen Zustandsautomaten erforderlich ist.
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") } }
Der Zustandsautomat läuft asynchron und wird Teil der Ereignisschleife Ihrer Anwendung.
Zustandsautomaten, die beendet werden
Der im vorigen Abschnitt definierte Zustandsautomat wird nie beendet. Damit ein Zustandsautomat beendet werden kann, muss er einen Endzustand auf oberster Ebene haben (FinalState object). Wenn der Zustandsautomat in den Endzustand der obersten Ebene eintritt, gibt er das Signal finished aus und hält an.
Um einen Endzustand in den Graphen einzuführen, müssen Sie lediglich ein FinalState Objekt erstellen und es als Ziel eines oder mehrerer Übergänge verwenden.
Gemeinsame Nutzung von Übergängen
Nehmen wir an, der Benutzer soll die Anwendung jederzeit durch Anklicken einer Schaltfläche "Beenden" beenden können. Um dies zu erreichen, müssen wir einen Endzustand erstellen und ihn zum Ziel einer Transition machen, die mit dem clicked() -Signal der Quit-Schaltfläche verbunden ist. Wir könnten für jeden Zustand eine Transition hinzufügen; dies erscheint jedoch redundant und man müsste auch daran denken, eine solche Transition für jeden neuen Zustand hinzuzufügen, der in Zukunft hinzugefügt wird.
Wir können das gleiche Verhalten erreichen (nämlich, dass das Anklicken der Quit-Schaltfläche den Zustandsautomaten beendet, unabhängig davon, in welchem Zustand sich der Zustandsautomat befindet), indem wir die Zustände s1
, s2
und s3
gruppieren. Dazu wird ein neuer Top-Level-Zustand erstellt und die drei ursprünglichen Zustände werden zu Kindzuständen des neuen Zustands. Das folgende Diagramm zeigt den neuen Zustandsautomaten.
Die drei ursprünglichen Zustände wurden in s11
, s12
und s13
umbenannt, um zu verdeutlichen, dass sie nun untergeordnete Zustände des neuen Top-Level-Zustands s1
sind. Untergeordnete Zustände erben implizit die Übergänge ihres übergeordneten Zustands. Das bedeutet, dass es jetzt ausreicht, einen einzigen Übergang von s1
zum Endzustand s2
hinzuzufügen. Neue Zustände, die zu s1
hinzugefügt werden, erben automatisch diesen Übergang.
Um Zustände zu gruppieren, müssen Sie bei der Erstellung des Zustands nur noch den richtigen Elternteil angeben. Außerdem müssen Sie angeben, welcher der untergeordneten Zustände der Anfangszustand ist (der untergeordnete Zustand, in den der Zustandsautomat eintreten soll, wenn der übergeordnete Zustand das Ziel eines Übergangs ist).
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() }
In diesem Fall wollen wir, dass die Anwendung beendet wird, wenn der Zustandsautomat fertig ist, also wird das finished() -Signal des Automaten mit dem quit() -Slot der Anwendung verbunden.
Ein untergeordneter Zustand kann einen geerbten Übergang außer Kraft setzen. Der folgende Code fügt beispielsweise einen Übergang hinzu, der bewirkt, dass die Schaltfläche "Beenden" ignoriert wird, wenn sich der Zustandsautomat im Zustand s12
befindet.
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") }
Ein Übergang kann einen beliebigen Zustand als Ziel haben, unabhängig davon, wo sich der Zielzustand in der Zustandshierarchie befindet.
Verwendung von Verlaufszuständen
Stellen Sie sich vor, dass wir das im vorigen Abschnitt besprochene Beispiel um einen "Unterbrechungs"-Mechanismus erweitern wollen; der Benutzer sollte auf eine Schaltfläche klicken können, um den Zustandsautomaten eine nicht damit zusammenhängende Aufgabe ausführen zu lassen, woraufhin der Zustandsautomat das fortsetzen sollte, was er zuvor getan hat (d. h. in den alten Zustand zurückkehren, der in diesem Fall einer der drei Zustände ist).
Ein solches Verhalten kann leicht mit Hilfe von Historienzuständen modelliert werden. Ein historischer Zustand (HistoryState object) ist ein Pseudo-Zustand, der den untergeordneten Zustand darstellt, in dem sich der übergeordnete Zustand befand, bevor er zuletzt verlassen wurde.
Ein historischer Zustand wird als Unterzustand des Zustands erstellt, für den wir den aktuellen Unterzustand aufzeichnen möchten; wenn der Zustandsautomat das Vorhandensein eines solchen Zustands zur Laufzeit erkennt, zeichnet er automatisch den aktuellen (echten) Unterzustand auf, wenn der übergeordnete Zustand beendet wird. Ein Übergang in den History-Zustand ist in Wirklichkeit ein Übergang in den Child-Zustand, den der Zustandsautomat zuvor gespeichert hatte; der Zustandsautomat "leitet" den Übergang automatisch in den realen Child-Zustand weiter.
Das folgende Diagramm zeigt den Zustandsautomaten, nachdem der Unterbrechungsmechanismus hinzugefügt wurde.
Der folgende Code zeigt, wie er implementiert werden kann; in diesem Beispiel wird einfach ein Meldungsfenster angezeigt, wenn s3
eingegeben wird, und dann sofort über den Verlaufszustand zum vorherigen Unterzustand von s1
zurückgekehrt.
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() }
Parallele Zustände verwenden
Nehmen wir an, Sie wollten eine Reihe von sich gegenseitig ausschließenden Eigenschaften eines Autos in einem einzigen Zustandsautomaten modellieren. Nehmen wir an, die Eigenschaften, an denen wir interessiert sind, 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 die Zustände darzustellen und sich frei zwischen allen möglichen Kombinationen zu bewegen, wie im folgenden Zustandsdiagramm dargestellt.
Wenn wir eine dritte Eigenschaft hinzufügen (z. B. Rot vs. Blau), würde sich die Gesamtzahl der Zustände auf acht verdoppeln; und wenn wir eine vierte Eigenschaft hinzufügen (z. B. Geschlossen vs. Umwandelbar), würde sich die Gesamtzahl der Zustände noch einmal verdoppeln, nämlich auf 16.
Dieser exponentielle Anstieg kann durch die Verwendung paralleler Zustände verringert werden, was ein lineares Wachstum der Anzahl der Zustände und Übergänge ermöglicht, wenn wir weitere Eigenschaften hinzufügen. Darüber hinaus können Zustände zu einem parallelen Zustand hinzugefügt oder aus ihm entfernt werden, ohne dass sich dies auf einen ihrer Geschwisterzustände auswirkt. Das folgende Zustandsdiagramm zeigt die verschiedenen parallelen Zustände für das Auto-Beispiel.
Um eine parallele Zustandsgruppe zu erstellen, setzen Sie childMode auf QState.ParallelStates.
Wenn eine parallele Zustandsgruppe eingegeben wird, werden alle ihre Unterzustände gleichzeitig eingegeben. Übergänge innerhalb der einzelnen Unterzustände funktionieren normal. Allerdings kann jeder der Unterzustände einen Übergang nehmen, 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, so dass kein Ereignis die parallelen Operationen unterbrechen kann. Die Ereignisse werden jedoch nach wie vor sequentiell verarbeitet, da die Maschine selbst einen einzigen Thread hat. Betrachten Sie beispielsweise die Situation, dass zwei Übergänge dieselbe parallele Zustandsgruppe verlassen und ihre Bedingungen gleichzeitig wahr werden. In diesem Fall hat das Ereignis, das als letztes verarbeitet wird, keine Auswirkungen.
Beendigung eines zusammengesetzten Zustands
Ein untergeordneter Zustand kann endgültig sein (ein FinalState Objekt); wenn ein endgültiger untergeordneter Zustand betreten wird, sendet der übergeordnete Zustand das Signal State::finished. Das folgende Diagramm zeigt einen zusammengesetzten Zustand s1
, der vor dem Eintritt in einen endgültigen Zustand einige Verarbeitungen durchführt:
Wenn der Endzustand von s1
erreicht ist, sendet s1
automatisch finished aus. Wir verwenden einen Signalübergang, um dieses Ereignis zum Auslösen eines Zustandswechsels zu veranlassen:
State { id: s1 SignalTransition { targetState: s2 signal: s1.finished } }
Die Verwendung von Endzuständen in zusammengesetzten Zuständen ist nützlich, wenn Sie die internen Details eines zusammengesetzten Zustands verbergen wollen. Die Außenwelt sollte in der Lage sein, den Zustand zu betreten und eine Benachrichtigung zu erhalten, wenn der Zustand seine Arbeit beendet hat, ohne die internen Details kennen zu müssen. Dies ist ein sehr mächtiger Abstraktions- und Kapselungsmechanismus beim Aufbau komplexer (tief verschachtelter) Zustandsautomaten. (Im obigen Beispiel könnte man natürlich auch einen Übergang direkt aus dem Zustand s1
's done
erzeugen, anstatt sich auf das Signal s1
's finished() zu verlassen, aber mit der Konsequenz, dass die Implementierungsdetails von s1
offengelegt werden und davon abhängig sind).
Bei parallelen Zustandsgruppen wird das Signal State::finished ausgegeben, wenn alle untergeordneten Zustände den Endzustand erreicht haben.
Ziellose Übergänge
Ein Übergang muss keinen Zielzustand haben. Ein Übergang ohne Ziel kann auf die gleiche Weise wie jeder andere Übergang ausgelöst werden, mit dem Unterschied, dass er keine Zustandsänderungen verursacht. So können Sie auf ein Signal oder Ereignis reagieren, wenn sich Ihre Maschine in einem bestimmten Zustand befindet, ohne diesen Zustand verlassen zu müssen. Ein Beispiel:
Button { id: button text: "button" StateMachine { id: stateMachine initialState: s1 running: true State { id: s1 SignalTransition { signal: button.clicked onTriggered: console.log("button pressed") } } } }
Die Meldung "Schaltfläche gedrückt" wird jedes Mal angezeigt, wenn die Schaltfläche angeklickt wird, aber der Zustandsautomat bleibt in seinem aktuellen Zustand (s1). Würde der Zielzustand explizit auf s1 gesetzt, würde s1 jedes Mal verlassen und wieder betreten werden (die Signale QAbstractState::entered und QAbstractState::exited würden ausgegeben).
Verwandte Informationen
© 2025 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.