|
|
|
TKeyMacro コンポーネント開発日誌
2001/05/07
2001/06/29 更新
フォーカスを受け取る TWinControl コンポーネントに対して、キーボードマク ロを実現するコンポーネントを開発しました。ここでは、この TKeyMacro コ ンポーネント開発の経過と今後行われるかもしれないバージョンアップ情報 などについて書きたいと思いま す。今回は、TKeyMacro コンポーネントにも同梱されている keymacrodev.txt の内容をそのまま掲載します。
■ TKeyMacro コンポーネントについて
■ 概要
Delphi-IDE のコードエディタで、Shift + Ctrl + R で記録開始・記録終了 Shift + Ctrl + P で記録されたキー操作を再現することが出来ますが、この仕 組みをキーボードマクロと呼ぶことにします。TKeyMacro コンポーネントは、 この仕組みを、フォーカスを受け取る(OnEnter イベントを持つ)TWinControl に対して実現するためのコンポーネントです。■ Delphi でのキーメッセージの流れ
キーボードマクロ実現には、記録開始時点から指定されたウィンドゥコントロ ールが受け取るキーメッセージを記録し、マクロ実行にはこの記録されたキーメ ッセージを対象コントロールへ SendMessage することで実現出来るはずです。通常の Windows アプリケーションは、色々なメッセージを受け取り、それを どう処理するかによってその動作が変わりますが、Delphi によって作成される アプリケーションではその仕組みに独特の手法が取り入れられています。
Delphi によって作成されるアプリケーションは、その名も TApplication ク ラスのインスタンス(以下 Application )によって動いています。皆さんが作 成される Project1.dpr の最後には、Application.Run; の1行があると思いま すが、この Run メソッドでは Application が終了されるまでループを繰り返し ています。ループ内では HandleMessage メソッド -> ProcessMessage メソッド が延々と実行され、Application にやって来るメッセージをせっせと処理してい ます。
この時、例えば Application によって管理される TMemo のインスタンスが WM_KEYDOWN を受け取った場合、ProcessMessage メソッドでは、それがメニュー のショートカットキーかどうかの判別を行うために IsKeyMsg メソッドで、 WM_KEYDOWN メッセージを一旦 CN_KEYDOWN メッセージに加工して、指定された 宛先( TMemo のインスタンス)へ送っています。
TMemo の基底クラスである TWinControl では、この CN_KEYDOWN メッセージ を受け取ると、TWinControl.IsMenuKey メソッドを実行して、メニューのショー トカットキーかどうかの判別を行います。判別が真の場合メッセージの Result に1が代入され、偽の場合は0が代入されます。
ProcessMessage メソッドでは加工して送ったメッセージの Result が0の場 合は、元の WN_KEYDOWN に戻して再び TMemo のインスタンスへ送ります。 TMemo は WM_KEYDOWN メッセージハンドラに従って動作します。
同様に、WM_CHAR は CN_CHAR へと加工され、TWinControl.CHChar メッセージ ハンドラでは、CM_DIALOGCHAR メッセージを発行し、各コントロールの CM_DIALOGCHAR メッセージハンドラでは、それがアクセラレーターキーかどうか 判別するなどの処理が行われています。
この仕組みについての詳細は、VCLをお持ちでしたら、
Forms.pas TApplication.ProcessMessage -> IsKeyMsg
Controls.pas TWinControl.CNKeyDown -> IsMenuKey
Controls.pas TWinControl.CNChar
StdCtrls.pas 内の各 CMDialogChar -> IsAccel
などを参照して下さい。また、Delphi CD-ROM に入っている Api32wh.lhp の「メッセージとメッセージ キューの概要」のトピックの記述は大変参考になりますので、ご一読されること をお勧め致します。
■ マクロを実現するために保存すべきメッセージ
つまり、TWinControl へ CN_KEYDOWN, CN_CHAR を投げてやれば、それがメニ ューのショートカットかどうか、またアクセラレーターキーかどうかを判別して くれることになりますから、CN_KEYDOWN, CN_CHAR を保存し、マクロ実行時には、 まず、CN_KEYDOWN, CN_CHAR を投げて、処理されなかったら、(メッセージ の Result が0だったら)WM_KEYDOWN, WM_CHAR に加工してもう一度投げるという 手法でマクロを実現することが出来ます。もう一つ IME からの文字列を受け取る WM_IME_CHAR メッセージを処理しなけ ればなりません。WM_IME_CHAR の場合、メッセージを受け取った直後に文字数分 の(バイト数ではない)CN_CHAR もやって来ますので、WM_IME_CHAR 直後の CN_ CHAR は保存対象からはずすことにします。
■ サブクラス化
メッセージを記録するためには、対象コントロールへやって来るメッセージを 全部見張る必要がありますが、TKeyMacro コンポーネントでは、マクロ記録時に 限って、対象コントロールをサブクラス化し、上記 CN_KEYDOWN, CN_CHAR, WM_I ME_CHAR メッセージだけを保存しています。メッセージを保存する時には、キーボードの状態(Ctrl, Shift, Alt が押さ れ ているかどうか)も保存してマクロ実行時にはそれを再現することも忘れては なりませんね。
■ マクロの実行 WM_IME_CHAR 問題
IME で変換確定を行った後の TMemo のメッセージを観察すると、WM_IME_CHAR が文字列数(バイト数ではない)送られて来た後に、同じ数の WM_CHAR が やって来るのは上述の通りですが、この時の WM_CHAR の CharCode には、$0082 など、全角1文字目だけが入っています。
ex1 \ ( ^ o ^ ) /
WM_IME_CHAR 815F, 28, 5E, 6F, 5E, 29, 815E
WM_CHAR 0081, 28, 5E, 6F, 5E, 29, 0081
ex2 あ い う え お
WM_IME_CHAR 82A0, 82A2, 82A4, 82A6, 82A8
WM_CHAR 0082, 0082, 0082, 0082, 0082
WM_IME_CHAR メッセージを受け取った直後に WM_CHAR メッセージが再来するの は、教科書通りの動作です。が、全角文字の2バイト目が欠落していることから の推測として、TMemo のような Windows 標準コントロールでは、WM_CHAR メッ セージハンドラで LeadBytes を受け取った時、メッセージキューから下位バイ トを取り出しているように思われます。以下の実験で試すことが出来ます。
procedure TForm1.Memo1Click(Sender: TObject); var I: Integer; begin // 念のため、メッセージキューを空にする Application.ProcessMessages; // 「あいうえお」の下位バイトだけをメッセージキューに溜める PostMessage(Memo1.Handle, WM_CHAR, $A0, 0); PostMessage(Memo1.Handle, WM_CHAR, $A2, 0); PostMessage(Memo1.Handle, WM_CHAR, $A4, 0); PostMessage(Memo1.Handle, WM_CHAR, $A6, 0); PostMessage(Memo1.Handle, WM_CHAR, $A8, 0); // 「あいうえお」の上位バイトだけをウィンドプロシージャに送りつける for I := 0 to 4 do SendMessage(Memo1.Handle, WM_CHAR, $82, 0); // 改行 SendMessage(Memo1.Handle, WM_CHAR, $0D, 0); end;最近まで、WM_IME_CHAR メッセージハンドラ内で文字列の取得処理を行い、その 際 CharCode shr 8 が実行され CharCode が更新されているのだろうという推測 に基づき TEditor の開発を進めていましたが、Windows2000 上で不具合が発生 し、TMemo をいじめて見て上記の仕組みを発見しました。余談でした。■ Application.ProcessMessages のジレンマ
WM_IME_CHAR と CN_KEYDOWN, CN_CHAR が混在するマクロを実行する場合、例え ば、あいうえおVK_RETURN を SendMessage すると、対象となるコントロールは 以下のようなメッセージを受け取ることになりますが、VK_RETURN が最初に処理 されてしまいます。
ex3
WM_IME_CHAR 82A0 あ
WM_IME_CHAR 82A2 い
WM_IME_CHAR 82A4 う
WM_IME_CHAR 82A6 え
WM_IME_CHAR 82A8 お
WM_CHAR 000D VK_RETURN
WM_CHAR 0082
WM_CHAR 0082
WM_CHAR 0082
WM_CHAR 0082
WM_CHAR 0082
これは、WM_IME_CHAR を SendMessage した場合 Windows によって2バイト文字 は2個の、1バイト文字は1個の WM_CHAR メッセージとしてメッセージキュー に溜められるだけで、ウィンドプロシージャでは処理されずに順番待ち状態にな るためです。この処理が「あいうえお」と続いて $0D の WM_CHAR がウィンドプ ロシージャに来ますので、まず最初に「改行」が実行されてしまいます。その処 理終了後、暇になった Application はおもむろにメッセージキューから先程溜 まった「あいうえお」を取り出して処理するという流れになります。
そこで、WM_IME_CHAR を SendMessage した後に Application.ProcessMessages を実行すると、以下のような順でメッセージを受け取り、最後の改行も処理され るようになります。
ex4
WM_IME_CHAR 82A0 あ
WM_CHAR 0082
WM_IME_CHAR 82A2 い
WM_CHAR 0082
WM_IME_CHAR 82A4 う
WM_CHAR 0082
WM_IME_CHAR 82A6 え
WM_CHAR 0082
WM_IME_CHAR 82A8 お
WM_CHAR 0082
WM_CHAR 000D VK_RETURN
ところが、マクロ実行メニューのショートカットキーに Shift + Ctrl + P を割 り当てて、そのショートカットキーを押しっぱなしにすると、マクロデータの合 間に P の文字が挿入されてしまう場合があります。
これは、押しっぱなしにしたことによって、メッセージキューに p のキーデー タが溜まった状態でマクロが実行されるため、途中の ProcessMessages 実行が メッセージキューから p をも取り出して処理してしまうために発生していると 思われます。
本来メッセージには(メッセージキューに格納されるデータには)Shift, Ctrl, Alt など、キーボードの状態の情報は入っておらず、PeekMessage されたデータ がメッセージハンドラへ渡され、それからやっとキーボードの状態を取得し、そ れをどう処理するのかの判別が行われますが、その瞬間のキーボードの状態がマ クロデータによって更新されてしまっているのがこの現象の原因だと考えられま す。
つまり、メッセージキューにデータが無くなるまで PeekMessage のループを回 す Application.ProcessMessages は使えないことになるのですが、 WM_IME_CHAR を SendMessage したときに限って、ProcessMessage で行われてい る処理を1回だけ行って見ると、P が挿入されることなく ex4 のような結果を 得ることが出来ましたので、TKeyMacro.Execute ではこの方式を採用することに しました。以下のような流れになります。
WM_IME_CHAR 「あ」$82A0 を SendMessage する
↓
メッセージキューに $82, $A0 の WM_CHAR が2個溜まる
↓
メッセージキューから $82 の WM_CHAR を取り出して、ウィンドプロシージャに 送りつける( TranslateMessage, DispatchMessage する)
↓
WM_CHAR メッセージハンドラでは LeadBytes を受け取ったので、メッセージキ ューから次の $A0 を取り出して2バイト文字として処理する。■ キーボードの状態の保存と復帰
マクロを実行することで、キーボードの状態が更新されてしまいますので、元に 戻す手段も必要になります。これが無いと、 Shift + Ctrl + P の連続実行が出 来なくなります。TKeyMacro.Execute では GetKeyboardState, SetKeyboardState を利用していますが、マクロ実行のループ途中にキーボード から手が離れるなどの状態変化に追随するために、マクロデータを1個実行する 都度、その直前のキーボードの状態を取得しています。
■ マクロデータのセーブとロード
記録したマクロデータをテキストファイルに保存・復帰するメソッドが ver 1.2 から実装されています。
保存用 procedure SaveToFile(const FileName: string); virtual; procedure SaveToStream(Stream: TStream); virtual; procedure MacroDataToStream(Stream: TStream); virtual; 復帰用 procedure LoadFromFile(const FileName: string); virtual; procedure LoadFromStream(Stream: TStream); virtual; function StreamToMacroData(Stream: TStream): Boolean; virtual;以下は「あいう」Enter を記録したデータです。実際には CN_KEYDOWN データ も保存されますが、ここでは掲載していません。
WM_IME_CHAR: 646, CharCode: 33440, KeyData: 1, Shift: 1, Ctrl: 0, Alt: 1 // あ
WM_IME_CHAR: 646, CharCode: 33442, KeyData: 1, Shift: 1, Ctrl: 0, Alt: 1 // い
WM_IME_CHAR: 646, CharCode: 33444, KeyData: 1, Shift: 1, Ctrl: 0, Alt: 1 // う
CN_CHAR: 48386, CharCode: 13, KeyData: 1835009, Shift: 1, Ctrl: 0, Alt: 1 // #13このデータを読み込むメソッドでは、Classes.pas にある TParser を利用して います。実際の読み込み処理では TParser が toInteger を返してきた場合の トークンを数値に変換して行頭から6個だけデータを読み込みます。ですから
646,33440,1,1,0,1
といったデータも有効なのです。6個目以降は改行 #13#10 を見つけるまでの トークンは無視されますので
646,33440,1,1,0,1 // あ $82A0
というのもおっけ〜です。
Shift, Ctrl, Alt については、マクロ記録時に押されていた場合は、128か 129が入ります。押されていない場合は、1か0です。以下は Api32wh.hlp 「GetKeyState」のトピックからの引用です。
>戻り値 > >関数が正常に終了した場合は、 指定された仮想キーの状態を示します。上位ビッ >トが1のとき、 キーは押されています。それ以外の場合、 キーは押されていませ >ん。下位ビットが1のとき、 キーはトグル状態です。CapsLockキーなどのキーは、 >オンされているときトグル状態です。下位ビットが0のとき、 キーはオフされて >おり、 トグル解除の状態です。キーボード上にあるトグル キーのインジケータ >ライトはもしあれば、 キーがトグル状態のときオンされ、 トグル解除の状態のと >きオフされます。cC Enter と入力されたときのマクロデータは
CN_CHAR: 48386, CharCode: 99, KeyData: 3014657, Shift: 0, Ctrl: 1, Alt: 0 // c
CN_CHAR: 48386, CharCode: 67, KeyData: 3014657, Shift: 129, Ctrl: 1, Alt: 0 // C
CN_CHAR: 48386, CharCode: 13, KeyData: 1835009, Shift: 1, Ctrl: 1, Alt: 0 // #13となります。数値だけで記述すると
48386,99,3014657,0,1,0
48386,67,3014657,129,1,0
48386,13,1835009,1,1,0
です。99は「c」67は「C」ですね。ぢゃ Shift は意味ないぢゃん、という ことになるのですが、確かに文字を入力するだけのマクロデータを作成する場合 は必要ありません。それから、KeyData も余程のことがない限り0で大丈夫です。 さらに裏技として WM_CHAR をロードすることも可能です。258,99,0,0,0,0
258,67,0,0,0,0
258,13,0,0,0,0
cC 改行が実行されます。メニューのショートカットかどうか、アクセラレー ターキーかどうかの判別は行われませんので、ご注意下さい。
$102,$63,0,0,0,0
$102,$43,0,0,0,0
$102,$0D,0,0,0,0
と $ を付けて16進で記述してもおっけ〜なのは、さすが TParser と言ったと ころでしょうか。TKeyMacro コンポーネントに付属のサンプルプロジェクトで データ表示覧に色々とデータを書いて試して見て下さい。
|
|