今回はシグナル遷移がどのようなものかを詳しく説明し,シグナル遷移の独自拡張の方法について説明します。
シグナル遷移
シグナル遷移とは図1のように表すことができます。図で「遷移前の状態」と書かれているのが現在の状態のときに,あるオブジェクトで特定のシグナルが発生すると引き起されるように定義された遷移です。
シグナル遷移を表すためにはQSignalTransitionのインスタンスが用いられ,遷移のために表1に挙げるようなプロパティがあります。
表1 シグナル遷移のプロパティ
| プロパティ | データ型 | 説明 |
|---|---|---|
| senderObject | QObject* | シグナル発生源となるオブジェクト。 |
| signal | QByteArray | 遷移を引き起すシグナル。 |
| sourceState | QState* const | 遷移前の状態。読込み専用プロパティ。内部的には親オブジェクトとなる。 |
| targetState | QAbstractState* | 遷移後の状態。 |
| targetStates | QList<QAbstractState*> | 並列状態のための遷移後の状態リスト。 |
最初の4つのプロパティを図1と見比べてみると,これらのプロパティでシグナル遷移が定まっているのがわかります。
シグナル遷移の内部動作
ステートマシンフレームワーク内でのシグナル遷移の処理概要は以下のようになっています。
- シグナル遷移の対象となっているオブジェクトでシグナルが発生する。
- QStateMachineはイベント型がQEvent::StateMachineSignalのイベントQStateMachine::SignalEventを発生させ,QSignalTransitionインスタンスに送る。
- QSignalTransition::eventTest()でイベントを受け取り,遷移対象のシグナルかを判定する。戻り値がtrueならば状態が遷移し,falseならば遷移しない。
前回の説明でも触れたように,ステートマシンフレームワークでは,Qtのイベントループを介して状態遷移機械がイベントループで開始されるようにスケジューリングされます。したがって,ステートマシンフレームワークを用いるには,イベントループを起こす必要があります。
オブジェクトツリー構成
オブジェクトのメモリ管理のために,オブジェクトツリーは,どのようになっているかを把握しておきましょう。前表のように,QSignalTransition オブジェクトの親オブジェクトは,sourceState() が返す QState オブジェクトになっています。QState オブジェクトの親オブジェクトは,QStateMachine オブジェクトになるので,QStateMachine オブジェクトを頂点とするオブジェクトツリーができていることになります。
シグナル遷移の設定パターン
前回の「基本的なステートマシンフレームワークの適用例」で,シグナル遷移の設定パターンをいくつか説明しました。他の設定パターンを含めてまとめ,それぞれの設定でどのようなことがされているか見てみましょう。
1: #include <QApplication>
2: #include <QPushButton>
3: #include <QStateMachine>
4: #include <QSignalTransition>
5:
6: int main( int argc, char** argv )
7: {
8: QApplication app( argc, argv );
9:
10: QPushButton button;
11:
12: QStateMachine machine;
13: QState* beforeState = new QState( &machine );
14: beforeState->assignProperty( &button, "text", "Before" );
15: QState* afterState = new QState( &machine );
16: afterState->assignProperty( &button, "text", "After" );
17:
18: beforeState->addTransition( &button, SIGNAL(clicked()), afterState );
19:
20: button.show();
21:
22: machine.setInitialState( beforeState );
23: machine.start();
24:
25: return app.exec();
26: }
図2のようにボタンをクリックすると,初期状態から別の状態に1回だけ遷移するコードです。
18: beforeState->addTransition( &button, SIGNAL(clicked()), afterState );
前回にも出て来たように,最初の設定パターンは最も簡潔です。このaddTransition()では,内部でQSignalTransition のインスタンスを生成してからQState::addTransition( QAbstractTransition* transition ) でシグナル遷移を設定しています。 QSignalTransitionのインスタンスが返されますが,ここでは戻り値を無視しています。
QState* afterState2 = new QState( &machine );
afterState2->assignProperty( &button, "text", "After 2" );
QSignalTransition* transition = beforeState->addTransition( &button, SIGNAL(clicked()), afterState );
transition = beforeState->addTransition( &button, SIGNAL(pressed()), afterState2 );
もし上記のようなコードを書いたとすると,pressed()シグナルで遷移が起き,clicked()シグナルの方では遷移は起きません。同様に,共にclicked()シグナルにすると,最初に呼出した方で遷移が起きます。つまり,遷移条件に無矛盾性を与えるのは,コードの書き手に委ねられます。
次に,上記の18行目を他の設定パターンで書き換えてみましょう。
QSignalTransition* signalTransition = new QSignalTransition( &button, SIGNAL(clicked()), beforeState );
signalTransition->setTargetState( afterState );
この設定パターンも前回示しました。このようにQSignalTransition のインスタンスを生成すると,遷移前の状態beforeState を親オブジェクトとするQSignalTransition オブジェクトができます。遷移後の状態をQSignalTransition::setTargetState()で設定すれば,元の18行目と同じ結果となります。
QSignalTransition* signalTransition = new QSignalTransition( beforeState );
signalTransition->setSenderObject( &button );
signalTransition->setSignal( SIGNAL(clicked()) );
signalTransition->setTargetState( afterState );
別の設定パターンです。遷移前の状態のみを指定してQSignalTransitionのインスタンスを生成した場合には,シグナルを発生させるオブジェクト,シグナル,遷移後の状態を設定します。QSignalTransitionのインスタンス生成で指定するbeforeState が親オブジェクトになります。
QSignalTransition* signalTransition = new QSignalTransition;
signalTransition->setSenderObject( &button );
signalTransition->setSignal( SIGNAL(clicked()) );
signalTransition->setTargetState( afterState );
beforeState->addTransition( signalTransition );
最後の設定パターンです。パラメータを指定せずにQSignalTransitionのインスタンスを生成すると,親オブジェクトは 0,つまりトップレベルのオブジェクトになります。2~4行は,前の設定パターンと同一です。最後の行では,QState::addTransition( QAbstractTransition* transition )で,遷移前の状態にシグナル遷移を設定しています。この呼出しで,QSignalTransition のインスタンスの親オブジェクトがbeforeStateオブジェクトに設定されます。このaddTransition()は状態に遷移を追加するメソッドですが,もし同一の遷移に対して繰り返し呼し出しを行っても,二重に登録されることはありません。

