Auf dieser Seite

Qt State Machine QML-Handbuch

Qt State Machine QML-APIs stellen Typen zum Erstellen und Ausführen von Zustandsgraphen in QML bereit. Dies ähnelt dem C++-Framework für Zustandsmaschinen, das auf Harels „Statecharts: A visual formalism for complex systems“ basiert, welches 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 Zustandsdiagrammen in QML-Anwendungen einzubetten.

Für Benutzeroberflächen mit mehreren visuellen Zuständen, die unabhängig vom logischen Zustand der Anwendung sind, sollten Sie die Verwendung von QML-Zuständen und -Übergängen in Betracht ziehen.

Eine vollständige Liste der vom Framework bereitgestellten QML-Typen zum Erstellen ereignisgesteuerter Zustandsmaschinen finden Sie unter: Qt State Machine QML Types

Verwendung von QtQuick- und QtQml.StateMachine-Importen

Warnung: Wenn Sie versuchen, sowohl ` QtQuick ` als auch `QtQml.StateMachine ` in einer einzigen QML-Datei zu importieren, stellen Sie sicher, dass Sie `QtQml.StateMachine ` zuletzt importieren. Auf diese Weise wird der Typ `State` vom Declarative State Machine Framework bereitgestellt und nicht von ` QtQuick`:

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 jegliche Mehrdeutigkeit mit dem „State“-Element von QtQuick zu vermeiden:

import QtQuick
import QtQml.StateMachine as DSM

DSM.StateMachine {
    DSM.State {
        // ...
    }
}

Eine einfache Zustandsmaschine

Um die Kernfunktionalität der State-Machine-API zu veranschaulichen, betrachten wir ein Beispiel: Eine Zustandsmaschine mit drei Zuständen: „ s1 “, „ s2 “ und „ s3 “. Die Zustandsmaschine wird über eine einzige Schaltfläche gesteuert; wenn die Schaltfläche angeklickt wird, wechselt die Maschine in einen anderen Zustand. Zu Beginn befindet sich die Zustandsmaschine im Zustand „ s1 “. Im Folgenden finden Sie ein Zustandsdiagramm, das die verschiedenen Zustände in unserem Beispiel veranschaulicht.

Drei Zustände s1, s2, s3, die bei „button.clicked“ durchlaufen

Der folgende Codeausschnitt zeigt den Code, der zum Erstellen einer solchen Zustandsmaschine 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")
        }
    }

Die Zustandsmaschine läuft asynchron und wird so Teil der Ereignisschleife Ihrer Anwendung.

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 (Objekt vom Typ „FinalState “) verfügen. Wenn die Zustandsmaschine in den Endzustand auf oberster Ebene wechselt, sendet sie das Signal „ finished “ aus und stoppt.

Um einen Endzustand in den Graphen einzufügen, müssen Sie lediglich ein „ FinalState “-Objekt erstellen und es als Ziel eines oder mehrerer Übergänge verwenden.

Gemeinsame Nutzung von Übergängen

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 für jeden Zustand einen Übergang hinzufügen; dies erscheint jedoch redundant, und man müsste zudem daran denken, einen solchen Übergang von jedem neuen Zustand aus hinzuzufügen, der in Zukunft hinzugefügt wird.

Wir können dasselbe Verhalten (nämlich, dass ein Klick auf die Schaltfläche „Beenden“ die Zustandsmaschine beendet, unabhängig davon, in welchem Zustand sich die Zustandsmaschine gerade befindet) erreichen, indem wir die Zustände „ s1 “, „ s2 “ und „ s3 “ gruppieren. Dies geschieht, indem wir einen neuen Zustand auf oberster Ebene erstellen und die drei ursprünglichen Zustände zu untergeordneten Zuständen des neuen Zustands machen. Das folgende Diagramm zeigt die neue Zustandsmaschine.

Zustand s1, der drei Unterzustände s11, s12 und s13 enthält, die bei „button.clicked“-Übergängen zyklisch durchlaufen werden

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

Um Zustände zu gruppieren, muss lediglich beim Erstellen des Zustands der richtige übergeordnete Zustand angegeben werden. Außerdem muss festgelegt werden, welcher der untergeordneten Zustände der Anfangszustand ist (der untergeordnete Zustand, in den die Zustandsmaschine wechseln 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 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.

            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 jeden beliebigen Zustand als Ziel haben, unabhängig davon, wo sich der Zielzustand in der Zustandshierarchie befindet.

Verwendung von Verlaufszuständen

Stellen wir uns 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 nicht damit zusammenhängende 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 drei Zustände ist).

