このページの内容

Qt State Machine QMLガイド

Qt State Machine QML API は、QML 内でステートグラフを作成・実行するための型を提供します。これは、Harel の『Statecharts: A visual formalism for complex systems』に基づく C++ ステートマシンフレームワークに類似しており、UML 状態遷移図の基礎ともなっています。C++ counterpart と同様に、このフレームワークは、State Chart XML (SCXML)に基づいたAPIと実行モデルを提供し、QMLアプリケーションにステートチャートの要素と意味論を組み込むことを可能にします。

アプリケーションの論理状態とは独立して、複数の視覚的状態を持つユーザーインターフェースについては、QMLの「状態と遷移(States and Transitions)」の使用を検討してください。

イベント駆動型ステートマシンを作成するためにこのフレームワークが提供する QML 型の完全なリストについては、以下を参照してください: Qt State Machine QML Types

QtQuick と QtQml.StateMachine の両方のインポートを使用する場合

警告: 1つのQMLファイル内でQtQuick とQtQml.StateMachineの両方をインポートしようとする場合は 、必ずQtQml.StateMachine を最後にインポートしてください。これにより、State型はQtQuick ではなく、Declarative State Machine Frameworkによって提供されます:

import QtQuick
import QtQml.StateMachine

StateMachine {
    State {
        // okay, is of type QtQml.StateMachine.State
    }
}

あるいは、QtQuick のState項目との曖昧さを避けるために、QtQml をStateMachine として別の名前空間にインポートすることもできます:

import QtQuick
import QtQml.StateMachine as DSM

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

単純なステートマシン

ステートマシンAPIの中核となる機能を実証するために、例を見てみましょう。3つの状態(s1 、s2 、s3 )を持つステートマシンです。このステートマシンは1つのボタンによって制御され、ボタンがクリックされると、別の状態に移行します。初期状態では、ステートマシンはs1 の状態にあります。以下は、この例のさまざまな状態を示す状態図です。

ボタンがクリックされた際の遷移で、3つの状態 s1、s2、s3 が切り替わる

以下のコードスニペットは、このようなステートマシンを作成するために必要なコードを示しています。

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

ステートマシンは非同期で実行され、アプリケーションのイベントループの一部となります。

終了するステートマシン

前のセクションで定義したステートマシンは、終了することはありません。ステートマシンが終了するためには、最上位の最終状態(FinalState オブジェクト)が必要です。ステートマシンが最上位の最終状態に入ると、マシンはfinished シグナルを発し、停止します。

グラフに最終状態を導入するには、FinalState オブジェクトを作成し、それを1つ以上の遷移のターゲットとして指定するだけで十分です。

遷移の共有

ユーザーが「終了」ボタンをクリックすることで、いつでもアプリケーションを終了できるようにしたいとします。これを実現するには、最終状態を作成し、それを「終了」ボタンのclicked()シグナルに関連付けられた遷移のターゲットにする必要があります。 各状態に遷移を追加することも可能ですが、これは冗長であるうえ、将来新たに状態が追加されるたびに、その状態からも同様の遷移を追加することを忘れてはなりません。

s1 、s2 、s3 の各状態をグループ化することで、(つまり、ステートマシンがどの状態にあっても「終了」ボタンをクリックするとステートマシンが終了するという)同じ動作を実現できます。これを行うには、新しいトップレベルの状態を作成し、元の3つの状態をその子状態として設定します。以下の図は、新しいステートマシンを示しています。

ボタンがクリックされた際の遷移に伴い、3つのサブステート s11、s12、s13 を循環するステート s1

元の3つの状態は、新しいトップレベル状態であるs1 の子状態となったことを反映して、s11 、s12 、s13 と名前が変更されました。子状態は親状態の遷移を暗黙的に継承します。つまり、s1 から最終状態であるs2 への遷移を1つ追加するだけで済むようになりました。s1 に追加された新しい状態は、この遷移を自動的に継承します。

状態をグループ化するには、状態の作成時に適切な親状態を指定するだけで十分です。また、どの子状態を初期状態とするか(親状態が遷移のターゲットとなった際に、ステートマシンが移行すべき子状態)を指定する必要があります。

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

この例では、ステートマシンが終了した際にアプリケーションを終了させたいので、ステートマシンのfinished()シグナルをアプリケーションのquit()スロットに接続しています。

子状態は、継承された遷移をオーバーライドすることができます。たとえば、次のコードは、ステートマシンがs12 状態にあるときに「Quit」ボタンを事実上無視するようにする遷移を追加しています。

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

遷移は、ターゲット状態が状態階層のどこにあるかに関係なく、任意の状態をターゲットにすることができます。

履歴状態の使用

前のセクションで説明した例に「割り込み」メカニズムを追加したいと仮定しましょう。 ユーザーはボタンをクリックして、ステートマシンに無関係なタスクを実行させ、その後、ステートマシンが以前行っていた処理を再開できるようにする必要があります(つまり、以前の状態、この場合は3つの状態のうちの1つに戻る)。

このような挙動は、履歴状態を使用することで簡単にモデル化できます。履歴状態(HistoryState オブジェクト)とは、親状態が最後に終了する前にあった子状態を表す疑似状態のことです。

履歴状態は、現在の子状態を記録したい状態の子として作成されます。実行時にステートマシンがそのような状態の存在を検知すると、親状態が終了した際に現在の(実際の)子状態を自動的に記録します。 履歴状態への遷移は、実際にはステートマシンが以前に保存していた子状態への遷移であり、ステートマシンは自動的にその遷移を実際の子状態へと「転送」します。

次の図は、割り込みメカニズムが追加された後のステートマシンを示しています。

サブ状態と履歴状態を持つ状態 s1 において、中断ボタンを押すと s3 へ遷移し、処理が完了すると s1 の履歴状態に戻る

以下のコードは、その実装方法を示しています。この例では、s3 に入った際にメッセージボックスを表示し、その後、履歴状態を経由して直ちに前の子状態であるs1 に戻るという単純な処理を行っています。

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

並列状態の使用

ある自動車の相互に排他的な一連の特性を、単一の状態マシンでモデル化したいと仮定します。対象となる特性が「Clean(きれい)」対「Dirty(汚い)」、および「Moving(移動中)」対「Not moving(停止中)」であるとします。以下の状態図に示すように、これらの状態を表現し、すべての可能な組み合わせの間を自由に移動するには、4つの相互に排他的な状態と8つの遷移が必要になります。

4つの状態:「清潔/静止」、「清潔/移動」、「汚れている/静止」、「汚れている/移動」(開始、停止、清掃、汚染への遷移を含む)

もし3つ目の特性(例えば「赤」対「青」)を追加すると、状態の総数は2倍の8つになります。さらに4つ目の特性(例えば「クローズド」対「コンバーチブル」)を追加すると、状態の総数は再び2倍の16つになります。

この指数関数的な増加は、並列状態を使用することで抑制でき、これにより、プロパティを追加しても状態数と遷移数の増加は線形に抑えられます。さらに、並列状態への状態の追加や削除は、その兄弟状態には一切影響を与えません。以下の状態図は、車の例におけるさまざまな並列状態を示しています。

領域 s11(クリーン/ダーティ)および s12(移動中/非移動中)を含む並列状態 s1

並列状態グループを作成するには、childModeをQState.ParallelStatesに設定します。

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

並列状態グループに入ると、そのすべての子状態が同時に進入されます。個々の子状態内での遷移は通常通り行われます。ただし、いずれかの子状態が、親状態を離れる遷移を行う可能性があります。これが発生すると、親状態とそのすべての子状態から離脱します。

ステートマシンフレームワークにおける並列処理は、インターリーブ型セマンティクスに従います。すべての並列操作は、イベント処理の単一の原子的なステップ内で実行されるため、いかなるイベントも並列操作を中断することはできません。ただし、マシン自体がシングルスレッドであるため、イベントの処理自体は依然として順次行われます。 例えば、同じ並列状態グループから脱出する2つの遷移があり、それらの条件が同時に真になった場合を考えてみましょう。この場合、2つのイベントのうち最後に処理されたイベントは、何の影響も及ぼしません。

複合状態からの退出

子状態は最終状態(FinalState オブジェクト)となり得ます。最終状態の子状態に入ると、親状態はState::finished シグナルをエミットします。次の図は、最終状態に入る前に何らかの処理を行う複合状態s1 を示しています:

状態 s1 から、処理サブ状態を経由して最終状態へ遷移し、s2 への移行がトリガーされる

s1 の最終状態に入ると、s1 は自動的にfinished を発行します。シグナル遷移を使用して、このイベントが状態変更をトリガーするようにします:

State {
    id: s1
    SignalTransition {
        targetState: s2
        signal: s1.finished
    }
}

複合状態において最終状態を使用することは、複合状態の内部詳細を隠蔽したい場合に役立ちます。外部からは、内部の詳細を知る必要なく、その状態に入り、状態が処理を完了した際に通知を受け取れるようにすべきです。 これは、複雑な(深くネストされた)ステートマシンを構築する際の、非常に強力な抽象化およびカプセル化の仕組みです。(上記の例では、もちろんs1 のfinished()シグナルに依存するのではなく、s1 のdone 状態から直接遷移を作成することも可能ですが、その場合はs1 の実装詳細が公開され、それに依存することになります)。

並列状態グループの場合、State::finished シグナルは、すべての子状態が最終状態に入ったときに発火します。

ターゲットのない遷移

遷移にターゲット状態が必ずしも必要というわけではありません。ターゲットのない遷移は、他の遷移と同様にトリガーできますが、状態変化を引き起こさない点が異なります。これにより、マシンが特定の状態にあるときにシグナルやイベントに反応しつつ、その状態を離れることなく処理を行うことが可能になります。例えば:

Button {
    id: button
    text: "button"
    StateMachine {
        id: stateMachine
        initialState: s1
        running: true
        State {
            id: s1
            SignalTransition {
                signal: button.clicked
                onTriggered: console.log("button pressed")
            }
        }
    }
}

ボタンがクリックされるたびに「button pressed」というメッセージが表示されますが、ステートマシンは現在の状態(s1)のまま維持されます。もしターゲット状態が明示的に s1 に設定されていた場合、s1 から毎回退出してから再進入することになり(QAbstractState::entered およびQAbstractState::exited シグナルが発行されます)、その都度状態変化が生じることになります。

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