アイエスエス株式会社 "Innovative System Solutions"
猫ウィンドウクラスを作る(2)
- 2009-07-26 (日)
- 技術
●クラスCCatWndの骨組み
まずは,「ウィンドウを出して,イベントを処理して,ウィンドウを閉じられる」という最低限の枠組みをコーディングします.
- ウィンドウプロシージャを用意する
- ウィンドウクラスの登録(RegisterClassEx)
- ウィンドウの生成と表示(CreateWindow,ShowWindow,UpdateWindow)
- ウィンドウの破棄(DestroyWindow)
- 登録したウィンドウクラスを捨てる処理が必要かも(UnregisterClass)
…と,順番通りにAPIが呼ばれるように.
各所でAPIにインスタンスハンドルを渡す必要がありますが,::GetModuleHandle( NULL );で取れるっぽいので,これを渡しています.
●ウィンドウクラスの登録
これをいつやるか? ということになりますが,とりあえず「最初にウィンドウを作ろうとしたとき」に行うことにしました.
(「最初に…」を判断するためにstaticメンバでフラグをひとつ用意して対処)
ウィンドウクラスの登録にはウィンドウプロシージャを指定する必要があるので,staticメンバ関数でウィンドウプロシージャを用意して,これを指定しておきます.
RegisterClassEx()が成功したら,上記5.のUnregisterClassをするための処理関数をatexit()で指定しておきます.
//CCatWndの宣言部抜粋 class CCatWnd { public: bool CreateWnd( ... );//ウィンドウ生成 bool DestroyWnd();//ウィンドウ破棄 private: HWND m_hWnd; //ウィンドウハンドルを覚えておく用 private: static bool ms_bRegisterClassFlag; //RegisterClassしたことを覚えておくフラグ //ウィンドウクラスの登録と削除,staticなウィンドウプロシージャ static bool RegisterClass(); static void UnRegisterClass(); static LRESULT CALLBACK WndProc__( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ); }
- CreateWnd()は最初にフラグms_bRegisterClassFlagを見て,必要ならRegisterClass()をコール,その後,::CreateWindow(…WS_POPUP…)でウィンドウの生成を行います.
- RegisterClass()はWndProc__をプロシージャとしたウィンドウクラスを::RegisterClass()APIで登録し,ms_bRegisterClassFlagを書き換え,最後にatexit( CCatWnd::UnRegisterClass )しておきます.
- UnRegisterClass()はms_bRegisterClassFlagを見て,必要なら単に::UnregisterClass()APIでウィンドウクラスの登録を解除するだけです.
●ウィンドウプロシージャでメンバ変数とか触りたい
ウィンドウプロシージャWndProc__()はstaticメンバ関数なので,このままではCCatWndインスタンスに手が届かない存在(?)です.
WndProc__()に渡されてくるウィンドウハンドルを頼りに,メッセージ処理を行うべきCCatWndインスタンスを判断する機構が必要です.
要するに,CCatWndに,非staticな(普通の)メンバ関数
//オブジェクト毎のプロシージャ
LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam );
があったら,WndProc__()が適切なオブジェクトのWndProc()をコールするようにできればOKです.
例えば,
static std::map< HWND, CCatWnd * > ms_HWND2ObjMap;
なんてものを作って管理してもよさそうですが,どうやらSetProp(),GetProp()というAPIを使えばその手間が省けるようなので,こちらを使うことにしました.
- ウィンドウが生成できたら,SetProp()でウィンドウハンドルにオブジェクトへのポインタを登録しておく
- WndProc__()では,GetProp()でオブジェクトへのポインタを取得し,そこからWndProc()を呼ぶ.
ということをすればよさそうです.
1.をどこでやるか,ですが,::CreateWindow()内でWM_CREATEメッセージが発生してくる関係上,CreateWnd()内でウィンドウを作るあたりで
m_hWnd = ::CreateWindow( … );
::SetProp( m_hWndとthisを渡して登録 );
というような記述をしてしまうと,オブジェクト毎のウィンドウプロシージャでWM_CREATEに対する処理を行うことができなくなってしまいました.
対策として,(ちょっとかっこわるいですが)CreateWnd()内で
ms_pNowCreatingObj = this;//CreateWindow()の直前にstaticメンバに,thisを代入
CreateWindow( … );
として,これからウィンドウを作るオブジェクトを覚えておいて,WndProc__()を以下のようにしました.
//ウィンドウプロシージャ //ウィンドウハンドルに登録されているオブジェクトのプロシージャに処理を渡す LRESULT CALLBACK CCatWnd::WndProc__(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CCatWnd* pObj = NULL; if( message == WM_CREATE ) {//WM_CREATEはCreateWindow()内で来るので,特別処理. pObj = ms_pNowCreatingObj; if( pObj ) { pObj->m_hWnd = hWnd; //※ここでウィンドウハンドルをメンバ変数に設定. ::SetProp( hWnd, "THIS", (HANDLE)( pObj ) ); //オブジェクトのポインタ登録 } } else { pObj = (CCatWnd*)( GetProp( hWnd, "THIS" ) ); } // if( pObj ) { return pObj->WndProc( message, wParam, lParam ); } else { return DefWindowProc( hWnd, message, wParam, lParam ); } }あとでms_pNowCreatingObjを排他処理で守るようにすれば安全…かな?
とにかくこれでCCatWndオブジェクトが自分で作ったウィンドウへのメッセージを処理できる枠組みができました.
あとは,コンストラクタで適切な初期化をし,WM_CREATE時あたりでレイヤードウィンドウの設定を行い,デストラクタやWM_DESTROY時の処理あたりに必要な後始末コードを追加したりしておきます.
猫ウィンドウクラスを作る(1)
- 2009-07-26 (日)
- 技術
●猫ウィンドウクラスを作る
前回までで,猫型ウィンドウを表示することができました.書き散らかしたコードによる機能をまとめると,
- ウィンドウ背景(猫以外の領域)を透過
- なんとなく徘徊
- タイマでアニメーション
といった感じです.
今回は,これらのコード(のうちのいくらか)を再利用できるように猫ウィンドウクラスを作成することにします.
さすがに業務アプリのウィンドウを黒猫にしたら怒られそうですが(←本当はやりたい),今後,猫アプリを作る際には,「猫がアニメーションする」という基本機能がクラス化されていると便利でしょう.
※「猫アプリなんてつくんねーよw」という雰囲気の職場ですが,さびしくなんてありません.
※それ以前に職場で猫ソフトを作っているという状況が(略
●猫ウィンドウクラスの機能
とりあえず機能を何でもかんでも詰め込んでしまうのも考えものなので,以下のような機能を有するものを目標とします.
- ウィンドウの生成と破棄
- ウィンドウは背景透過する(レイヤードウィンドウ)
- アニメーションの登録
- 表示するアニメーションの選択
- 選択中のアニメーションがタイマで勝手に表示される
- GUIとして右クリック時にポップアップメニューが出る
- 機能拡張用の仕組み
くらいで.
「徘徊」は猫アプリ次第で必要か否かが変わる要素でしょうから,このクラスには直接には含めないことにしました.
また,前回,マウスドラッグで猫を動かせるようにしていましたが,これも同様の理由で含めません.
(ポップアップメニューだって余計な要素に見えるかもしれませんが,「猫ウィンドウを閉じるのに最も自然な手段な気がする→自分が使うときは多分例外なく必ず使う→猫ウィンドウクラスは多分自分しか使わない」という推論から,猫ウィンドウがサポートすべき最低限の機能として採用.)
クラスの基本的な使い方としては,
- インスタンス作る
- アニメーションを必要な個数分だけ登録する
- ウィンドウ生成,表示
- 随時必要に応じて,登録しておいた中から,現在表示させるアニメーションを選択する
- ウィンドウ閉じる
といった感じになります.