Ein solches Verhalten lässt sich mithilfe von Verlaufszuständen leicht modellieren. Ein Verlaufszustand (HistoryState -Objekt) ist ein Pseudozustand, der den untergeordneten Zustand repräsentiert, in dem sich der übergeordnete Zustand vor seinem letzten Verlassen 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 automatisch den aktuellen (tatsächlichen) Kindzustand auf, sobald der übergeordnete Zustand beendet wird. Ein Übergang zum Verlaufszustand ist faktisch ein Übergang zu dem untergeordneten Zustand, den die Zustandsmaschine zuvor gespeichert hatte; die Zustandsmaschine „leitet“ den Übergang automatisch an den tatsächlichen untergeordneten Zustand weiter.

Das folgende Diagramm zeigt die Zustandsmaschine nach dem Hinzufügen des Interrupt-Mechanismus.

Zustand s1 mit Unterzuständen und einem Verlaufszustand; ein Druck auf die Unterbrechungstaste führt zu einem Übergang nach s3, der nach Abschluss des Vorgangs zum Verlaufszustand von s1 zurückkehrt

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.

    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()
    }

Verwendung paralleler Zustände

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 die Zustände darzustellen und frei zwischen allen möglichen Kombinationen zu wechseln, wie in der folgenden Zustandsübersicht gezeigt.

Vier Zustände: sauber/unbeweglich, sauber/beweglich, verschmutzt/unbeweglich, verschmutzt/beweglich mit Übergängen zwischen „gestartet“, „gestoppt“, „gereinigt“ und „verschmutzt“

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.

Dieser exponentielle Anstieg lässt sich durch die Verwendung paralleler Zustände reduzieren, was ein lineares Wachstum der Anzahl von Zuständen und Übergängen ermöglicht, wenn wir weitere Eigenschaften hinzufügen. Darüber hinaus können Zustände zum parallelen Zustand hinzugefügt oder daraus entfernt werden, ohne dass dies Auswirkungen auf die zugehörigen Geschwisterzustände hat. Das folgende Zustandsdiagramm zeigt die verschiedenen parallelen Zustände für das Auto-Beispiel.

Paralleler Zustand s1 mit den Bereichen s11 (sauber/verschmutzt) und s12 (in Bewegung/nicht in Bewegung)

Um eine parallele Zustandsgruppe zu erstellen, setzen Sie `childMode` auf ` QState.ParallelStates`.

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

Beim Eintritt in eine parallele Zustandsgruppe werden alle zugehörigen 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. Betrachten wir beispielsweise 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.

Verlassen eines zusammengesetzten Zustands

Ein untergeordneter Zustand kann ein Endzustand sein (ein „ FinalState “-Objekt); wenn ein untergeordneter Endzustand erreicht wird, sendet der übergeordnete Zustand das Signal „ State::finished “ aus. Das folgende Diagramm zeigt einen zusammengesetzten Zustand „ s1 “, der vor dem Eintritt in einen Endzustand einige Verarbeitungsschritte durchführt:

Zustand s1 mit einem Verarbeitungsunterzustand, der in den Endzustand übergeht und den Übergang zu s2 auslöst

Wenn der Endzustand von „ s1 “ erreicht wird, sendet „ s1 “ automatisch das Signal „ finished “ aus. Wir verwenden einen Signalübergang, um zu bewirken, dass dieses Ereignis einen Zustandswechsel auslöst:

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 möchten. Die Außenwelt sollte in den Zustand eintreten und eine Benachrichtigung erhalten können, wenn der Zustand seine Arbeit abgeschlossen hat, ohne die internen Details kennen zu müssen. 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 – dies hätte jedoch zur Folge, dass Implementierungsdetails von „ s1 “ offengelegt würden und man davon abhängig wäre.)

Bei parallelen Zustandsgruppen wird das Signal „ State::finished “ ausgesendet, sobald alle untergeordneten Zustände in ihre Endzustände übergegangen sind.

Zielfreie Übergänge

Ein Übergang muss keinen Zielzustand haben. Ein Übergang ohne Ziel kann auf dieselbe Weise wie jeder andere Übergang ausgelöst werden; der Unterschied besteht darin, dass er 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. Zum 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 „Taste gedrückt“ wird bei jedem Klick auf die Schaltfläche angezeigt, doch die Zustandsmaschine verbleibt in ihrem aktuellen Zustand (s1). Wäre der Zielzustand explizit auf s1 festgelegt, würde s1 jedes Mal verlassen und erneut betreten werden (die Signale „ QAbstractState::entered “ und „ QAbstractState::exited “ würden ausgegeben).

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