Delphi-ML で、IME の変換スタートと終了を知りたい、という投稿がありました。
全ての Edit をサブクラス化して実装しようとされていたのですが、それでは非常に大変だと思い、Windows Hook による方法を投稿しました。
それが、以下のコードです。
unit uIMEStartEnd;interfaceusesWinapi.Windows;type// IME の開始と終了を知らせるイベントTIMEStartEndNotifyEvent =procedure(const iWnd: HWND; const iStart: Boolean) of object;// イベントを受け取るイベントリスナを設定・削除するprocedure AddIMEEventListener(const iEvent: TIMEStartEndNotifyEvent);procedure RemoveIMEEventListener(const iEvent: TIMEStartEndNotifyEvent);implementationusesWinapi.Messages, Vcl.Controls, System.Generics.Collections;var// WindowsHook のハンドルGHookHandle: HHOOK;// イベントリスナのリストGHandlers: TList<TIMEStartEndNotifyEvent>;// イベントリスナを追加procedure AddIMEEventListener(const iEvent: TIMEStartEndNotifyEvent);beginif (GHandlers.IndexOf(iEvent) < 0) thenGHandlers.Add(iEvent);end;// イベントリスナを削除procedure RemoveIMEEventListener(const iEvent: TIMEStartEndNotifyEvent);beginif (GHandlers.IndexOf(iEvent) > -1) thenGHandlers.Remove(iEvent);end;// イベントリスナを呼び出す// iWnd IME メッセージを受け取ったウィンドウ// iStart 開始の場合は Trueprocedure CallEventHandlers(const iWnd: HWND; const iStart: Boolean);varHandler: TIMEStartEndNotifyEvent;beginfor Handler in GHandlers doHandler(iWnd, iStart);end;// Hook のメイン関数function CallWndProc(iNCode: Integer;iWParam: WPARAM;iLParam: LPARAM): LRESULT; stdcall;begin// 先に、次のフックチェインを呼び出してしまうResult := CallNextHookEx(GHookHandle, iNCode, iWParam, iLParam);// iNCode が 0 以下ならフックは作業してはならないif (iNCode < 0) thenExit;// lParam は CWPStruct 形式で、メッセージが入っているwith PCWPStruct(iLParam)^ do begincase message ofWM_IME_STARTCOMPOSITION: begin// IME 変換開始CallEventHandlers(hwnd, True);end;WM_IME_ENDCOMPOSITION: begin// IME 変換終了CallEventHandlers(hwnd, False);end;end;end;end;initializationbegin// イベントハンドラを管理するリストを作成GHandlers := TList<TIMEStartEndNotifyEvent>.Create;// WindowsHookGHookHandle :=SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, 0, GetCurrentThreadID);end;finalizationbegin// Hook を解放UnhookWIndowsHookEx(GHookHandle);// リストを破棄GHandlers.Free;end;end.
具体的にはソースコードのコメントを参照してほしいのですが、一点だけ Windows Hook について説明します。
Windows Hook は Windows の作業に割り込む機構です。
例えば、キーボードの入力があった時や、新しいウィンドウが開くときなど本来アプリケーション側からは見えない Windows の作業に割り込むことができます。
Windows Hook を設定するためには SetWindowsHookEx API を使います。
SetWindowsHookEx の説明を見て頂ければわかるように、様々なフックがあります。
今回は、その中で WH_CALLWNDPROC フックを使う事にしました。
このフックは Window Procedure にメッセージが渡されるタイミングで呼び出されます。
そのタイミングとはいつかというと、具体的には SendMessage API が呼ばれた時です。
PostMessage API を使った場合は、WH_GETMESSAGE フックが使えます。
WH_CALLWNDPROC フックを使うのは IME のメッセージは SendMessage で送られるためです。
そして、今回のソースでは SetWindowsHookEx の3番目の引数に 0 を指定しています。
ここに 0 を指定して、次の引数に現在の Thread ID を指定すると、現在のスレッドで処理される SendMessage についてフックされるます。
この Unit の Initialization 節はメインスレッドから呼ばれるので、メインスレッド(GUI の操作・表示をするスレッド)がメッセージを受け取る度に、CallWndProc 関数が呼ばれることになります。
その結果、Edit などで IME の処理が行われると、それを感知してイベントを発行できます。
また、SetWindowsHookEx の3番目の引数に HInstance を指定してフックする DLL を作ると、全プロセスに結びつくフックを作る事ができます。
これによって、他のプログラムのメッセージを見ることもできます。
これをグローバルフック DLL と呼びます。
グローバルフック DLL の作成については、また次回!
0 件のコメント:
コメントを投稿