アイエスエス株式会社 "Innovative System Solutions"
猫ウィンドウクラスを作る(4)
- 2009-07-26 (日)
- 技術
●猫を右クリックでポップアップメニューが出るようにする
猫ウィンドウがデフォルトで唯一提供するGUIであるポップアップメニューを実装します.
そして,このポップアップメニューを猫ウィンドウを閉じる手段として利用することにします.
●メニューを作る
ポップアップメニューは::CreatePopupMenu()で作り,::DestroyMenu()で破棄します.
WM_CREATE時にメニューを作成し,WM_DESTROY時に破棄することにします.
CCatWndに,作ったメニューのハンドルを保持するためのm_hPopupMenuを追加し,コンストラクタでNULLに初期化しておきます.
01
//ポップアップメニュー作成
02
//※CAT_WND_MENU_ID_EXITは適当な値の定数
03
bool
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
//ポップアップメニュー破棄
13
bool
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
//メニューへの項目追加
30
bool
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()内で,猫を右クリックしたら作っておいたメニューを表示するようにします.
(万が一メニュー作成等に失敗していた場合,ウィンドウを閉じる手段が無くなってしまうので,そのための対策つき)
01
case
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になるとのことなので,それでメニューのイベントであることを判断しています.
01
case
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); }メインウィンドウとして使うか否かは,ウィンドウ生成時に引数で設定するようにしました.
猫ウィンドウクラスを作る(3)
- 2009-07-26 (日)
- 技術
●アニメーションの登録と選択
CCatWndでは,猫表示のために
- 猫アニメーションパターンをいくつか登録する
- 現在表示すべきアニメーションパターンを選択する
という手順を踏みます.アニメーション登録の方法は
- アニメーションフレーム画像を並べた画像(BMP)
- アニメーションフレーム画像のサイズ
- アニメーションフレーム数
- 各アニメーションフレーム毎の情報(どの絵を表示するか,次のフレームまでの時間)
- アニメーションID値(選択するときの指定に使う)
といったデータを用意して,登録用関数に渡すことにしました.
仕様として,BMPは「同一サイズのアニメーションフレーム画像を隙間なく羅列したもの」を用意することにします.
また,背景部分は白であることとします.
例えば,次の様な画像です.
黒猫の着地アニメーション用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指定タイプを用意.
26
bool
RegisterAnimation_BMPFILE(
const
char
*ImageName,
const
SAnimData &rAnimData );
27
bool
RegisterAnimation_RESOURCE(
UINT
BMP_ID,
const
SAnimData &rAnimData );
28
29
//アニメーションの選択
30
bool
SetCurAnimFrame( unsigned
int
AnimID, unsigned
int
FrameIndex );
31
void
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は「猫ウィンドウ」クラスなのであり,猫でないものを表示するのは「クラス設計者の意図していない間違った使い方」であるので,アニメーション登録関数の注釈にはきちんと
//アニメーションの登録
//※ちゃんと「猫」の画像を指定すること.猫でない画像を指定した場合の動作は不定ですと明記して対策しておきます.
(不定なのだから,偶然正しく動作する,ということもあり得る)