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通りの解決策を考えてみた。
- タイマ等のイベントを利用して、常に、親Windowがフォアグラウンドに来る様に(SetForegroundWindow()関数を使う)して、キーイベントを待つ。この方法で、出来ると思ったのだが、実験してみたら、SetForegroundWindow自体が上手く動作しなかった。Win32APIのHELPには、これをすると、Keyboardイベントは、ダイレクトに指定したWindowに来ると書かれていたのだが…。何れにしても、この方法はちょっと強引なので、使えそうも無い。
- ダミーのダイヤログ(ボタンや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でのプログラム側からは入力の出来るエディット
- ボタンの補助無しで、[リターン]を押されたら、イベントを上げるエディット
|