4.6がやってきた-Qt最新事情2010

第6回 ステートマシーンフレームワークの詳細 [その2]

この記事を読むのに必要な時間:およそ 5 分

無条件遷移

シグナル遷移の設定パターンではありませんが,18行目を以下のように書き換えると無条件で遷移が起きます。

beforeState->addTransition( afterState );

初期状態にしているbeforeStateの状態になると,すぐにafterState状態になります。無条件遷移は経過的な状態が必要な場合に用いられます。

無条件遷移でも内部的に以下のクラスの遷移オブジェクトが生成されます。

class UnconditionalTransition : public QAbstractTransition
{
public:
    UnconditionalTransition(QAbstractState *target)
        : QAbstractTransition()
    { setTargetState(target); }
protected:
    void onTransition(QEvent *) {}
    bool eventTest(QEvent *) { return true; }
};

そして,QState::addTransition( QAbstractTransition* transition )が呼び出されています。ステートマシンフレームワークでは,内部的に遷移を励起させるためのイベントを発生させています。上記の無条件遷移クラスは,eventTest() で常にtrueを返すようにしているので,そのイベントが来ると遷移が起こります。

ここ迄のいろいろな設定パターンからわかるように,どのような場合でも遷移オブジェクトが使われます。

シグナル遷移の独自拡張

無条件遷移でUnconditionalTransitionクラスと同じようにして,独自の判定を行うシグナル遷移を作成する方法を説明します。

QPushButtonがチェックされ,valueプロパティが100以上の整数値の場合に遷移するようなシグナル遷移を作成してみます。動作は図3のようになります。

図3 独自判定をするシグナル遷移の例

図3 独自判定をするシグナル遷移の例

 1: #include <QApplication>
 2: #include <QPushButton>
 3: #include <QStateMachine>
 4: #include <QSignalTransition>
 5: 
 6: class ClickedTransition : public QSignalTransition
 7: { 

独自のシグナル遷移を作成するには,まず,QSignalTransitionを継承します。

 8: public:
 9:     explicit ClickedTransition( QState* sourceState = 0 )
10:         : QSignalTransition( 0, SIGNAL(clicked()), sourceState ) {}
11: 
12:     explicit ClickedTransition( QPushButton* button, QState* sourceState = 0 )
13:         : QSignalTransition( button, SIGNAL(clicked()), sourceState ) {}
14: 

clicked(bool) シグナルに限定して,継承元のQSignalTransitionを初期化するコンストラクタを用意します。

15: protected:
16:     bool eventTest( QEvent* event ) {
17:         if ( !QSignalTransition::eventTest( event ) )
18:             return false;
19:         QStateMachine::SignalEvent *signalEvent = static_cast( event );
20:         return signalEvent->arguments().at(0).toBool() 
21:             && signalEvent->sender()->property( "value" ).toInt() > 99;
22:     }

メソッドeventTest()には,シグナル情報をラップしたQStateMachine::SignalEventが渡ってきます。シグナル情報は表2のようになっています。

表2 シグナル遷移におけるシグナル情報

シグナル情報説明
QList<QVariant> arguments() constシグナルの引数。arguments().at(0)で最初の引数,at(1)で2番目の引数が取り出せる。
QObject* sender () constシグナルを送信したオブジェクト。
int signalIndex () constシグナルのインデックス。

シグナルclicked(bool)を用いているので,次のようにするとシグナルの1番目のbool型の引数値を取出せます。

signalEvent->arguments().at(0).toBool()

さらに詳しくシグナルの送信元オブジェクトを参照するには,以下のようにします。

QPushButton* pushButton = qobject_cast<QPushButton*>( signalEvent->sender() );

さらにメタオブジェクト情報を辿れば,どのようなシグナルが渡されて来たかもわかります。

QMetaMethod metaMethod = pushButton->metaObject()->method( signalEvent->signalIndex() );

たとえばmetaMethod.signature()で,シグナルのシグニチャが"clicked(bool)"とわかります。

23: };
24: 
25: int main( int argc, char** argv )
26: {
27:     QApplication app( argc, argv );
28: 
29:     QPushButton button;
30:     button.setCheckable( true );
31:     button.setChecked( true );

QPushButtonのチェックを有効にして,チェック状態にしておきます。

32:     button.setProperty( "value", 100 );

ダイナミックプロパティvalueに整数値100を設定して,シグナル遷移後にボタンのテキストが変わるようにします。この値を100未満にすると,2度目のクリックでボタンのテキストが変わらなくなります。

33: 
34:     QStateMachine machine;
35:     QState* beforeState = new QState( &machine );
36:     beforeState->assignProperty( &button, "text", "Before" );
37:     QState* afterState = new QState( &machine );
38:     afterState->assignProperty( &button, "text", "After" );
39: 
40:     ClickedTransition* clickedTransition = new ClickedTransition( &button, beforeState );
41:     clickedTransition->setTargetState( afterState );
42:     

状態を2つ用意して,独自のシグナル遷移ClickedTransitionを使っています。

43:     button.show();
44: 
45:     machine.setInitialState( beforeState );
46:     machine.start();
47: 
48:     return app.exec();
49: }

再実装したQSignalTransition::eventTest()でいろいろな判定をして,遷移条件を独自に作ることができます。しかし,できるだけ独自のものは使わないようにした方が見通しがよくなることに注意してください。

おわりに

次回以降では,マウスとキーイベント遷移,イベント遷移の独自拡張,履歴状態,並列状態,遷移とアニメーション効果について説明して行く予定です。

著者プロフィール

杉田研治(すぎたけんじ)

1955年生まれ。東京都出身。株式会社SRAに勤務。プログラマ。

仕事のほとんどをMac OS XとKubuntu KDE 4でQtと供に過ごす。