アイエスエス株式会社 "Innovative System Solutions"
徘徊アプリの動作骨組(1)
●黒猫徘徊アプリの実装
前回,CCatWndクラスの大枠部分の実装ができました.
開発目標である「黒猫徘徊」部分の実装を,CCatWndManagerの派生クラスを作って実装していきます.
●猫の状態で場合分け
CCatWndには複数のアニメーションパターンを登録できます.
当然,現在のアニメーション選択状況によって,やりたいことが変わってきます.
例えば,
- アプリ起動時には特定のアニメーションで猫が出現.
- 徘徊時はウィンドウ位置を都度動かす必要がある.
- 徘徊中止状態も欲しい…
等です.そのような処理分岐の記述が,圧倒的な長さを誇るif elseif… またはswitch case case… みたいなことになっていると,コードを見た瞬間に個人的に萎えるので,
「アニメーションパターン毎に状態クラスを用意」しておいて,これを切り替えることで猫の状態を遷移させることにします.
世間では「Stateパターン」とか呼ぶかもしれません.
猫状態の基本クラスを用意し,各状態クラスを派生させて作ります.基本クラスは↓のようなものです.
//DesktopMascotState class CDMState { public: virtual ~CDMState(){} public: virtual bool OnCreate( CCatWnd *pCatWnd ) = 0; virtual void OnSelect( CCatWnd *pCatWnd, class CDMManager *pManager ) = 0; virtual bool OnPreChangeAnimFrame( CCatWnd *pCatWnd, class CDMManager *pManager, unsigned int AnimID, unsigned int FrameIndex ){ return true; } virtual bool OnPopupMenu( CCatWnd *pCatWnd, class CDMManager *pManager, UINT ItemID ){ return false; } virtual bool OnOtherEvent( CCatWnd *pCatWnd, class CDMManager *pManager, UINT message, WPARAM wParam, LPARAM lParam ){ return false; } };徘徊アプリで状態毎に異なる処理が必要そうなタイミングに呼ぶための処理関数群が宣言されています.宣言順に上から
- 初期処理用(アニメーションの登録等)
- 状態選択されたとき用(アニメーションの切換え等)
- フレーム切り替わり時用(アニメーションが1周したら他の状態に移行とか)
- ポップアップメニューのイベントに対する処理用
- その他のメッセージ処理用
といった感じです.
(コードを書き忘れるとアニメーション切換えが起らなくなってしまう類のメソッドを純粋仮想関数にしてあります.)
●CCatWndManager派生クラスの実装
CCatWndManagerから派生させたクラスには,以下のように…
class CDMManager : public CCatWndManager { public: bool ChangeState( CCatWnd *pCatWnd, unsigned int indx );//m_CurStateIndexを書き換える private: unsigned int m_CurStateIndex;//現在の状態を選択するための値 std::vector< CDMState * > m_StatePool;//状態群.インスタンス生成時あたりで状態群をつくって保持しておく. bool IsValidStateIndex( unsigned int indx ) const { return ( indx < m_StatePool.size() ); } CDMState *GetCurState(){ return m_StatePool.at( m_CurStateIndex ); } };状態クラス群を保持していて,必要なタイミングでChangeState()で切り替えて使用します.
あとは適切なタイミングで
GetCurState()->XXX();
として今の状態クラスに処理を任せるようにしておけばOK.
例えば,フレーム画像変更時の処理は,//フレーム変更直前時 bool CDMManager::OnPreChangeAnimFrame( class CCatWnd *pCatWnd, unsigned int AnimID, unsigned int FrameIndex ) { if( IsValidStateIndex(m_CurStateIndex) ) { return GetCurState()->OnPreChangeAnimFrame( pCatWnd, this, AnimID, FrameIndex ); } else { return true; } }として,現在の状態クラスに処理を投げるだけです.
猫ウィンドウクラスを作る(5)
- 2009-07-26 (日)
- 技術
●利用側で機能拡張できるようにする
CCatWndを使う側では,追加したメニュー項目選択時の処理や,猫ウィンドウが特に処理を行っていないメッセージに対して処理を追加したいはずなので,それができるようにしておきます.
●追加処理オブジェクトを指定させる
機能拡張のために「CCatWndを継承させる」というのでもいいのですが,今回はなんとなく
「CCatWndにコールバック関数を設定しておいて,WndProc()の中でこの関数をコールしてやる」という類の方針を取りました.
コールバック関数の型は
bool AdditionalWndProc( class CCatWnd *pCatWnd, UINT message, WPARAM wParam, LPARAM lParam );
みたいな.
実際には以下のように,関数ではなく,そういう関数をメンバにもつクラスのオブジェクトを指定する形になっていますが,意味は一緒です.
//猫ウィンドウとともに使う外部処理IF //アプリはこれを継承したクラスを作り,猫ウィンドウにセットして使う. class CCatWndManager { public: virtual ~CCatWndManager(){} public: //追加イベント処理関数. //猫ウィンドウは,内部で必要なメッセージに対応する処理を行ったあとで, //ウィンドウプロシージャ内(の最後らへん)でこの関数をコールする. //(ただし,こないメッセージもある. // WM_RBUTTONUPはポップアップに使われるので来ない. // あと,猫ウィンドウのアニメーション用タイマのイベントとかも) // //メッセージを処理したらtrueを,処理しない場合はfalseを返すこと. //(::DefWindowProc()をコールする類の処理は猫ウィンドウが行う) virtual bool AdditionalWndProc( class CCatWnd *pCatWnd, UINT message, WPARAM wParam, LPARAM lParam ){ return false; } };CCatWnd側には設定用の関数を用意します.
void SetCatManager( CCatWndManager *pCatManager );
CCatWnd側は,設定がなされていれば,WndProc()内から設定されたオブジェクトのAdditionalWndProc()を呼びます.
CCatWnd側で既定の処理があるメッセージの際にも呼ぶか?ということについては考え方次第かもしれませんが,とりあえず今は↑の注釈にあるように,
「呼ばれないメッセージもある」という,「じゃあ何のメッセージのときは呼ばれるのか」が把握できないなんとも嫌な感じに仕上げてみました.
しかしこれだと,アニメーション選択に最もふさわしいであろうタイミングである「アニメーションフレームを切り替える時」に呼ばれなくなってしまうので,CCatWndManagerに//アニメーションのフレーム切換え前にコールされる. //引数は今の(切換え前の)アニメーションのAnimIDとFrameIndex. // //trueを返すとデフォルト動作(次のフレームに進める)処理が行われる. //デフォルト動作を行わせないときはfalseを返すこと. virtual bool OnPreChangeAnimFrame( class CCatWnd *pCatWnd, unsigned int AnimID, unsigned int FrameIndex );とかいう特別な関数が追加されたりとかグダグダな感じになっています……なんだかなぁ.
●CCatWndに必要なメソッドを追加する
これでCCatWndManager(を継承したクラス)からCCatWndを操作できるので,アプリケーション独自の処理を追加できます.
ただし,今のままでは「CCatWndに対してできること」が少なすぎる(メニュー項目の追加とアニメーションの選択くらいしかない)ので満足に操作できません.CCatWndに今後publicなメソッドを増やしていく必要がありそうです.
CCatWndにウィンドウハンドルを返す関数を持たせれば,かなり自由に操作できるようになりますが,あまりにも自由すぎて「SetWindowLongでウィンドウプロシージャ置き換えたよ!」とか言われても困るので,現時点ではウィンドウハンドルは外部に教えない方針です.
とりあえず現在の最終目標は「黒猫徘徊ソフト」なので,徘徊に必要そうな機能(ウィンドウ位置を返す関数やウィンドウを移動する関数等)から順次追加されている最中です.
づづく...