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

第4回 ステートマシーンフレームワーク

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

リスト1 書き換えサンプル

 1: #include <QApplication>
 2: #include <QGraphicsView>
 3: #include <QGraphicsScene>
 4: #include <QGraphicsPixmapItem>
 5: #include <QPropertyAnimation>
 6: #include <QSequentialAnimationGroup>
 7: #include <QParallelAnimationGroup>
 8: #include <QLayout>
 9: #include <QPushButton>
10: #include <QState>
11: #include <QStateMachine>
12: #include <QSignalTransition>

10~12行で,ステートマシンフレームワークのシグナルトランジションを使用するために必要なヘッダファイルをインクルードしています。

52: Harness::Harness()
53:     : QWidget( 0 ), d_ptr( new HarnessPrivate )
54: {
55:     Q_D( Harness );
56: 
57:     QGraphicsScene* graphicsScene = new QGraphicsScene( 0, 0, d->movingAreaSize.width(), d->movingAreaSize.height() );
58:     MovableGraphicsPixmapItem* yellowBallItem = createBallItem( ":/images/YellowGlassBall.png" );
59:     QSize ballSize = yellowBallItem->pixmap().size();
60:     int ballSpacingY = ( d->movingAreaSize.height() - ballSize.height() * 3 ) / 4;
61:     int yellowBallY = ballSpacingY;
62:     yellowBallItem->setPos( d->horizontalOffset, yellowBallY );
63:     graphicsScene->addItem( yellowBallItem );
64: 
65:     MovableGraphicsPixmapItem* greenBallItem = createBallItem( ":/images/GreenGlassBall.png" );
66:     int greenBallY = yellowBallY + ballSize.height() + ballSpacingY;
67:     greenBallItem->setPos( d->horizontalOffset, greenBallY );
68:     graphicsScene->addItem( greenBallItem );
69: 
70:     MovableGraphicsPixmapItem* redBallItem = createBallItem( ":/images/RedGlassBall.png" );
71:     int redBallY = greenBallY + ballSize.height() + ballSpacingY;
72:     redBallItem->setPos( d->horizontalOffset, redBallY );
73:     graphicsScene->addItem( redBallItem );
74: 
75:     QGraphicsView* graphicsView = new QGraphicsView();
76:     graphicsView->setScene( graphicsScene );
77: 
78:     QPushButton* animateButton = new QPushButton( "Start" );

初期状態に入ると,プロパティによってボタンのテキストがセットされるので,new QPushButton()としても良いのですが,レイアウト時にサイズが決められるようにすることでフリッカーを避けられるので,あらかじめセットするようにします。

 80:     QHBoxLayout* buttonLayout = new QHBoxLayout;
 81:     buttonLayout->addStretch();
 82:     buttonLayout->addWidget( animateButton );
 83: 
 84:     QVBoxLayout* topLayout = new QVBoxLayout;
 85:     topLayout->addWidget( graphicsView );
 86:     topLayout->addLayout( buttonLayout );
 87: 
 88:     setLayout( topLayout );
 89:     setFixedSize( sizeHint() );
 90: 
 91:     int movableBallStartX = d->horizontalOffset;
 92:     int movableBallEndX = d->movingAreaSize.width() - ballSize.width() - d->horizontalOffset;
 93: 
 94:     QPropertyAnimation* yellowBallAnimation = createBallAnimation( yellowBallItem, 20 * 1000, movableBallStartX, movableBallEndX, yellowBallY );
 95:     QPropertyAnimation* greenBallAnimation = createBallAnimation( greenBallItem, 10 * 1000, movableBallStartX, movableBallEndX, greenBallY );
 96:     QPropertyAnimation* redBallAnimation = createBallAnimation( redBallItem, 10 * 1000, movableBallStartX, movableBallEndX, redBallY );
 97: 
 98:     d->ballAnimationGroup = new QParallelAnimationGroup( this );
 99:     d->ballAnimationGroup->addAnimation( yellowBallAnimation );
100:     QSequentialAnimationGroup* sequentialAnimation = new QSequentialAnimationGroup;
101:     sequentialAnimation->addAnimation( greenBallAnimation );
102:     sequentialAnimation->addAnimation( redBallAnimation );
103:     d->ballAnimationGroup->addAnimation( sequentialAnimation );
104: 
105:     QStateMachine* stateMachine = new QStateMachine( this );

状態遷移機械のインスタンスで,以降でこれに対して状態や遷移を定義します。

107:     QState* startState = new QState( stateMachine );
108:     startState->assignProperty( yellowBallItem, "pos", yellowBallAnimation->startValue() );
109:     startState->assignProperty( greenBallItem, "pos", greenBallAnimation->startValue() );
110:     startState->assignProperty( redBallItem, "pos", redBallAnimation->startValue() );
111:     startState->assignProperty( animateButton, "text", "Start" );
112:     stateMachine->setInitialState( startState );

アニメーションの開始待ちを表すstartState状態のインスタンスです。メソッドassignProperty()でこの状態になったときに,オブジェクト(QObjectのサブクラスのオブジェクト)のposプロパティに設定する値を指定しています。設定値は,ボールが左端にあるときの位置です。setInitialState()で,状態遷移機械stateMachine の初期状態の設定をします。初期状態は必ず指定する必要があり,忘れた場合には実行時に警告メッセージがコンソールに表示されます。

114:     QState* endState = new QState( stateMachine );
115:     endState->assignProperty( yellowBallItem, "pos", yellowBallAnimation->endValue() );
116:     endState->assignProperty( greenBallItem, "pos", greenBallAnimation->endValue() );
117:     endState->assignProperty( redBallItem, "pos", redBallAnimation->endValue() );
118:     endState->assignProperty( animateButton, "text", "Reset" );

アニメーションの完了状態を表すendState状態のインスタンスで,ボールが右端にあるときの位置を指定しています。このように,どの状態のときにどのようなプロパティを宣言的に規定することで,どのようになっていて欲しいかを明示的に表記できるのがステートマシンフレームワークの利点のひとつです。

120:     QSignalTransition* movingBallTransition = startState->addTransition( animateButton, SIGNAL( clicked() ), endState );
121:     movingBallTransition->addAnimation( d->ballAnimationGroup );

startState状態にあるときに,ボタンがシグナルclicked()を送信するとendState状態に遷移するシグナルトランジションのインスタンスです。addAnimation()でアニメーションをセットするとボタンのクリックで,アニメーションが始まります。クリックの瞬間に指定した状態に移るのであって,アニメーションの完了によって状態が移るのではないことに注意が必要です。

122:     QSignalTransition* resetTransition = endState->addTransition( animateButton, SIGNAL( clicked() ), startState );
123:     Q_UNUSED( resetTransition );

逆に,endState状態にあるときにボタンがシグナルclicked()を送信すると,startState状態に戻るシグナルトランジションのインスタンスです。endState状態は,見かけ上はアニメーションの動作中か,3つのボールがすべて右端にあるときの状態です。

125:     stateMachine->start();
126: }

状態遷移機械によって動作が規定されてるので,ボタンのクリックで呼出されるHarness::startAnimation()スロットは不要です。スロットでボールを左端に戻す必要もなく,アニメーションを開始させるような実行コードは明示的には書きません。こういったことは,状態遷移機械stateMachineで宣言的に表明しているからです。

おわりに

Qtのステートマシンフレームワークの概要とその実際の適用例について説明しました。簡単な例ですが,状態遷移機械で動作を規定すると検証し易いコードにもなることがわかります。次回は,各クラスの詳細や独自のシグナルトランジションやイベントトランジションの作成方法について説明する予定です。

著者プロフィール

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

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

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