Home

アイエスエス株式会社 "Innovative System Solutions"

猫ウィンドウクラスを作る(4)

●猫を右クリックでポップアップメニューが出るようにする

猫ウィンドウがデフォルトで唯一提供するGUIであるポップアップメニューを実装します.
そして,このポップアップメニューを猫ウィンドウを閉じる手段として利用することにします.

 

 

 


  

 

●メニューを作る

ポップアップメニューは::CreatePopupMenu()で作り,::DestroyMenu()で破棄します.
WM_CREATE時にメニューを作成し,WM_DESTROY時に破棄することにします.
CCatWndに,作ったメニューのハンドルを保持するためのm_hPopupMenuを追加し,コンストラクタでNULLに初期化しておきます.

01//ポップアップメニュー作成
02//※CAT_WND_MENU_ID_EXITは適当な値の定数
03bool CCatWnd::CreateDefaultPopupMenu()
04{
05 if( m_hPopupMenu != NULL )return true;
06 m_hPopupMenu = ::CreatePopupMenu();
07 if( m_hPopupMenu == NULL )return false;
08 
09 return InsertPopupMenuItem( 0, CAT_WND_MENU_ID_EXIT, std::string("Exit") );
10}
11 
12//ポップアップメニュー破棄
13bool CCatWnd::DestroyPopupMenu()
14{
15 if( m_hPopupMenu )
16 {
17  if( ::DestroyMenu( m_hPopupMenu ) )
18  {
19   m_hPopupMenu = NULL;
20   return true;
21  }
22  else
23  { return false; }
24 }
25 
26 return true;
27}
28 
29//メニューへの項目追加
30bool CCatWnd::InsertPopupMenuItem( UINT PosIndex, UINT ItemID, const std::string &rItemString )
31{
32 if( !m_hPopupMenu )return false;
33 if( rItemString.empty() )return false;
34 
35 MENUITEMINFO miinfo = { 0 };
36 miinfo.cbSize = sizeof( MENUITEMINFO );
37 miinfo.fMask = MIIM_ID | MIIM_STRING;
38 miinfo.wID = ItemID;
39 miinfo.cch = (UINT)( rItemString.length() );
40 std::vector< char > Data( rItemString.begin(), rItemString.end() ); //※constでないものを要求されるのでコピーを生成して対応
41 Data.push_back( '' );
42 miinfo.dwTypeData = &( Data.at(0) );
43 
44 bool ret = ( ::InsertMenuItem( m_hPopupMenu, PosIndex, TRUE, &miinfo ) != 0 );
45 if( ret )
46 { ::DrawMenuBar( m_hWnd ); }
47 return ret;
48}

ポップアップメニューの項目はデフォルトでは終了のための”Exit”のみですが,項目を追加するInsertPopupMenuItem()をpublicにすることで,利用側からの拡張を可能にしています.

 

 

 


  

 

●ポップアップメニューの表示と処理

WndProc()内で,猫を右クリックしたら作っておいたメニューを表示するようにします.
(万が一メニュー作成等に失敗していた場合,ウィンドウを閉じる手段が無くなってしまうので,そのための対策つき)

01case WM_RBUTTONUP:
02  {
03   //ポップアップメニュー表示
04   bool bTrackPopup = false;
05   if( m_hPopupMenu )
06   {
07    //座標取得
08    int x=0 ,y=0;
09    {
10     POINT pos;
11     pos.x = LOWORD( lParam );
12     pos.y = HIWORD( lParam );
13     ::ClientToScreen( m_hWnd, &pos );
14     x = pos.x;
15     y = pos.y;
16    }
17 
18    //ポップアップメニュー
19    bTrackPopup = ( ::TrackPopupMenu( m_hPopupMenu, TPM_LEFTALIGN|TPM_BOTTOMALIGN, x,y, 0, m_hWnd, NULL ) != 0 );
20   }
21 
22   //メニュー関連に失敗しているとき用の念のための対策
23   if( !bTrackPopup )
24   { DestroyWnd(); }
25  }
26  break;

ポップアップメニューを表示する::TrackPopupMenu()関数は,「ポップアップメニュー表示中は処理が返ってこないのに,他のイベント処理はその間も動作している」という謎の動作をしていて不安をあおります…
メニュー項目が選択されるとWM_COMMANDメッセージがくるので,そこに処理を記述します.
メニューのイベント時にはHIWORD(wParam)が0になるとのことなので,それでメニューのイベントであることを判断しています.

01case WM_COMMAND:
02  {
03   if( HIWORD(wParam)==0 ) //メニューからのメッセージか?
04   {
05    switch( LOWORD(wParam) )
06    {
07    case CAT_WND_MENU_ID_EXIT:
08     DestroyWnd();
09     break;
10 
11    default:
12     break;
13    }
14   }
15  }
16  break;

これで 右クリック→メニュー出る→”Exit”選択でウィンドウ閉じる が達成できました.
(このままではまだ,追加されたメニュー項目については処理できませんが.)

 

 

 


  

 

●アプリ終了へ向かわすには?

ウィンドウは閉じられるようになりましたが,このときにアプリケーションを終了に向かわせるかどうかは,猫ウィンドウの役どころによって変わってきます.
猫ウィンドウがメインウィンドウとして使われているなら,WM_DESTROY時にPostQuitMessage()APIを呼ぶようにします.

