2012年6月19日火曜日

WndProc(ウインドウプロシージャ)

最後の関数WndProcはウインドウプロシージャと呼ばれるものです。いわゆるイベントドリブンを記述するための部分です。
Windowsのシステムは作られたウインドウに対し、事ある毎にメッセージを発行します。例えばウインドウを動かしたり最小化したり最大化したりマウスをクリックしたりキーボードを押した時などに、それを知らせるメッセージがウインドウプロシージャに届けられます。その中にはシステムによって自動的に届けられるメッセージもあれば、wWinMain()関数内で書いたPeekMessage()やGetMessage()でプログラマが自らメッセージを捕まえてDispatchMessage( &msg );によってウインドウプロシージャへ送っているものもあります。
いずれにしても これにより、ウインドウプロシージャの中でプログラマは作ったウインドウに対して何が起きたらどういう動作をさせるのかという事を記述しておく事が出来ます。

メッセージの種類は多岐に渡りますがそれを全部処理する必要はありません。各ウインドウが必要に応じて処理を記述すれば良い訳です。処理を記述しなかったメッセージは DefWindowProc()関数によってシステム内部でデフォルトの処理がなされます。
またプログラマが任意でメッセージを発行する様なコードを書く事もできます。これにより一つのウインドウが別のウインドウと通信を取る事が出来ます。

ウインドウプロシージャは複数のウインドウやボタンコントロールから共通して利用され得ますし、逆に複数のウインドウプロシージャを作ってウインドウ毎の処理を別に記述する事もあり得ます。チュートリアルにおいてはウインドウを一つだけ作り、ウインドウプロシージャも一つだけ作っています。


LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )

第1引数hWndはウインドウハンドルです。これを使えばウインドウプロシージャが複数のウインドウで共有されている場合でも、どのウインドウに対して送られて来たメッセージかを識別できます。前項、InitWindow()の説明の中でウインドウの作成に使った CreateWindow() の戻り値がウインドウハンドルです。

第2引数messageはメッセージの種類を表す番号です。
3番目と4番目の引数はメッセージの詳細を表す数値であり、2番目の引数messageの内容によってその意味が異なります。

ウインドウプロシージャに送られて来たメッセージはチュートリアルにある様にスイッチ文を使って処理するのが一般的な様です。


    switch( message )
    {
        case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hWnd, message, wParam, lParam );
    }

2番目の引数messageは番号だと言いましたが、どの番号がどのメッセージなのか分かり易くするためにWinUser.hファイル(windows.hから読み込まれる)の中でその数字に対して名前を与えてあります。例えば
#define WM_CREATE 0x0001
#define WM_DESTROY 0x0002
#define WM_MOVE 0x0003
#define WM_SIZE 0x0005
・・・という感じです。

チュートリアル1は空のウインドウを出すだけなので処理しているメッセージはWM_PAINTとWM_DESTROYの2種類だけです。

WM_PAINT
ウインドウの描画が必要な際に送られて来るメッセージです。まずウインドウを作って画面に表示した時に描画が必要ですし、それ以外にも最小化して再び復元した時、ウインドウのサイズを変えた時などにシステムが「再描画してはいかがか?」という意図でメッセージを送ってくれます。一つの画面を描画、表示するには莫大なピクセルの色情報を保持する必要があります。そのため基本的にWindowsはアプリケーションが描画したデータを保存しません。その代わり、再描画が必要になる度にメッセージを送って来る訳です。

ちなみに静止画の場合はBeginPaintとEndPaintの間に描画処理を記述すれば良いのですが、今後のチュートリアルでは動的なモデルを表示する予定なので今後もここには何も書かないでしょう。しかしチュートリアル1から7までのプログラムは、実行中にウインドウのサイズを変えると一瞬画面がチラ付くのではないでしょうか?WM_PAINTが来た時にRender()を呼んでやればそのチラ付きはなくなるはずです。

WM_DESTROY
ユーザーがウインドウを閉じる等してウインドウが破壊された時に呼ばれます。その下の

PostQuitMessage( 0 ); がアプリケーションの終了を伝えます。具体的には WM_QUIT メッセージを発行する事になり、これによってwWinMainエントリーポイント内に書いたwhile( WM_QUIT != msg.message )が満たされる事になります。

スイッチ文の最後に
return DefWindowProc( hWnd, message, wParam, lParam );
とあります。これは我々がウインドウプロシージャ内で記述しなかったメッセージに対する一般的な処理をさせるためのものです。私は詳しく知りませんが、こういう仕組みが必要だという事は、実は我々がよく見るウインドウプロシージャの姿はWindowsが持つ膨大なメッセージ処理機構の氷山の一角に過ぎないのかも知れません・・・。その膨大なメッセージに対して、我々は必要なものだけをすくい取ってデフォルトの処理に対するオーバーロードを行なっているに過ぎないと考える事も出来ます。

さて、せっっかくなのでここで比較的良く使いそうなキーボやマウスのイベントを処理するためのコード例を書いておきます。


LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
 PAINTSTRUCT ps;
 HDC hdc;

 switch( message )
 {
 case WM_KEYDOWN:        // キーが押されたとき
  if( wParam == VK_RETURN )
  {
   MessageBox( hWnd, L"Enter" ,L"pressMessage",MB_OK );
  }
  if(wParam == VK_SHIFT)
  {
   MessageBox( hWnd, L"Shift",L"pressMessage",MB_OK );
  }
  if(wParam == VK_CONTROL)
  {
   MessageBox( hWnd, L"Ctrl",L"pressMessage",MB_OK );
  }
  if(wParam == VK_ESCAPE)
  {
   if( IDYES ==MessageBox( hWnd, L"アプリケーションを終了しますか?",L"エスケープキーが押されました",MB_YESNO ) )
    DestroyWindow(hWnd);
  }
  if(wParam == VK_SPACE)
  {
   MessageBox( hWnd, L"space",L"pressMessage",MB_OK );
  }
  if(wParam == VK_LEFT)
  {
   MessageBox( hWnd, L"←",L"pressMessage",MB_OK );
  }
  if(wParam == VK_UP)
  {
   MessageBox( hWnd, L"↑",L"pressMessage",MB_OK );
  }
  if(wParam == VK_RIGHT)
  {
   MessageBox( hWnd, L"→",L"pressMessage",MB_OK );
  }
  if(wParam == VK_DOWN)
  {
   MessageBox( hWnd, L"↓",L"pressMessage",MB_OK );
  }
  if(wParam == VK_F1)
  {
   MessageBox( hWnd, L"F1",L"pressMessage",MB_OK );
  }
  if(wParam =='A')
  {
   MessageBox( hWnd, L"a",L"pressMessage",MB_OK );
  }
  if(wParam =='D')
  {
   MessageBox( hWnd, L"d",L"pressMessage",MB_OK );
  }
  if(wParam =='W')
  {
   MessageBox( hWnd, L"w",L"pressMessage",MB_OK );
  }
  if(wParam =='S')
  {
   MessageBox( hWnd, L"s",L"pressMessage",MB_OK );
  }
  if(wParam =='1')
  {
   MessageBox( hWnd, L"1",L"pressMessage",MB_OK );
  }
  break;

  // マウスイベント
  //ダブルクリックを検知するにはウインドウスタイルでCS_DBLCLIKSのフラグを入れている必要あり。
  //例)wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
 case WM_LBUTTONDOWN:
  MessageBox( hWnd, L"マウス左ダウン",L"pressMessage",MB_OK );
  break;
 case WM_LBUTTONUP:
  MessageBox( hWnd, L"マウス左アップ",L"pressMessage",MB_OK );
  break;
 case WM_LBUTTONDBLCLK:
  MessageBox( hWnd, L"マウス左ダブルクリック",L"pressMessage",MB_OK );
  break;

 case WM_RBUTTONDOWN:
  MessageBox( hWnd, L"マウス右ダウン",L"pressMessage",MB_OK );
  break;
 case WM_RBUTTONUP:
  MessageBox( hWnd, L"マウス右アップ",L"pressMessage",MB_OK );
  break;
 case WM_RBUTTONDBLCLK:
  MessageBox( hWnd, L"マウス右ダブルクリック",L"pressMessage",MB_OK );
  break;

 case WM_MBUTTONDOWN:
  MessageBox( hWnd, L"マウス中ダウン",L"pressMessage",MB_OK );
  break;
 case WM_MBUTTONUP:
  MessageBox( hWnd, L"マウス中アップ",L"pressMessage",MB_OK );
  break;
 case WM_MBUTTONDBLCLK:
  MessageBox( hWnd, L"マウス中ダブルクリック",L"pressMessage",MB_OK );
  break;

 case WM_PAINT:
  hdc = BeginPaint( hWnd, &ps );
  EndPaint( hWnd, &ps );
  break;

 case WM_DESTROY:
  PostQuitMessage( 0 );
  break;

 default:
  return DefWindowProc( hWnd, message, wParam, lParam );
 }

return 0;
}



ちなみにMessageBox関数はメッセージを気軽に出していけるwindowsの関数です。(ただしこういったウインドウズのGUIは一般的にフルスクリーンモードとの相性が悪いです。)
コメントにも記しましたがダブルクリックのメッセージを受け取るにはウインドウクラスの登録の時に、構造体のスタイルメンバにCS_DBLCLKSのフラグを混ぜている必要があります。
各イベントが発生するとどのキーやマウスのメッセージが発生したかをメッセージボックスで教えるので、動作確認が出来る様になっています。しかしマウスをクリックした時点でメッセージボックスにフォーカスが移動してしまうので素早くEnterキーを押してからもう一度画面をクリックしないとダブルクリックの確認は出来ない仕様です。(マウスアップの確認は押しっぱなしが効きます。)

キーボードのメッセージを受け取った時は ウインドウプロシージャ第3引数wParamの値を使って具体的にどのキーが押されたのかを調べています。その値もWinUser.hで文字としての体裁が与えられています。ここで紹介したもの以外も「ウィンドウズ 仮想キー」とかでググれば出てくると思います。


ちなみにウインドウズのアプリケーションはデフォルトでAlt+F4で終了させられますが、上のコードではエスケープキー(Esc)でアプリケーションを終了させられるようにしてみました。

なお、ここで書いているDestroyWindow()はウインドウを閉じるための関数です。これによって上でも説明したWM_DESROYメッセージが発行されます。じゃぁ、DestroyWindow()を呼ばないでここで直接 WM_DESTROYメッセージが処理している様に PostQuitMessage( 0 );でアプリケーションを終了したらどうでしょうか?・・・DestroyWindow()がやっている処理はWM_DESTROYメッセージの発行以外にもあるのでそういう事はしない方がいいです。

0 件のコメント: