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