イベントの処理(Event)


POINT ウィンドウズアプリケーションの動作内容はイベントによって何をするかを決めることできまる。 システム上でおきたイベントをキッカケにして処理が実行される。
マウスがクリックされたら -> これをする。 マウスが右クリックされたら -> これをする。
といった流れになり、 逐次処理のように直線的な流れのプログラムにはならない。 アプリケーションごとに MessageQ があり、 GetMessage() で取りにいく。 CallBack は Windows に呼んでもらい, Windows が返り値を取得する。 SAMPLE https://www.dropbox.com/s/cpot50cixh03yce/mousemovewindow.exe( マウスで動くウィンドウ )


Window の動作内容を決める


イベントごとに処理をコールバックしてもらう。 System から Call される 自分で処理しないものは DefWindowProc() になげる


Message.をうけとる


DESC win32 API ではメッセージという概念を使用する message は キューに格納されている Application はキューから Message を取得する。 typedef struct tagMSG { HWND hwnd; // message の宛先 UINT message; // Message の種類 WPARAM wParam; // 追加パラメータ LPARAM lParam; DWORD time; // message がポストされた時間 POINT pt; // message がポストされた時のカーソル位置 } MSG , * PMSG; SYNTAX BOOL GetMessage( LPMSG lpMsg, // msg のポインタを渡してうけとる HWND hWnd , // message をうけとる window ( ある window に送られた message がたまっているのをもらう ) // NULL にすると 自分のThreadでつくった 全window 宛の取得可能 UINT wMsgFilterMax, // msg のフィルタ ( ともに 0 を指定すると Message の Filter がかからない ) UINT wMsgFilterMin ); 呼び出し側スレッドのメッセージキューからメッセージを取得し 指定された構造体にそのメッセージを格納する ポストされたメッセージが取得可能になるまで 着信した送信済みメッセージをディスパッチする。 GetMessage() とは異なり PeekMessage() は 何かメッセージがポストされるのを待たずに制御を返す 関数から制御が返ると この構造体に、呼び出し側スレッドのメッセージキューから取得したメッセージ情報がセットされる。 RET 0(FALSE) : WM_QUIT 1>0(TRUE) : WM_QUIT 以外 -1 : ERROR POINT message をうけとるためには, 常時 message をうけとる必要あり( loop を作成 ) DispatchMessage( &msg ); は自分以外( APP ) にも message を割り振るために使用する GetMessage() で message を取得してしまうので, 他人あての message かもしれないので再度割り振る作業をするだけ さらにいえば, これをしないと wndProc に送られない DispatchMessage() は送られた msg( 伝言 )を処理する対象に送るための処理 message は window 宛でなく 直接 window に送られるケースもあり Windows は ハードウェアイベントが発生するとシステムが管理するメッセージキューにメッセージを追加する アプリケーションの要求に従ってキューの先頭にあるメッセージをわたす GetMessage() でキューから次のメッセージを取得する Windows アプリケーションは繰り返し文で GetMessage() を呼び出し メッセージの受信を監視する( メッセージループという ) うけ取るべきメッセージがキューに存在しないとき GetMessage() は制御を返さずにメッセージの受信をまつ そのため繰り返し処理が無意味に連続して CPU の負荷を高めることはない
int cnt = 0; while ( cnt ++ <= ( 100 * 10 ) ) { // Message を取得 MSG msg; GetMessage( &msg, hWnd, 0, 0 ); // 自分宛 しかきていないような // 自分の子供のことかも printf( "hwnd(self) %d hwnd %d msg %d \n" , hWnd, msg.hwnd, msg.message ); printf( "msg %d \n" , msg.message ); // 適当な時間ねる Sleep( 5 ); }



message を割り振る( dispatch )


DESC WindowProc で message を処理する LRESULT CALLBACK WndProc(HWND hWnd , UINT Msg , WPARAM wParam , LPARAM lParam); HWND : message が発生した window ハンドル POINT message proc の最後には, DefWindowProc() を呼び出すことで, default の window のふるまいをする 描画処理したり message を処理するという object の基底クラスにあたる


WindowProcedureを定義する


POINT 処理をしたら 0 をかえすこと
// マウスクリックされたら、メッセージボックスを表示する。 LRESULT ndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { swtich(msg) { WM_LBUTTONDOWN: { WORD y = HIWORD( lp ); WORD x = LOWORD( lp ); char s[256]; sprintf( s, "x = %d y = %d", x, y ); MessageBox( NULL, s, s, MB_OK ); break; } default: // 興味のないイベントはデフォルトの処理におまかせする。 return DefWindowProc( ); } // 自分で処理をしたら 0 return 0; }



PeekMessage


SYNTAX BOOL PeekMessage( LPMSG msg , HWND hWnd , UINT wMsgFilterMin , UINT wMsgFilterMax , UINT wRemoveMsg // Message を削除するかどうか ( GetMessage() == PM_REMOVE ) ); POINT メッセージがポストにあるか覗くために使用すると思えばよい。 GetMessage() を構造がにてる RET 0 : メッセージがなかった !0 : メッセージがあった
// PM_REMOVE を指定しないときは, // Message があるかどうかチェックする時に利用する // 通常は POST をのぞいて あれば取得する
DESC Window Procedure 内に 無限 LOOP を作成することは禁止 一定時間内に処理を返して, 制御をメッセージループに返す必要あり デッドタイムとは window くんが, user からの入力をまつ無駄な時間 公式DOC の要約 hWnd で Message のあて先を指定する( 子供も含まれる ) Message の種類を絞り込みたいときは, wMsgFilterMax, Min で範囲を指定する hWnd は , Thread( Application ) で作成したものに限るはず もし 2 個以上 Window をつくっていれば, 効果があるということ NULL を指定すると, PeekMessage() を呼び出した現在のスレッドに所属する任意のウィンドウ宛のメッセージを取得する WARNING wMsgFilterMin と wMsgFilterMax の各パラメータでどのような値を指定したときでも PeekMessage は必ず WM_QUIT メッセージを取得する POINT Message を絞り込みたいときに便利
// マウス関連のメッセージのみを取得する if ( PeekMessage( &msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE ) ){ } // キーボード入力に関係するメッセージだけ if ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ){ }



マウスイベント(MouseEvent)





Click.(WM_LBUTTONDOWN.WM_RBUTTONDOWN)


DESC 図形の描画の際に使用 矩形選択の使用にはよいサンプル Client 領域内でクリックしたときに発生する DESC mouse 関連情報を取得する関数あり -> GetSystemMetrics retval 0 wp mouse button + ctrl shift lp ClientArea 左上原点からの位置 MOVE も含めてすべてのマウスイベントで共通。 POINT Client 領域のみの Mouse のみ有効. MSG // 押された WM_LBUTTONDOWN WM_MBUTTONDOWN WM_RBUTTONDOWN // 離された WM_LBUTTONUP WM_MBUTTONUP WM_RBUTTONUP // 動いた / マウスがクライアント領域を移動すると発生。 WM_MOUSEMOVE lp // Mouse 位置 ( Client 領域の座標系 ) y = HIWORD( lp ); x = LOWORD( lp ); POINT DD している状態で,クライアントエリア の外で button を離すとmsgはそのwindowに送信されない ( マウスが離されことが通知されない ) -> ならば, window にマウス情報の制御権を与えてあげれるということで MouseCapture -> call する Timing は WM_LBUTTONDOWN | UP を利用する.


非クライアントエリアのマウスメッセージ


DESC title bar などでの mouse click を取得する時に利用 DefWindowProc() で処理される MSG WM_NCLBUTTONDOWN WM_NCMOUSEMOVE wp HTBORDER : 位置ではなく どの component かを表す 列挙定数 lp POINT Screen 全体からの位置
// TitleBar がない Window には WM_LBUTTONDOWN がきたら 自分自身に WM_NCLBUTTONDOWN を POST すること DragDrop できるようになる if ( msg == WM_LBUTTONDOWN ) { PostMessage( hWnd, WM_NCLBUTTONDOWN, (WPARAM)HTCAPTION, lp ); return 0; }



マウス移動


MESSAGE WM_MOUSEMOVE POINT mouse move は Windows が処理した回数に応じて message が届く WM_MOUSEMOVE は window が非アクティブでも message が届く Mouse が [ ClientArea ] で動いた場合にくる. SetCapture() した場合は CrientArea の外からも届くよ. x = LOWORD( lp ) y = HIWORD( lp )


WindowEvent





サイズ変更


DESC ユーザーによってウィンドウのサイズが変更された時もメッセージが発生します GetClientRect() を描画毎に設定するのは都合が悪い WM_SIZE: ret 0 wp サイズ変更タイプ( ) lp x, y POINT CS_HREDRAW | CS_VREDRAW を設定することで, 自動で再描画される


移動


DESC ユーザーによってウィンドウのサイズが変更された時もメッセージが発生します GetClientRect() を描画毎に設定するのは都合が悪い msg WM_MOVE: ret 0 wp サイズ変更タイプ( ) lp x, y POINT lp は screen 座標系での値 親 window がある場合は , client 領域


キーボードイベント(KeyboardEvent)


WM_KEYDOWN SystemKey == [Alt-F10] DESC Windows のイベントの基本 他のアプリとの関係も把握可能 キーボードイベントは押された瞬間に発生する すぐにメッセージキューにははいらずシステムメッセージキューにはいる SystemMsgQ ---> AppMsgQ キーボードイベントは Window とその子供コントロールに対して有効。 キーボードからの入力をうける権利を kb forcus という。 keyborad を押すと入力フォーカスをもつ Application に WM_KEYDOWN, WM_KEYUP が転送される MESSAGE WM_KEYUP // Key が押された WM_KEYDOWN // SYS 以外の Key WM_SYSKEYUP WM_SYSKEYDOWN // F1 - F10 // ここが抽象化されている ( 任意のパラメータ ) wp 仮想キーコード( VirtualKey ) lp キーボードが押されると WM_KEYDOWN が発行される
case: WM_KEYDOWN: { char str[256]; sprintf(str, "kb val = %xh", wp ); MessageBox( NULL, str, str, MB_OK ); break; }



TranslateMessage


SYNTAX BOOL TranslateMessage( const MSG *lpMsg ); DESC 仮想キーコードを実際の文字に変換する。 仮想キーメッセージを文字メッセージへ変換する 文字メッセージは 呼び出し側スレッドのメッセージキューにポストされ 次にそのスレッドが GetMessage(), PeekMessage() を呼び出すと 文字メッセージを取得する。 RET !0 : messageQ に 文字メッセージがポストされたとき 0 : されなかったとき この関数をコールすることで WM_CHAR がよばれる POINT キーコードではなく文字の入力をとるにはこの関数を メッセージループで呼ぶ必要がある。
WM_KEYDOWN -> WM_CHAR ( wparam に文字コード )
while ( GetMessage ) { TranslateMessage( &msg ); }
メッセージハンドラに WM_CHAR が届くようになるので 押された文字を wparam から取得して好きな処理をする。
case WM_CHAR: { char buf[256]; sprintf( buf, "%c", wp ); MessageBox( NULL, buf, buf, MB_OK ); return 0; }
WM_KEYDOWN -> WM_CHAR ( wparam に文字コード ) -> WM_CHAR は WM_KEYDOWN の後に生成される WARNING この処理をしないと EditControl には文字を入力できない。 TIP ALT key を押しながらの system key を処理する時は WM_SYSKEY を利用すること TIP key コードは 仮想キーコード値を利用することで Device に依存しない ( CPU は IO から値をとることを忘れずに ) KeyBoard には ScanCode という値が割りあてられているが この値を Program でとると HW 依存になるため VK を利用することで抽象化する lparam は 6 つの field data が格納される 0 - 15 : リピート情報 ( Program の処理速度以上で key が押されている場合 1 より大きい値になる ) -> 処理が間に合わない場合は, windows が入力をまとめてしまい, repeat をセット [ 1 1 1 1 1 1 ] -> [ 1 ] 一回にすればいいやという発想 24 : Alt, Ctl の値がくる 29 : Alt 30 : 直前のキーの値を取得可能 -> trg に使用できる 31 : 押した 1, 離した 0 Alt key は WM_KEYUP | KEYDOWN を発行しない( windows 自体が alt + tab などに使用するため ) 代わりに WM_SYSKEYDOWN | WM_SYSKEYUP を利用すること POINT // cast を使う wsprintf( str , "%d" , (short)lp); // 下位 16 bit でrepeat を使う


WM_CHAR





TimerEvent


SYNTAX UINT SetTimer( HWND hWnd , // 送信先ウィンドウ UINT id, // タイマー ID( 0 でない任意の数値 ) && hWnd が異なれば ID が同じでも OK UINT elapse , // 間隔 ( msec ) TIMERPROC timerFunc // callback() ); BOOL KillTimer( HWND hWnd, UINT id ); SAMPLE https://www.dropbox.com/s/kukm14rnh5zofxw/timer.exe( タイマー ) DESC アニメーションを実現するには 毎ループの間隔を均等にする必要がある。 ( -> CPU 依存になるのはだめ ) Timer は非同期処理ではなく, Mouse, Keyboard のようにメッセージ扱いであり、 割り込みではない MSG WM_TIMER wp Timer ID RET 0 タイマーのセットは初期設定のタイミングでしておく。
WM_CREATE: { // 2000 msec 間隔のタイマーを id 100 という名前でセット SetTimer( hWnd, 100, 2000, 0 ); break; }
時間になると WM_TIMER メッセージが届くため、ここで適当な処理をする。
case WM_TIMER: { MessageBox( NULL, "タイマーテスト", "タイマーテスト", MB_OK ); break; }
static int x = 0; static int t = 0; case WM_TIMER: { // 自分の Timer かどうかチェック if ( wp == IDT_TEST ) { // Animation させるには 背景をクリアする // 動かす絵などを描画 x = 100 * sin( t++ / 60.0f ) + 100; // 背景色でクリア HDC hDC = GetDC( hWnd ); RECT rc; GetClientRect( hWnd, &rc ); Rectangle( hDC, 0, 0, rc.right, rc.bottom ); BitBlt( hDC, x, 100, 64, 64, hDCBmp, 0, 0, SRCCOPY ); break; } }