SetWindowLongによるウインドウのサブクラス化とタブキー

 ウインドウのウインドウプロシージャ−は、SetWindowLong()で書き換える事が出来ます。自分で作成したウインドウクラスのウインドウはもちろん、EDITを始めとしたWindowsの方で用意されているウインドウクラスのインスタンスのウインドウプロシージャも書き換えられるので既存のGUI部品に機能を追加・変更する事が可能になります。今回は、EDITのウインドウプロシージャ−を書き換えてTabキーによるフォーカス移動を実装してみました。

ウインドウプロシージャ−の書き換え

 ウインドウプロシージャ−を書き換えるには、SetWindowLong()GWL_WNDPROCを指定してウインドウのハンドルと新しいウインドウプロシージャ−のアドレスを渡します。例えば、ウインドウhwndのウインドウプロシージャ−をfnProcで書き換えるなら

  SetWindowLong(hwnd,GWL_WNDPROC,(DWORD)fnProc);

 とするわけです。ただし、実際には新しいウインドウプロシージャ−で処理しないメッセージなどを元のウインドウプロシージャ−で処理するために元のウインドウプロシージャ−のアドレスも必要になります。SetWindowLong()でウインドウプロシージャ−を書き換えると元のウインドウプロシージャ−のアドレスが返されるので、そのアドレスをWNDPROC型にキャストして保存しておくと良いでしょう。以下のような感じになります。

  lpfnProc=(WNDPROC)SetWindowLong(hwnd,GWL_WNDPROC,(DWORD)fnProc);

 ウインドウプロシージャ−は基本的に通常のウインドウプロシージャ−と同様に書けますが、自分で処理しないメッセージなどはDefWindowProc()でデフォルトの処理をさせるのではなくCallWindowProc()で元のウインドウプロシージャ−に渡してやります。つまり、このウインドウプロシージャ−では自分で処理したい部分だけを書き換えて後は元と同じ処理をさせるわけですね。文字通りウインドウを「サブクラス化」するような感覚と言えるでしょう。上の例だと、ウインドウプロシージャの最後で

 return (CallWindowProc(lpfnProc,hwnd,iMsg,wParam,lParam));

 とします。

サブクラス化によるフォーカス移動

 Tabキーによるフォーカス移動は、ウインドウをサブクラス化してTabキーが押されたら次のウインドウにSetFocus()する、という感じで実現します。問題は、「次のウインドウ」をどう指定するか、ですね。今回は、HWND型の配列を作りウインドウを配列にしました。つまり、

  for (i=0;i<4;i++) {

      /* EDITの配列作成 */
      hwWin[i]=CreateWindow("EDIT",NULL,WS_CHILD|WS_VISIBLE,
        8+i*40,8,32,24,hwnd,(HMENU)i,hInst,NULL);

      /* EDITのウインドウプロシージャ−を置換 */
      lpfnProc[i]=(WNDPROC)SetWindowLong(hwWin[i],GWL_WNDPROC,(DWORD)CtrProc);

  }

 という感じで複数のEDITを配列にまとめるわけです。これで、SetFocus(hwWin[i])とすれば配列内のEDITに次々とフォーカスを移動して行く事が出来ます。また、ウインドウプロシージャ−も共通のものにしている点に注意してください。今回は、すべてのEDITに対して「次のEDITにフォーカスを移す」という共通の処理を行うため、一つの関数でまとめた方がすっきりします。EDITにはそれぞれ配列の添え字と同じ数値をIDとして設定しているので、ウインドウプロシージャ−でIDを見ればどのEDITの処理かも特定可能です。

 では、ウインドウプロシージャ−CtrProc()を見てみましょう。この関数では、Tabキーが押されたらフォーカスを移動する、という処理を行うのでWM_CHARメッセージでタブキーが渡されたらフォーカスを移してそのまま帰ります。つまり、Tabキーに対する反応をフックして元の関数の処理を変更するわけです。

  LRESULT CALLBACK CtrProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {

      int i;

      /* メッセージを受信したウインドウを特定 */
      i=GetWindowLong(hwnd,GWL_ID);

      switch (iMsg) {
 
          case WM_CHAR:
		
              if ((DWORD)wParam=='\t') { /* Tabキー */

                  /* 次のウインドウにフォーカス移動 */
                  SetFocus(hwWin[(i+1) % 4]);

                  return 0;

              }

          break;

      }

      return (CallWindowProc(lpfnProc[i],hwnd,iMsg,wParam,lParam));

  }

 最初のGetWindowLong(hwnd,GWL_ID);が、IDを取得してどのウインドウに対する処理かを特定する部分です。

プログラム

 実行すると4つのEDITを持つウインドウが表示されるので、EDITをクリックしてからTabキーでフォーカスを移動してみてください。

プログラムソース表示

戻る