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 の状態にあります。以下は、この例のさまざまな状態を示す状態図です。

以下のコードスニペットは、このようなステートマシンを作成するために必要なコードを示しています。
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つの状態は、新しいトップレベル状態である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 オブジェクト)とは、親状態が最後に終了する前にあった子状態を表す疑似状態のことです。
履歴状態は、現在の子状態を記録したい状態の子として作成されます。実行時にステートマシンがそのような状態の存在を検知すると、親状態が終了した際に現在の(実際の)子状態を自動的に記録します。 履歴状態への遷移は、実際にはステートマシンが以前に保存していた子状態への遷移であり、ステートマシンは自動的にその遷移を実際の子状態へと「転送」します。
次の図は、割り込みメカニズムが追加された後のステートマシンを示しています。

以下のコードは、その実装方法を示しています。この例では、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つの遷移が必要になります。

もし3つ目の特性(例えば「赤」対「青」)を追加すると、状態の総数は2倍の8つになります。さらに4つ目の特性(例えば「クローズド」対「コンバーチブル」)を追加すると、状態の総数は再び2倍の16つになります。
この指数関数的な増加は、並列状態を使用することで抑制でき、これにより、プロパティを追加しても状態数と遷移数の増加は線形に抑えられます。さらに、並列状態への状態の追加や削除は、その兄弟状態には一切影響を与えません。以下の状態図は、車の例におけるさまざまな並列状態を示しています。

並列状態グループを作成するには、childModeをQState.ParallelStatesに設定します。
並列状態グループに入ると、そのすべての子状態が同時に進入されます。個々の子状態内での遷移は通常通り行われます。ただし、いずれかの子状態が、親状態を離れる遷移を行う可能性があります。これが発生すると、親状態とそのすべての子状態から離脱します。
ステートマシンフレームワークにおける並列処理は、インターリーブ型セマンティクスに従います。すべての並列操作は、イベント処理の単一の原子的なステップ内で実行されるため、いかなるイベントも並列操作を中断することはできません。ただし、マシン自体がシングルスレッドであるため、イベントの処理自体は依然として順次行われます。 例えば、同じ並列状態グループから脱出する2つの遷移があり、それらの条件が同時に真になった場合を考えてみましょう。この場合、2つのイベントのうち最後に処理されたイベントは、何の影響も及ぼしません。
複合状態からの退出
子状態は最終状態(FinalState オブジェクト)となり得ます。最終状態の子状態に入ると、親状態はState::finished シグナルをエミットします。次の図は、最終状態に入る前に何らかの処理を行う複合状態s1 を示しています:

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.