[[BORLAND C++]]
INDEX
Section.13 KeyboardEvent
Windowsでツールを造っていると、どうしても、キーイベントをハンドルしなければならない場面に出くわす事がある。例えば、ボタンを表示させて、マウスでクリックしたら先に進む様な場面を、簡略化したい様な場合だ。押されたキーによって、その後の進行を分岐する様な場合は尚更なのである。

  • で、色々探したのだが…
    という訳で、Webやらヘルプを色々と探してみたのだが、なかなか答えには辿りつけなかった。サンプルソースでも分かる様に、手探り状態で、探り当てた結果をちょっと書いておく。ただ、きっともっと良い手段が有るのだろうから、方法が分かり次第報告する。

  • WM_KEYDOWN
    フォーカスのあたっているWindowに対して、キーが押されたイベントとして発生するのがこのイベントである。反対に、キーが放された場合には、WM_KEYUPが発生するが、単一の押下であれば、パラメータは一緒と思われるので、普通はWM_KEYDOWNだけハンドルすれば事は足りる。しかし、このイベント,どうやら、同じWindowの中でも、EDIT等,他のハンドルにフォーカスがあたって居る場合,親Windowにはこのイベントは入らないのである。これは、ちょっと困った事なのである。Windowsのアプリは、ちょっとしたユーザの操作でフォーカスが移動するし、ボタンを押されれば、ボタンにフォーカスが移動してしまう。これではいくら、親Windowで、キーイベントを待っていても、駄目なのである。
    また、このキーイベントにて取得出来るキー情報は、押されたキーの位置情報がメインである。例えば、[A]というキーが押されれば、wParamには、[A(0x0041)]というキーコードが入り、lParamのスキャンコードは[0x1E/キーの位置番号,となりのSキーは0x1Fになる]である。CAPSロックが掛かっていようが、カナモードだろうが、このコードは変わらないのである。アルファベットは、まだましな方で、[@]等の記号は、カタカナコードが返って来るのだ…何れかのキーを押して下さいとか、[A]を押されたらどの様に動作する,[B]を押された場合は、こうするという感じのアプリならば問題無いが、[A]を押された場合と、[a]を押された場合で動作を区別する…大文字/小文字を区別する様なキー入力では、ちょっと使えない…という話になってしまいそうだ。

  • WM_CHAR WM_KEYDOWNが発生すると、EDITコントロール等では、更にWM_CHARのイベントが発生する。このイベントのwParamは、即ちキーコードそのままで、大文字/小文字も区別されているし、親Windowでも同じ様に発生する。しかしながら、元々WM_KEYDOWNが発生しなければ、このイベントは発生しないので、何れにしても簡単にはハンドル出来そうも無いのだ。

  • 解決策
    本来の解決策は、もっと他のところに用意されている気がするのだが、とりあえず、2通りの解決策を考えてみた。
    1. タイマ等のイベントを利用して、常に、親Windowがフォアグラウンドに来る様に(SetForegroundWindow()関数を使う)して、キーイベントを待つ。この方法で、出来ると思ったのだが、実験してみたら、SetForegroundWindow自体が上手く動作しなかった。Win32APIのHELPには、これをすると、Keyboardイベントは、ダイレクトに指定したWindowに来ると書かれていたのだが…。何れにしても、この方法はちょっと強引なので、使えそうも無い。

    2. ダミーのダイヤログ(ボタンやEDIT等のフォーカスを持つものは一切無いもの)を表示して、このダイヤログにて、キー入力を受け付ける。→ちょっとスマートでは無い気もするが、実用を考えると、こんなところか?但し、ダイヤログ単体では、WM_CHARイベントは発生しないので、キーコードについては割り切るしか無い様である。


  • サンプル
    ダミーのダイヤログを表示させて、入力を待つという方法のサンプルである。Windowの左下のKEYというボタンを押すと、小さいダイヤログが表示されるので、キーを入力すれば、得られるコードが表示されるという仕組みである。
    サンプル014のソースコード

  • 追記
    エディットコントロール等のWindowプロシージャをフックして使う方法が分かったので追記しておく。方法は以下の通り,
    • 仮のウインドウプロシージャ(の入れ物)(eg. WPEdit)と、書き換えるプロシージャ(自分で造った本体)(eg.WEditProc())を宣言しておく。 目的のエディットコントロールのハンドルを、HEditとすると、
    • GetWindowLong(HEdit,GWL_WNDPROC)を利用して、ウインドウプロシージャを、仮のウインドウプロシージャに取得する。
    • SetWindowLong(HEdit,GWL_WNDPROC,(LONG)WEditProc)にて、目的のコントロールのプロシージャを、自分で造ったものに置き換える。
    • WEidtProcで、WM_KEYDOWNやWM_CHAR等のイベントを受け取って動作すれば良い。
    • 全てはハンドル出来ないので、自分がハンドルした以外のメッセージについては、CallWindowProc()を使って、元のコントロールのプロシージャにとばす。その戻り値を返せば良い。
    WNDPROC WPEdit ;

    LRESULT CALLBACK WEditProc(HWND……)
    {
      if(msg==WM_CHAR)…
      …
      return CallWindowProc(WPEdit,hwnd,msg,wPalam,lParam) ;
    }

    …   HEdit=CreateWindow("EDIT",…) ;

      WPEdit=(WNDPROC)GetWindowLong(HEdit,GWL_WNDPROC) ;
      SetWindowLong(HEdit,GWL_WNDPROC,(LONG)WEditProc) ;
    この方法を応用すれば、どんな事が出来るだろう?以下の様な応用方法が考えられる
    • 数字とか、指定された文字しか入力出来ないエディット
    • WM_KEYDOWN,WM_LBUTTONDOWN等をフックすれば、リードオンリーだが、WM_CHARでのプログラム側からは入力の出来るエディット
    • ボタンの補助無しで、[リターン]を押されたら、イベントを上げるエディット
2002/04/30
2002/05/09
HomeSweetHome2
Ozzy's Software