//メインウィンドウ指定されている場合,アプリ終了へ向かわす
   if( this->IsMainWnd() )//※IsMainWnd()はメインウィンドウ指定されているかどうかを返すメンバ関数
   { PostQuitMessage(0); }

メインウィンドウとして使うか否かは,ウィンドウ生成時に引数で設定するようにしました.

猫ウィンドウクラスを作る(5)

猫ウィンドウクラスを作る(3)

●アニメーションの登録と選択

CCatWndでは,猫表示のために

  1. 猫アニメーションパターンをいくつか登録する
  2. 現在表示すべきアニメーションパターンを選択する

という手順を踏みます.アニメーション登録の方法は

  • アニメーションフレーム画像を並べた画像(BMP)
  • アニメーションフレーム画像のサイズ
  • アニメーションフレーム数
  • 各アニメーションフレーム毎の情報(どの絵を表示するか,次のフレームまでの時間)
  • アニメーションID値(選択するときの指定に使う)

といったデータを用意して,登録用関数に渡すことにしました.
仕様として,BMPは「同一サイズのアニメーションフレーム画像を隙間なく羅列したもの」を用意することにします.
また,背景部分は白であることとします.
例えば,次の様な画像です.

CatStartB黒猫の着地アニメーション用BMP.フレーム画像サイズ48*54の画像を4枚羅列.

この画像例では,フレーム画像を縦にならべていますが,横にならべても,複数行*複数列に並べてもいいように,各フレームでどの場所のフレーム画像を表示するか,という情報については(x,y)のインデクスで指定する形にしました.

01//アニメーションパターン設定用データ
02//登録用メソッドには構造体を作って渡す形にした
03  //各フレームのデータ
04 struct SAnimFrameData
05 {
06  unsigned int m_XIndex; //X方向パタンインデクス
07  unsigned int m_YIndex; //Y方向パタンインデクス
08  unsigned int m_AnimTime_ms; //表示時間[ms]
09 
10  SAnimFrameData( unsigned int XIndex=0, unsigned int YIndex=0, unsigned int AnimTime=1 ) : m_XIndex(XIndex), m_YIndex(YIndex), m_AnimTime_ms(AnimTime) {}
11 };
12 
13  //アニメーションデータ
14 struct SAnimData
15 {
16  unsigned int m_UnitWidth;//フレーム画像サイズ
17  unsigned int m_UnitHeight;
18  std::vector< SAnimFrameData > m_Frames;
19  unsigned int m_AnimID; //アニメーションを表す固有の値
20 
21  SAnimData() : m_UnitWidth(0), m_UnitHeight(0), m_AnimID(0) {}
22 };
23 
24//アニメーションの登録関数
25//ファイル名指定タイプと,リソースID指定タイプを用意.
26bool RegisterAnimation_BMPFILE( const char *ImageName, const SAnimData &rAnimData );
27bool RegisterAnimation_RESOURCE( UINT BMP_ID, const SAnimData &rAnimData );
28 
29//アニメーションの選択
30bool SetCurAnimFrame( unsigned int AnimID, unsigned int FrameIndex );
31void GetCurAnimFrame( unsigned int *pAnimID, unsigned int *pFrameIndex ) const;

登録関数内では,指定された画像の読込みに成功したら,アニメーション表示に必要となる情報を,アニメーションIDをキーとしたmapに蓄えています.

std::map< unsigned int, 必要なデータをまとめた型 * > m_AnimMap;//キーはアニメーションID値

アニメーション選択関数では,指定されたIDがm_AnimMapに登録されていれば,そのアニメーションを,指定されたフレームから開始するように設定を行います.
また,ウィンドウのサイズをアニメーションのフレーム画像サイズに合わせます.
このとき,フレーム画像のサイズが前に選択されていたアニメーションと違うと猫の位置がずれてしまったりするのですが,このことへの対処は選択する側に任せることにしています.
(面倒なら,全アニメーションのフレームサイズを統一すればいいか,とか.)

 

 


 

●アニメーションの表示

 

これは単純にタイマで時間毎にフレームを切り替えて表示するだけです.

登録情報に次のフレームまでの時間があるので,アニメーション選択が行われた時点で::SetTimer()でタイマをその時間に合わせ,あとはタイマイベント毎にフレームを次に進めてタイマ時間をセットして::InvalidateRect()で再描画要求を出します.

WM_PAINTに対する描画処理部分で現在のフレーム画像を描画します.

 

 


 

●猫じゃないとダメですよ

 

何やら隣近所から
「これなら絵が猫じゃなくても何ら問題がない→クラス名はCAnimWndとかにすべき」
とかいうまるで理解できない声が聞こえてきたりしています.何なのでしょうか?

CCatWndは「猫ウィンドウ」クラスなのであり,猫でないものを表示するのは「クラス設計者の意図していない間違った使い方」であるので,アニメーション登録関数の注釈にはきちんと

 //アニメーションの登録
 //※ちゃんと「猫」の画像を指定すること.猫でない画像を指定した場合の動作は不定です

と明記して対策しておきます.
(不定なのだから,偶然正しく動作する,ということもあり得る)

猫ウィンドウクラスを作る(4)

Home

Search
Feeds
Authorized
奨学金支援制度
Meta

Return to page top