Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2021年11月22日] [更新 2021年11月24日] Delphi サンプルプログラム集
   
986_仮想キーコードとショートカットキー送信操作用ユニット
動作確認等 Windows 10 ビルド 19042 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
986_SendShortCutKey.zip [16.3 MB] 2021年11月24日版(EXE同梱)







仮想キーコードとショートカットキー送信操作用ユニット


他のアプリに仮想キーコードによるキー操作をエミュレートするため関数類のユニットです.[Ctrl] + [C] に限らず,ショートカットキー送信を汎用的に利用する目的で作成しましす.仮想キーコードの送信には keybd_event 関数,SendInput 関数を使用した仮想キーコードのkeybd_event 関数を使用していますが,日本語の送信用には SendInput 関数を使用しています.
keybd_event 関数,SendInput 関数を使用した仮想キーコードの送信では,アクティブなウィンドのフォーカスがあるコントロールがキー操作のメッセージを受け取ります.キー操作のメッセージは,受信側のスレッドのメッセージキューに Post されます.

添付の plSendShortCut.pas と plvkEnum.pas をライブラリパスの通ったフォルダ内に置き,uses に plSendShortCut を追加して使用します.
記事の前半は関数の使用方法を兼ねたサンプルです.後半は SendInput 関数とキー操作とそのメッセージに関する内容になっています.






01_コピーと貼り付けのテスト


コピーと貼り付けだけの単純なテストです.Ctrl + V のように半角の空白があっても構いません.ます.仮想キーコード名の先頭の VK_ を省略できるようにしています.このサンプルは uses に Vcl.Clipbrd が必要です.
この例のように,ショートカットキー送信には SendShortCutKeyEx 関数を使用します.第 1 引数は送信先のウィンドウのハンドルてす.第 2 引数が送信する仮想キーコードのキー名の組み合わせて,文字列として渡します.



図1
ショートカットキー送信操作用ユニットの使用例
  • [Ctrl] + [C] と [Ctrl] + [V] によるコピーと貼り付けの操作

リスト1
ショートカットキー送信操作用ユニットのテスト.テキストのコピーと貼り付け
implementation

uses
  plSendShortCut,
  Vcl.Clipbrd;

{$R *.dfm}

//=============================================================================
//  ショートカットキー送信テスト
//  ショートカットキー送信関係の関数を別ユニットにして利用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  Edit1.SetFocus;
  Edit1.SelStart := Length(Edit1.Text);

  Clipboard.AsText := 'テスト文字列';

  // 送信先のウインドウハンドルを何らかの方法で取得
  LTargetWnd := Handle;
  SendShortCutKeyEx(LTargetWnd, 'Ctrl + V');

// 以下の 2 行は Ctrl + A + C でもよい
  SendShortCutKeyEx(LTargetWnd, 'Ctrl + A');
  SendShortCutKeyEx(LTargetWnd, 'Ctrl + C');
  SendShortCutKeyEx(LTargetWnd, 'Home');
end;


[Ctrl] + [V] のように括弧付きでも動作します.第 1 引数は,送信先のウィンドウがアクティブであることが明確な場合は省略できます.
以下の 3 つの例は,全て同じ動作となります.


  // 第1 引数のウィンドウハンドルの値を指定
  SendShortCutKeyEx(LTargetWnd, 'Ctrl + V');
  SendShortCutKeyEx(LTargetWnd, 'Ctrl + A');


  // 第1 引数のウィンドウハンドルの値を省略
  SetForegroundWindow(LTargetWnd);
  SendShortCutKeyEx('Ctrl + V');
  SendShortCutKeyEx('Ctrl + A');


  // キー名を [ ] で括る形式
  SetForegroundWindow(LTargetWnd);
  SendShortCutKeyEx('[Ctrl] + [V]');
  SendShortCutKeyEx('[Ctrl] + [A]');




02_[ + ] キーの送信とウェブブラウザの操作とメニュー操作


[Ctrl] + [+] のようなショートカットキーの場合は,キー名を結合する + と区別するために,[+] のように [] で括って指定する例です.
以下はウェブブラウザあるいはメモ帳の表示を拡大または縮小表示するサンプルです.

Microsoft Excel (エクセル) の拡大のショートカットキーは [Ctrl] +[Alt] + [+] で,縮小は [Ctrl] + [Alt] + [-] です.Microsoft Word (ワード) の拡大のショートカットキーは [Ctrl] + []] または [Ctrl] + [Shift] + [.] ( > の文字のキー) で,縮小は [Ctrl] + [[] または [Ctrl] + [Shfit] + [;] ( < の文字のキー) です.ワードの場合,拡大と縮小の機能は,選択状態のテキストが対象です.



図2
ウェブブラウザの拡大表示
  • これはデフォルトの状態
図3
拡大表示した状態

リスト2
ショートカットキー送信操作用ユニットのテスト.ウェブブラウザの拡大と縮小表示
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  ショートカットキー送信テスト
//  ウェブブラウザ等の Zoom 機能のショートカットキー操作
//
//  動作確認方法
//  (1) ウェブブラウザまたはメモ帳を前面に表示
//  (2) 本プログラムの TButton をクリック
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  // このフォームを表示する前にアクティブだったウィンドウのハンドルを取得
  LTargetWnd := GetNextWindow(Handle, GW_HWNDNEXT);
  while not IsWindowVisible(LTargetWnd) do begin
    LTargetWnd := GetNextWindow(LTargetWnd, GW_HWNDNEXT);
  end;


  if Sender = Button1 then begin
    // 拡大
    // + は [] で括る
    // Ctrl + + でいいのだが...
    SendShortCutKeyEx(LTargetWnd, 'Ctrl + [+]');
  end;

  if Sender = Button2 then begin
    // 縮小
    SendShortCutKeyEx(LTargetWnd, 'Ctrl + -');
  end;

  if Sender = Button3 then begin
    // 等倍
    SendShortCutKeyEx(LTargetWnd, 'Ctrl + 0');
  end;

  SetForegroundWindow(Handle)
end;


メニューで操作する例です.以下のように,メニューのクセラレータキーを順番に押す操作の指定をします.
メニューバーのプルダウンメニューは,[Alt] + [アクセラレータキー] で表示しますが,そのウィンドウが非アクティブになると自動的に閉じてしまうタイプと,意図的に [ESC] キーやメニューの項目をクリックしないと閉じないタイプがあります.

ウィンドウのキャプションバー (タイトルバー) の左端を,マウスの右ボタンをクリックすると表示するシステムメニューは,[Alt] + [Space]で表示できます.


    // [表示] のメニューを開く
    SendShortCutKeyEx(LTargetWnd, 'Alt + V') ;
    // アプリによってはタイムラグが必要
    Sleep(10);
    // アクセラレータキーを送信
    SendShortCutKeyEx(LTargetWnd, 'Z + I') ;


ウィンドウが非アクティブになると閉じるメニューの場合は,以下のコードでも動作します.


    // [表示] のメニューを開いてアクセラレータキーを操作
    SendShortCutKeyEx(LTargetWnd, 'Alt + V + Z + I') ;


図4
メニューのアクセラレータキーを操作して拡大表示
  • [Alt] + [V] で [表示] メニューを開く
  • [Z] [I] のキーを順番に押す




03_VK_BROWSER_XXX, VK_MEDIA_XXX 等の送信


仮想キーコードの定数名に,VK_BROWSER_XXX,VK_MEDIA_XXX 等,ウェブブラウザと Media Player の操作用があります.それらは末尾に Ex がない SendShortCutKey 手続きを使用してください.文字に変換して引数に渡します.
以下は,ウェブブラウザの [戻る] [進む] の操作例です.


図5
ウェブブラウザの [戻る] [進む] の操作

リスト3
ショートカットキー送信操作用ユニットのテスト.ウェブブラウザの [戻る] [進む] の操作例
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  拡張キーコード VK_BROWSER_XXX 等の操作
//  末尾に Ex がない手続きを使用する
//
//  動作確認方法
//  (1) ウェブブラウザを前面に表示
//  (2) 本プログラムの TButton をクリック
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  // このフォームを表示する前にアクティブだったウィンドウのハンドルを取得
  LTargetWnd := GetNextWindow(Handle, GW_HWNDNEXT);
  while not IsWindowVisible(LTargetWnd) do begin
    LTargetWnd := GetNextWindow(LTargetWnd, GW_HWNDNEXT);
  end;


  if Sender = Button1 then begin
    // ブラウザの [戻る]
    // vkBrowserBack も使用可能だが uses に System.UITypes が必要
    SendShortCutKey(LTargetWnd, Char(VK_BROWSER_BACK));
  end;

  if Sender = Button2 then begin
    // ブラウザの [進む]
    SendShortCutKey(LTargetWnd, Char(VK_BROWSER_FORWARD));
  end;

  if Sender = Button3 then begin
    // ブラウザの [ホーム]
    SendShortCutKey(LTargetWnd, Char(VK_BROWSER_HOME));
  end;

  SetForegroundWindow(Handle)
end;


[戻る] や [進む] に限らず,ウェブブラウザには多くのショートカットキーがあります.下表はその一部です.例えば,ブラウザの URL 欄の日本語 IME が OFF の状態で次のコードを実行すると,指定したウェブページを現在のタブに表示します.


    // 指定したウェブページを表示
    // アドレスバーは検索にも使用できる
    SendShortCutKeyEx(LTargetWnd, 'Alt + D');
    SendShortCutKeyEx(LTargetWnd, 'Delete');
    SendInputText(LTargetWnd, 'http://mrxray.on.coocan.jp/index.htm');
    SendShortCutKeyEx(LTargetWnd, 'Enter');


ショートカットキー 機能
[Alt] + [←] 戻る
[Alt] + [→] 進む
[Alt] + [Home] ホーム
[Home] ページの先頭へ
[End] ページの最後へ
[Alt] + [D] アドレスバーに移動して 全選択
[Ctrl] + [F] ページ内検索
ページ内検索中の次検索は [F3]
前検索は [Shift] + [F3]
[Ctrl] + [E] 検索窓を表示.閉じるのは [ESC]
[Ctrl] + [F4] 現在のタブを閉じる
[Ctrl] + [T] 新規タブ
[Ctrl] + [TAB] タブの移動 (右方向)
[Shift] + [Ctrl] + [T] 閉じた直後のタブを再表示
[F5] ページ更新
[Shift] + [F5] キャッシュを使用しないページ更新
[Ctrl] + [F5] ページのキャッシュをクリア
[Ctrl] + [U] ページのソースを新規タブで表示して,そのタブをアクティブにする
[F12] (要素の) 検証.開発者用.トグル




04_[ ファイル名を指定して実行 ] でコマンドを管理者権限で実行


[ファイル名を指定して実行] のダイアログを起動して,cmd という文字列を送信し,管理者権限でコマンドプロンプトを起動する例です.



図6
コマンドプロンプトを管理者権限で起動
  • [ファイル名を指定して実行] のダイアログを起動
  • 実行するファイル名を自動入力
  • [Shift} + [Ctrl] + [Enter] で起動すると管理者権限での起動になる

リスト4
ショートカットキー送信操作用ユニットのテスト.コマンドプロンプトを管理者権限で起動
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  ショートカットキー送信テスト
//  [ファイル名を指定して実行] から管理者権限でコマンドプロンプトを起動
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  LTargetWnd := Handle;

  // [ファイル名を指定して実行] のダイアログを起動
  SendShortCutKeyEx(LTargetWnd, 'Win + R');
  Sleep(10);
  LTargetWnd := GetForegroundWindow;

  // コマンドプロンプトを管理者権限で起動
  SendShortCutKeyEx(LTargetWnd, 'Ctrl + A + Del');
  SendKeysASCII(LTargetWnd, 'cmd');
  SendShortCutKeyEx(LTargetWnd, 'Shift + Ctrl + Enter');
end;




05_[ Windows ] キーに関するショートカットキー


Windows 10 になって [Windows] キーを使用したショートカットキーが大幅に増えました.次のショートカットキーは [ 切り取り&スケッチ] というスクリーンショット用のツールを起動します.このツールは,初期の Windows 10 にはありません.キャプチャした画像はアクションセンターにサムネイルが表示されます.そのサムネイル画像をクリックすると [ 切り取り&スケッチ] の画像編集ツールが起動します.
アクションセンターのショートカットキーは [Win] + [A] です.

アクティブなウィンドウは [Alt] + [Print Screen] でキャプチャしてクリップボードに画像をコピーできます.画面全体のキャプチャは [Print Screen] です.[Print Screen] はキーの刻印が [PrtSc] や [PrtScn] のキーボードもあります.


SendShortCutKeyEx(Handle, 'Win + Shift + S');


図7
[ 切り取り&スケッチ] ツール

[スタート] のメニューは [Windows] キーで表示します.[Win] + [X] で表示されるメニューの項目は,次のような操作で実行できます.


  LTargetWnd := Handle;

  // [Win] + [X] のメニューを開く
  SendShortCutKeyEx(LTargetWnd, 'Win + X');

  // メニューウィンドウのアクセラレータキーを送信
  // タイムラグが必要かも知れない
  LTargetWnd := GetForegroundWindow;
  SendShortCutKeyEx(LTargetWnd, 'F');


以下のコードは絵文字パネルを表示します.


  Edit1.SetFocus;
  SendShortCutKeyEx(Handle, 'Win + >');




06_ASCII 文字列の送信


keybd_event 関数を使用して文字を送信する手続きを実装しています.手続き名は SendKeysASCII です.扱えるのは ASCII の英数字と記号に限ります.それ以外の仮想キーコードが含まれていると,正常に動作しないことがあります.仮想キーコードには ASCII コードと重複する値があります.このショートカットキー送信操作用ユニットでは,それを回避するために,仮想キーコードの値ではなく,文字列として渡して処理するようにしています.
漢字を含む日本語の送信には SendInputText 手続きを使用してください.

keybd_event 関数,SendInput 関数を使用して文字列の送信が可能なウィンドウであれば,クリップボードの操作でもテキストとしての文字列を貼り付けることができます.



図8
文字送信用手続きのテスト
  • 欧文フォントを使用しているので,\ 記号がバックスラッシュになっている

リスト5
ショートカットキー送信操作用ユニットのテスト.文字送信用手続きのテスト
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  ショートカットキー送信テスト
//  文字送信用 SendKeysASCII のテスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
  LText      : string;
begin
  // 送信先のウインドウハンドルを何らかの方法で取得
  LTargetWnd := Handle;

  Memo1.SetFocus;
  Memo1.Lines.Clear;
  Memo1.Font.Size := 18;

  LText := 'Delphi XE5 Windows 10' + sLineBreak
         + '!"#$%&''()' + sLineBreak
         + '-=^~\|+*;:[]{}<>,./?_' + sLineBreak;

  SendKeysASCII(LTargetWnd, LText);
end;


下図は,受信側の入力コントロールの日本語 IME が ON になっている状態で文字列「akaihana」を送信した場合です.通常のローマ字入力の時と同じ未確定の状態になります.



図9
文字列「akaihana」を送信
  • 受信側の入力ントロールの日本語 IME が ON で,ローマ字入力の場合
  • 変換が未確定の状態になる




07_メモ帳を閉じる時の操作


メモ帳の保存確認ダイアログの操作例です.保存確認のダイアログで,保存しないで自動的に閉じるようにします.ダイアログ上のコントロールを操作する場合,GetDlgItem 関数で総対象のコントロールの ID 値がらウィンドウハンドルを取得して操作することがあります.しかし,メモ帳の保存確認のダイアログのボタンには ID が設定されていません.そこで,ボタンのアクセラレータキーを送信して処理しています.



図10
メモ帳の保存確認ダイアログを操作
  • 保存しないで閉じる
    そのために,アクセラレータキー [N] を送信する

リスト6
ショートカットキー送信操作用ユニットのテスト.メモ帳の保存確認ダイアログを操作
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  ショートカットキー送信テスト
//  メモ帳の保存確認ダイアログの自動操作
//
//  動作確認方法
//  (1) メモ帳を起動する
//  (2) このプログラムの Button1 をクリックする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
  LDlgWnd    : HWND;
  LText      : string;
begin
  // このフォームを表示する前にアクティブだったウィンドウのハンドルを取得
  LTargetWnd := GetNextWindow(Handle, GW_HWNDNEXT);
  while not IsWindowVisible(LTargetWnd) do begin
    LTargetWnd := GetNextWindow(LTargetWnd, GW_HWNDNEXT);
  end;


  LText := 'メモ帳の保存確認ダイアログの操作' + sLineBreak
         + '数秒後に自動的に閉じます。';
  SendInputText(LTargetWnd, LText);
  // 少し時間をおいて文字列が送信されたこを確認
  Sleep(7000);

  // ウィンドウを閉じる
  SendShortCutKeyEx(LTargetWnd, 'Alt + F4');

  // 保存確認のダイアログのウィンドウハンドルを取得して,そのウィドウを自動で閉じる
  // メモ帳の保存確認のダイアログのボタンには ID が設定されていないので
  // ボタンのアクセラレータキー (保存しない(N)) で処理
  Sleep(100);
  LDlgWnd := GetForegroundWindow;
  if FindWindow('#32770', nil) = LDlgWnd then begin
    SendShortCutKeyEx(LDlgWnd, 'N');
  end;
end;


keybd_event 関数,SendInput 関数で送信したキー操作のメッセージは,フォーカスがあるコントロールが受け取ります.メモ帳の保存確認のダイアログの起動時は [保存する(S)] のボタンにフォーカスがあります.したがって,以下のように [TAB]キー でフォーカスを移動して,[Enter] キーを押せば同じ結果となります.


  Sleep(100);
  LDlgWnd := GetForegroundWindow;
  if FindWindow('#32770', nil) = LDlgWnd then begin
    SendShortCutKeyEx(LDlgWnd, 'TAB');
    SendShortCutKeyEx(LDlgWnd, 'Enter');
  end;


メモ帳のテキストを保存して終了する場合は,例えば次のようになります.


implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  ショートカットキー送信テスト
//  メモ帳の保存操作
//
//  動作確認方法
//  (1) メモ帳を起動する
//  (2) このプログラムの Button1 をクリックする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd  : HWND;
  LSaveDlgWnd : HWND;
  LSavePath   : string;
  LText       : string;
begin
  // このフォームを表示する前にアクティブだったウィンドウのハンドルを取得
  LTargetWnd := GetNextWindow(Handle, GW_HWNDNEXT);
  while not IsWindowVisible(LTargetWnd) do begin
    LTargetWnd := GetNextWindow(LTargetWnd, GW_HWNDNEXT);
  end;


  LSavePath := ExtractFilePath(Application.ExeName) + 'テスト文書.txt';

  LText := 'メモ帳の保存操作' + sLineBreak
         + '数秒後,自動的に保存して閉じます。' + sLineBreak;
  SendInputText(LTargetWnd, LText);
  // 少し時間をおいて文字列が送信されたこを確認
  Sleep(5000);


  // 上書き保存確認のダイアログが現れないようにする
  // 全て名前を付けて保存にする
  if FileExists(LSavePath) then begin
    DeleteFile(LSavePath);
  end;

  // 名前を付けて保存
  // BOM なしの UTF-8 での保存となる
  SendShortCutKeyEx(LTargetWnd, 'Shift + Ctrl + S');
  Sleep(100);
  LSaveDlgWnd := GetForegroundWindow;
  SendShortCutKeyEx(LSaveDlgWnd, 'Delete');
  SendInputText(LSaveDlgWnd, LsavePath);
  SendShortCutKeyEx(LSaveDlgWnd, 'Enter');

  // 保存したら閉じる
  sleep(100);
  SendShortCutKeyEx(LTargetWnd, 'Alt + F4');
end;




08_テンキーの数字キー送信


末尾に Ex がない SendShortCutKey 手続きを使用すると,テンキーの数字キーとテンキーでない数字キーを区別して送信できます.Delphi では,OnKeyDown イベントで仮想キーコードの値を取得できます.その動作確認用のテストです.



図11
Button1 クリック
  • テンキーではない数字 [3] のキーの仮想キーコードを送信
図12
Button2 クリック
  • テンキーの数字 [3] のキーの仮想キーコードを送信
  • OnKeyDown イベントで仮想キーコードが取得できている

このテストプログラムでは,Edit1 (TEdit) に直接キーボードからキーを押してテストできるようになっています.
[Print Screen] キーは,OnKeyDown イベントでは捕捉できません.キーフックを使用すれば捕捉できます.



図13
[A] のキーを押した結果
図14
[Home] キーを押した結果
図15
] のキーの場合
  • ] キーの押下を送信するには,文字の ASCII コード $5D ではなく,$DD を送信する
  • ] の文字があるキーの仮想キーコードは $DD

リスト7
テンキーではない数値キーとテンキーの数値キーの送信
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  テンキーではない数値キーの仮想キーコードを送信
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  Edit1.Text := '';
  Edit1.SetFocus;

  LTargetWnd := Handle;
  SendShortCutKey(LTargetWnd, Char('3'));
end;

//=============================================================================
//  テンキーの数値キーの仮想キーコードを送信
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  Edit1.Text := '';
  Edit1.SetFocus;

  LTargetWnd := Handle;
  // Char(vkNumpad3) も使用可能だが,uses に System.UITypes が必要
  SendShortCutKey(LTargetWnd, Char(VK_NUMPAD3));
end;

//=============================================================================
//  表示をクリア
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Edit1.Text := '';
  Edit1.SetFocus;
end;

//=============================================================================
//  Edit1 の OnKeyDown イベント
//  仮想キーコードの値が取得できる
//  Shift キーの押下状態には影響されない (仮想キーコードはキーのコードなので)
//=============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  LAsciiCode : WORD;
  LText      : string;
begin
  // 仮想キーコードの値を ASCII コードの値に変換
  LAsciiCode := LoWord(MapVirtualKey(Key, MAPVK_VK_TO_CHAR));

  LText := 'KEY DOWN  :  $' +IntToHex(Key, 4) + '   ' + Char(Key) + '   ' + Char(LAsciiCode);
  Memo1.Lines.Add(LText);
end;

//=============================================================================
//  Edit1 の OnKeyPress イベント
//  文字として取得できる
//  テンキーの数字キーとの区別はない
//=============================================================================
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
var
  LText : string;
begin
  LText := 'KEY PRESS :  $' + IntToHex(Ord(key), 4) + '   ' + Key;
  Memo1.Lines.Add(LText);
end;


仮想キーコードの定数名の文字列を使用すれば,SendShortCutKeyEx 関数でも動作します.


//=============================================================================
//  テンキーの数値キーの仮想キーコードを送信
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  Edit1.Text := '';
  Edit1.SetFocus;

  LTargetWnd := Handle;
  // 先頭の vk を省略した Numpad3 でも可
  SendShortCutKeyEx(LTargetWnd, 'vkNumpad3');
end;




09_TShortCut 型のショートカットキーの送信


アプリケーションでは TPopupMenu あるいは TActionList や TActionManager を使用してメニューを実装することがあります.それらのメニューに割り当てたショートカットキーは,各メニュー項目の TMenuItem.ShortCut プロパティで取得できます.この値は TShortCut 型です.そのショートカットキーを実行したい場合は,そのショートカットキーの文字列を作成しなくても,SendShortCutKeyEx 関数の引数に,TShortCut 型の値を渡すことができます.
これは,自アプリに限って使用可能な方法です.



procedure TForm1.Button1Click(Sender: TObject);
var
  LShortCut : TShortCut;
begin
  Edit1.SetFocus;
  Edit1.SelStart := Length(Edit1.Text);

  // Clipboard の使用には uses に Vcl.Clipbrd が必要
  Clipboard.AsText := 'テスト文字列';

  // TShortCut の値を設定
  // vkV 等の定数の使用には uses に System.UITypes が必要
  LShortCut :=  ShortCut(vkV, [ssCtrl]);
  SendShortCutKeyEx(LShortCut);
end;




10_拡張キーの判定


ショートカットキー送信操作用ユニットでは,拡張キーの判定を自動で行っていますが,押下したキーが拡張キーであるかは,WM_KEYDOWN メッセージで調べることができます.



図16
WM_KEYDOWN メッセージで拡張キーを判定
  • $41 は [A] キー
  • $23 は [END] キー

//-----------------------------------------------------------------------------
//  TEdit の継承クラス TEditEx の WM_KEYDOWN メッセージ処理
//
//  wParam : 非システムキーの仮想キーコード
//  lParam : 24 ビットが拡張キーのフラグ
//-----------------------------------------------------------------------------
procedure TEditEx.WMKeyDown(var Message: TMessage);
var
  LIsExtended : Boolean;
  LText       : string;
begin
  inherited;

  LIsExtended := (Message.LParam and $1000000) <> 0;
  LText := '$' + IntToHex(Message.WParam, 4);
  if LIsExtended then LText := LText + '   EXTENDED';

  Form1.Memo1.Lines.Add(LText);
end;


このようなメッセージは,TApplicationEvent コンポーネント ([Additional] タブ) の OnMessage イベントでも処理することができます.


//=============================================================================
//  ApplicationEvents1 (TApplicationEvent コン) の OnMessage イベント処理
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LIsExtended : Boolean;
  LText       : string;
begin
  if (Msg.hwnd = Edit1.Handle) and (Msg.message = WM_KEYDOWN) then begin
    LIsExtended := (Msg.lParam and $1000000) <> 0;
    LText := '$' + IntToHex(Msg.WParam, 4);
    if LIsExtended then LText := LText + '   EXTENDED';

    Memo1.Lines.Add(LText);
  end;
end;




11_SendInput 関数によるショートカットキーの送信操作


Windows API の SendInput 関数も,アクティブなウィンドウに対してキー操作ができます.
マイクロソフトでは,Keybd_event 関数の代りに SendInput 関数の使用を勧めています.plSendShortCut.pas に関数内の手続きとして Keyevent があります.その手続きのコードを以下のコードに置き換えると,SendInput 関数による仮想キーコードの送信になります.
この変更をしても,ASCII 文字送信用の SendKeysASCII 関数は,keybd_event 関数を使用する処理のままです.



リスト8
SendInput 関数を使用して仮想キーコードを送信
  procedure Keyevent(AByte: Byte; AFlag: Cardinal);
  const
    // 拡張キーの判定用
    LExtendKys = [$21..$2F, $5B..$5F, $A6..$B7, $90];
  var
    LdwFlag : Cardinal;
    LInput  : TInput;
  begin
    if AByte in LExtendKys then begin
      LdwFlag := AFlag or KEYEVENTF_EXTENDEDKEY;
    end else begin
      LdwFlag := AFlag;
    end;

    LInput.Itype          := INPUT_KEYBOARD;
    LInput.ki.wVk         := AByte;
    LInput.ki.wScan       := MapVirtualKey(AByte, 0);
    LInput.ki.dwFlags     := LdwFlag;
    LInput.ki.time        := 0;
    LInput.ki.dwExtraInfo := 0;

    SendInput(1, LInput, SizeOf(TInput));
  end;


SendInput 関数を使用してショートカットキーを送信する例です.[Ctrl] + [V} (貼り付け) を送信します.
plSendShortCut.pas ても,Keyevent 手続きをこのように使用しています.



図17
SendInput 関数を使用してショートカットキーを送信
  • メモ帳にクリップボードのテキストを貼り付け

リスト9
SendInput 関数によるショートカットキーの送信
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Vcl.Clipbrd;

{$R *.dfm}

  //---------------------------------------------------------------------------
  //  SendInput 関数を使用してキー操作を送信
  //---------------------------------------------------------------------------
  procedure Keyevent(AByte: Byte; AFlag: Cardinal);
  const
    // 拡張キーの判定用
    LExtendKys = [$21..$2F, $5B..$5F, $A6..$B7, $90];
  var
    LdwFlag : Cardinal;
    LInput  : TInput;
  begin
    if AByte in LExtendKys then begin
      LdwFlag := AFlag or KEYEVENTF_EXTENDEDKEY;
    end else begin
      LdwFlag := AFlag;
    end;

    LInput.Itype          := INPUT_KEYBOARD;
    LInput.ki.wVk         := AByte;
    LInput.ki.wScan       := MapVirtualKey(AByte, 0);
    LInput.ki.dwFlags     := LdwFlag;
    LInput.ki.time        := 0;
    LInput.ki.dwExtraInfo := 0;

    SendInput(1, LInput, SizeOf(TInput));
  end;

//=============================================================================
//  SendInput 関数によるショートカットキーの送信操作
//
//  動作確認方法
//  (1) メモ帳を起動
//  (2) 本プログラムの Button1 をクリック
//
//  uses に Vcl.Clipbrd が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
  LText      : string;
begin
  // このフォームを表示する前にアクティブだったウィンドウのハンドルを取得
  LTargetWnd := GetNextWindow(Handle, GW_HWNDNEXT);
  while not IsWindowVisible(LTargetWnd) do begin
    LTargetWnd := GetNextWindow(LTargetWnd, GW_HWNDNEXT);
  end;
  SetForegroundWindow(LTargetWnd);

  Clipboard.AsText := 'Send Input 関数でショートカッキー送信';

  Keyevent(VK_CONTROL, 0);
  Keyevent(Byte('V'),  0);

  Keyevent(VK_CONTROL, KEYEVENTF_KEYUP);
  Keyevent(Byte('V'),  KEYEVENTF_KEYUP);
end;

end.




12_ASCII 文字コードと仮想キーコードの値


keybd_event 関数が送信するのは仮想キーコードです.上のコードで SendUnput 関数が送信するのも仮想キーコードです.
絵文字パネルを表示するショートカットキーは [Win] + [.] ですが,次のコードを実行しても絵文字パネルは表示しません.Edit1 のテキストが選択状態であると,それを削除する動作になります.


procedure TForm1.Button1Click(Sender: TObject);
begin
  // 絵文字パネルを表示するつもり.絵文字パネルは表示しない
  // 文字 . の ASCII コードは $2E.これは仮想キーコード VK_DELETE の値
  Edit1.SetFocus;
  SetForegroundWindow(Handle);

  Keyevent(VK_LWIN,   0);
  Keyevent(Byte('.'), 0);

  Keyevent(VK_LWIN,   KEYEVENTF_KEYUP);
  Keyevent(Byte('.'), KEYEVENTF_KEYUP);
end;


Byte('.') の値は $2E です.これは文字 . の ASCII コードです.これは仮想キーコードの定数 VK_DELETE の値となっています.[.] キーの仮想キーコードの値は $BE です.次のコードを実行すると絵文字パネルを表示します.[.] キーは [>] キーと同じです.視覚的にわかりやすい [Win] + [>] の表記を使用することもあります.

前述のテンキーの数字キー送信のサンプルを使用すると,仮想キーコードの値を調べることができます.ショートカットキー送信操作用ユニットに実装している AsciiToVirtualKey 関数を使用すると,ASCII コードから仮想キーコードの値が取得できます.
数字とアルファベットの大文字の ASCII コードは,仮想キーコードの値と同じです.変換は必要ありません.ASCII コードも仮想キーコードも 1 バイトの値ですから,この場合の数字とアルファベットと言うのは半角文字のことです.


procedure TForm1.Button2Click(Sender: TObject);
begin
  // 絵文字パネルを表示
  // [.] ([>]) キーの仮想キーコードは $BE
  Edit1.SetFocus;
  SetForegroundWindow(Handle);

  Keyevent(VK_LWIN, 0);
  Keyevent($BE,     0);

  Keyevent(VK_LWIN, KEYEVENTF_KEYUP);
  Keyevent($BE,     KEYEVENTF_KEYUP);
end;

end.


uses に System.UITypes を追加すると,vkLWin や記号キーの仮想キーコードの定数名 vkPeriod (値は VK_PERIOD と同じ) 等が使用できます.それらの定数名を使用すると,仮想キーコードの値を調べる必要がありません.
これらの定数は,Char(vkPeriod) のように Char 型の値に変換すれば,ショートカットキー送信操作用ユニットの手続きでも使用可能です.
Delphi XE5(UP2) Pro の Winapi.Windows には VK_PERIOD の定義はありません.



procedure TForm1.Button3Click(Sender: TObject);
begin
  // 絵文字パネルを表示
  Edit1.SetFocus;
  SetForegroundWindow(Handle);

  Keyevent(vkLWin,   0);
  Keyevent(vkPeriod, 0);

  Keyevent(vkLWin,   KEYEVENTF_KEYUP);
  Keyevent(vkPeriod, KEYEVENTF_KEYUP);
end;


図18
絵文字パネルを表示
  • ショートカットキーは [Win] + [>]
  • 絵文字パネルは,入力フォーカスがあるコントロールの位置に表示される
  • Delphi XE5 の TEdit ではカラー絵文字にならない




13_SendInput 関数による文字列の送信


SendInput 関数の第 2 引数は TInput レコード型の配列です.上のサンプルでは扱うキー操作が 1 つなので配列は定義していません.
TInput 型の TInput.ki.wVk の値を 0 にして,Input.ki.dwFlags に KEYEVENTF_UNICODE を含めめると Unicode の文字の送信となります.Delphi 2009 以降の Dlphi の文字は Unicode ですからそのまま利用できます.以下のサンプルでは,文字列を引数にした手続きを作成してテストしています.

SendInput 関数による文字列の送信では,受信側の入力コントロールの日本語 IME が ON の場合,送信結果は未確定文字列の状態になります.



図19
SendInput 関数を使用して文字列を送信
  • メモ帳に文字列を送信した結果
  • Unicode の文字列が扱える

リスト10
SendInput 関数による文字列の送信
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  SendInput 関数を使用した文字列の送信
//  文字の途中で仮想キーコード必要ないと仮定
//  文字列として送信する手続きにしてしまう
//
//  TInput.ki.dwFlags に KEYEVENTF_UNICODE を含め
//  TInput.ki.wVk := 0 にする必要があるので仮想キーコードは使用できない
//-----------------------------------------------------------------------------
procedure SendInputText(AText: string);
var
  LInput : array [0..1] of TInput;
  LIndex : Integer;
begin
  // 改行文字変換
  AText := AdjustLineBreaks(AText, tlbsLF);

  for LIndex := 1 to Length(AText) do begin
    // キーを押す設定
    LInput[0].Itype          := INPUT_KEYBOARD;
    LInput[0].ki.wVk         := 0;
    LInput[0].ki.wScan       := Ord(AText[lIndex]);
    LInput[0].ki.dwFlags     := KEYEVENTF_UNICODE;
    LInput[0].ki.time        := 0;
    LInput[0].ki.dwExtraInfo := 0;

    // キーを離す設定
    LInput[1].Itype          := INPUT_KEYBOARD;
    LInput[1].ki.wVk         := 0;
    LInput[1].ki.wScan       := Ord(AText[lIndex]);
    LInput[1].ki.dwFlags     := KEYEVENTF_KEYUP or KEYEVENTF_UNICODE;
    LInput[1].ki.time        := 0;
    LInput[1].ki.dwExtraInfo := 0;

    SendInput(2, LInput[0], SizeOf(TInput));
  end;
end;

//=============================================================================
//  SendInput 関数を使用して文字を送信
//
//  動作確認方法
//  (1) メモ帳を起動
//  (2) 本プログラムの Button1 をクリック
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  // このフォームを表示する前にアクティブだったウィンドウのハンドルを取得
  LTargetWnd := GetNextWindow(Handle, GW_HWNDNEXT);
  while not IsWindowVisible(LTargetWnd) do begin
    LTargetWnd := GetNextWindow(LTargetWnd, GW_HWNDNEXT);
  end;
  SetForegroundWindow(LTargetWnd);

  SendInputText('動作確認 - Delphi XE5');
end;

end.


[備考]
keybd_event 関数,SendInput 関数を使用した送信では,アクティブなウィンドのフォーカスがあるコントロールがメッセージを受け取ります.文字列の送信と取得用の WM_SETTEXT メッセージや WM_GETTEXT メッセージでは,操作対象のウィンドウコントロールに対してメッセージを送信します.例えはメモ帳の場合,メモ帳自身のウィンドウではなく,メモ帳の編集ウィンドウに対してメッセージを送信します.





14_メッセージキューと WM_KEYDOWN, WM_KEYUP メッセージ


WM_KEYDOWN, WM_kEYUP メッセージを使用してキー操作を行う場合の参考サンプルでが,keybd_event 関数や SendInput 関数を使用する場合にも参考となるサンプルです.

キー操作をすると WM_KEDOWN 等のメッセージが発生します.マウスのボタンをクリックすると WM_LBUTTONDOWN 等とのメッセージが発生します.これらのメッセージは,メッセージキューと呼ばれるバッファに溜められ,処理可能な状況になると実行される仕組みになっています.時間がかかる処理の最中に,ウィンドウのキャプションバーの右端の [X] ボタンをクリックしても閉じないで,処理が終わると閉じることがあります.これがメッセージキューの動作です.

下に示すサンプルは,仮想キーコードとして TMemo に文字列を送信した後,TMemo に END という文字を表示します.しかし,実際には END という文字を先に表示します.



図20
キー操作の前に END の文字表示される
  • 仮想キーコードは END の文字描画前に送信されている
  • メッセージキューの処理は,Button1 クリックのイベント終了後 (スコープを抜けると) 実行される

今度は,仮想キーコードを送信した後に Application.ProcessMessages を実行してみます.すると,送信した文字列が先に表示されます.Application.ProcessMessages はメッセージキューのメッセージを処理します.

他のアプリにキーやマウスの操作を送信すると,メッセージはそのアプリのメッセージキューに溜まります.アプリのメッセージキューは,別のアプリからは操作できません.
Application.ProcessMessages を実行しても他のアプリには何の影響も与えません.本ページのサンプルでは Sleep 手続きを使用してタイムラグを設けています.Sleep 手続きは,自プロセスの実行を停止しますが,他のアプリの実行が停止することはありません.



図21
キー送信後 Application.ProcessMessages を実行
  • キーバッファの内容が直ちに実行される
  • その後に END の文字が表示される

リスト11
送信したキーコードの処理のタイミング
implementation

uses
  plSendShortCut;

{$R *.dfm}

//=============================================================================
//  メッセージキューの動作確認
//  ショートカットキー送信操作用ユニットの SendKeysASCII を使用
//  メッセージキュー (キーバッファ) の処理はスコープを抜けた時に行われる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  LTargetWnd := Handle;

  Memo1.SetFocus;
  Memo1.Lines.Clear;
  Memo1.Font.Size := 18;

  SendKeysASCII(LTargetWnd, 'Post Keys Test');
  Memo1.Lines.Add('END');
end;

//=============================================================================
//  メッセージキューの動作確認
//  ショートカットキー送信操作用ユニットの SendKeysASCII を使用
//  Application.ProcessMessages で/メッセージキューの処理が可能
//  ただし自スレッドの場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  LTargetWnd := Handle;

  Memo1.SetFocus;
  Memo1.Lines.Clear;
  Memo1.Font.Size := 18;

  SendKeysASCII(LTargetWnd, 'Post Keys Test');
  Application.ProcessMessages;
  Memo1.Lines.Add('END');
end;


WM_SETTEXT メッセージで文字列を送信する例です.SendMessage 関数で送信した場合は,送信した文字列が表示されて,END の文字がその後に表示されています.SendMessage 関数で送信したメッセージは直ちに処理されます.
PostMessage 関数で送信すると,送信した文字列は表示されません.PostMessage 関数は,メッセージをメッセージキューにポストしますが,WM_SETTEXT による文字列の送信ではキーやマウスの操作はありません.メッセージキューは何も処理しません.
このように,SendMessage 関数を使用すべきメッセージは SendMessage 関数を使用します.PostMessage 関数を使用すべきメッセージは PostMessage 関数を使用します.そうしないと期待する処理にはなりません.

keybd_event 関数,または SendUnput 関数は,キー操作のメッセージを送信先のメッセージキューにポストします.受信側は,そのメッセージキューのメッセージを処理します.



図22
Button1 クリック
  • SendMessage 関数で WM_SETTEXT メッセージを送信
図23
Button2 クリック
  • PostMessage 関数で WM_SETTEXT メッセージを送信
  • 送信した文字列は表示されない

//=============================================================================
//  メッセージキューの動作確認
//  WM_SETTEXT メッセージによるテキストの送信
//  SendMessage の場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBuffer : PChar;
begin
  Memo1.Lines.Clear;
  Memo1.Font.Size := 18;

  LBuffer := 'SendMessage WM_SETTEXT';
  SendMessage(Memo1.Handle, WM_SETTEXT, 0, LPARAM(LBuffer));
  Memo1.Lines.Add('END');
end;

//=============================================================================
//  メッセージキューの動作確認
//  WM_SETTEXT メッセージによるテキストの送信
//  SendMessage で送信すべきメッセージを PostMessage で送信した場合
//  何も出力しない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LBuffer : PChar;
begin
  Memo1.Lines.Clear;
  Memo1.Font.Size := 18;

  LBuffer := 'PostMessage WM_SETTEXT';
  PostMessage(Memo1.Handle, WM_SETTEXT, 0, LPARAM(LBuffer));
  Memo1.Lines.Add('END');
end;


[Ebter] キーの例です.[Enter] キーの仮想キーコード VK_RETURN を送信します.
PostMessage 関数を使用すると,指定した位置で [Enter] キーの動作をして改行します.SendMessage 関数で送信した場合は [Enter] キーの操作として動作しません.SnedMessage 関数はメッセージキューにキー操作のメッセージをポストしません.メッセージキューにメッセージがないのでキー操作は発生しません.

このサンプルでは WM_KEYUP を使用ていませんが,LParam の値で WM_KEYUP と同じ設定をしています.



図24
Button1 クリック
  • PostMessage 関数で送信
  • キー操作のメッセージが処理される
  • 指定した位置に改行が入る
図25
Button2 クリック
  • SendMessage 関数で送信
  • キー操作のメッセージは処理されない
  • OnKeyDown イベントで,受信したキーの情報は取得可能

//=============================================================================
//  メッセージキューの動作確認
//  WM_KEYDOWN, WM_KEYUP による仮想キーコード VK_RETURN の送信
//  PostMessage で送信した場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.SetFocus;
  Memo1.Lines.Text := 'KeyDownUp - PostMessage';
  Memo1.SelStart := Length('KeyDownUp - ');

  PostMessage(Memo1.Handle, WM_KEYDOWN, VK_RETURN, $001C0001);
  PostMessage(Memo1.Handle, WM_KEYDOWN, VK_RETURN, $C01C0001);
end;

//=============================================================================
//  メッセージキューの動作確認
//  WM_KEYDOWN, WM_KEYUP による仮想キーコード VK_RETURN の送信
//  PostMessage で送信すべきメッセージを SendMessage で送信した場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.SetFocus;
  Memo1.Lines.Text := 'KeyDownUp - SendMessage';
  Memo1.SelStart := Length('KeyDownUp - ');

  SendMessage(Memo1.Handle, WM_KEYDOWN, VK_RETURN, $001C0001);
  SendMessage(Memo1.Handle, WM_KEYDOWN, VK_RETURN, $C01C0001);
end;


キー [A] を押すと小文字 a となります.これが通常の結果です.大文字の A にするには [Shift] キーを押した状態で [A] のキーを押します.しかし,以下のコードを実行しても [Shift] キー押下を伴う [Shift] + [A] の操作にはなりません.


  PostMessage(Edit1.Handle, WM_KEYDOWN, VK_SHIFT, $002A0001);
  PostMessage(Edit1.Handle, WM_KEYDOWN, Ord('A'), $001E0001);

  PostMessage(Edit1.Handle, WM_KEYUP, Ord('A'), $C01E0001);
  PostMessage(Edit1.Handle, WM_KEYUP, VK_SHIFT, $C02A0001);


[Shift] キーや [Ctrl] キーの操作を伴う場合は,その状態を SetKeyboardState 関数等を使用して処理する必要があります.[F10] キーと,[Alt] キー等を含むシステム関係のキー操作の送信には WM_SYSKEYDOWN と WM_SYSKEYUP メッセージを使用します.
そのような処理をコーディングするのが面倒であれば,本項の最初のリンクの記事にある PostKeyExHWND 手続きを利用することができます.その例です.PostKeyExHWND の最後の引数は拡張キーのフラグです.このサンプルでは,[Shift] キーの押下状態,[Caps Lock] の状態に関係なく大文字の A を出力します.

このコードで他のアプリへ送信した場合,送信先のウィンドウコントロールには小文字の a が出力されます.他のアプリに WM_KEYDOWN, WM_KEYUP メッセージで仮想キーコードを送信する場合,[Shift] + [A] のようなキーの組み合わせは送信できません.自アプリで SetKeyboardState 関数等を実行しても,他のアプリに対する操作とはなりせん.アクティブなウィンドウに対してであれば,keybd_event 関数,あるいは SendInput 関数を使用してキーの組み合わせの処理が可能です.

この PostKeyExHWND を使用しても,仮想キーコードが VK_SNAPSHOT である [Print Screen] キーの送信は機能しません.PostKeyExHWND は WM_KEYDOWN, WM_KEYUP メッセージを使用する送信です.WM_KEYDOWN, WM_KEYUP メッセージによる送信では,VK_SNAPSHOT, VK_NUMLOCK, VK_LWIN, VK_RWIN, VK_TAB 等のシステム関係キーの送信は機能しません.



図26
[Shift] や [Ctrl] キーの送信操作

//============================================================================= 
//  [Shift] + [A] 等のキーの組み合わせの送信
//  PostKeyExHWND を利用する方法
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LVirtualKey : Cardinal;
begin
  Edit1.Text := '';

  LVirtualKey := Ord('A');
  PostKeyExHWND(Edit1.Handle, LVirtualKey, [ssShift], False);
end;   


WM_CHAR メッセージを使用すると文字の送信ができます.WM_CHAR メッセージは,仮想キーコードの送信用ではありません.WParam パラメータには送信する文字の文字コード (コードポイント) の値を渡します.制御文字としての ASCII コードも使用てきますが,ASCII コードには,矢印キー,[Shift], [Ctrl], [Home],[End] 等のキーに相当する値はありません.仮想キーコードの送信では $DD を送信すると「 ]」 の文字になります.WM_CHAR は文字の送信ですから,ASCII コードの $5D を送信すると文字「]」になります.Ord(']') の値は $5D です.
WM_CHAR メッセージの送信には SendMessage 関数を使用します.WM_CHAR による文字送信は,送信先のウィンドウをアクティブにする必要はありません.

仮想キーコードの送信とは,どのキーを押して離すのかという情報送信することです.文字の送信は,文字通り「文字」の送信です.



図27
WM_CHAR メッセージによる文字の送信例
  • WM_CHAR メッセージは,仮想キーコードの送信用ではない

//============================================================================
//  WM_CHAR による文字の送信
//  WM_CHAR メッセージは仮想キーコードの送信用ではない
//
//  WParam は文字のコード
//  ASCII コードも使用できるが,このようなテストで目で見て確認可能なのは,  
//  $0D (VK_RETURN と同じ値), $0A (VK_LINEFEED と同じ値), $09 (VK_TAB と同じ値),
//  数字,アルファベット,記号文字等
//  ASII コードには方向キー,[Shift],[Ctrl], [Home], [End] 等に相当する値はない
//============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.SetFocus;
  Memo1.Lines.Text := 'WM_CHAR - SendMessage';
  Memo1.SelStart := Length('WM_CHAR - ');

  SendMessage(Memo1.Handle, WM_CHAR, $0A, 1);

  SendMessage(Memo1.Handle, WM_CHAR, Ord('改'), 3);
  SendMessage(Memo1.Handle, WM_CHAR, Ord('行'), 1);
  SendMessage(Memo1.Handle, WM_CHAR, Ord(' '), 1);

  Memo1.Lines.Add('END');
end;


[備考]
SendMessage 関数でも仮想キーコードの送信はできます.ただし,SendMessage 関数を使用した仮想キーコードの送信では,ここまでのテストで確認したように,キー操作としてのメッセージをメッセージキューにポストしません.

SendMessage 関数で仮想キーコードを送信した場合でも,メッセージを受信するコントロールの OnKeyDown イベント,OnKeyUp イベント,OnKeyPress イベントが発生します.また,WM_KEYDWON メッセージも WM_KEYUP メッセージも受信できます.それらのイベントや受信メッセージで何らかの処理を実行することができます.LParam を 0 にして WM_KEYDOWN, WM_KEYUP メッセージを送信した場合,OnKeyPress イベントは 2 回発生します.

他のアプリへ SendMessage 関数で仮想キーコードを送信した場合は,受信側の WM_KEYDOWN, WM_KEYUP メッセージは発生しません.





15_WM_KEYDOWN と WM_KEYUP の LParam の値


Windows のメッセージの WParam と LParam の値は,PostMessage 関数と SendMessage 関数では,それぞれ 第 3 引数と第 4 引数 (最後の引数) で指定します.
WM_KEYDOWN と WM_KEYUP の WParam の値は仮想キーコードです.LParam はスキャンコードと拡張キーのフラグと各種のフラグを指定します.

[Enter] キーに相当する ASCII 文字は復帰 (CR) ですが,その値は $0D です.[Enter] キーの仮想キーコードも $0D て,スキャンコードは $1C です (テンキーの [Enter] キーは $E01C).下図は LParam の値を 2 進数で表示しています.赤枠のビットがスキャンコードのデータです.



図28
LParam の値の 2 進数表示
  • マイクロソフトの記事ではビット位置での説明になっている

WM_KEYDOWN メッセージと WM_KEYUP メッセージの LParam の値は,下図の下のコードで生成できます.下図は実際の使用例です.
最初の TEdit に ASCII 文字あるいは仮想キーコードの定数名を入力して Button1 をクリックします.このサンプルでは,添付の plvkEnum ユニット内の関数 vkTextToVirtualKey を使用して仮想キーコードの定数名の文字列から仮想キーコードを計算します.取得した仮想キーコードを Edit2 (2 番目の TEdit) に WM_KEYDOWN と WM_KEYUP メッセージで送信します.



図29
LParam パラメータの設定
  • キー [A] の仮想キーコードを送信する例
図30
VK_HOME の LParam パラメータの値
  • VK_HOME は拡張キーなので LParam に拡張キーのフラグを追加
図31
VK_HOME の LParam パラメータの値
  • LParam に拡張キーのフラグがない場合
  • KeyName が [Num 7] になっている
    テンキーの [7] キーには [Home] の刻印がある
図32
VK_LINEFEED の LParam の値
  • VK_LINEFEED (LF) にはスキャンコードはない
  • キーボードには VK_LINEFEED に相当するキーはない

添付の plvkEnum ユニットには,仮想キーコードの定数名を文字列として取得する関数を実装しています.その使用例です.Edit1 に仮想キーコードの値を入力して Button2 をクリックします.
VK_ 形式の定義は Winapi.Windows.pas 内で定義されていますが,VK_6 等の定義はありません.下図の $DD に相当する定数名は VK_ 形式では VK_OEM_6 となっています.アンダースコアがない vk 形式は,米国標準キーボードにおける定義となっていて,System.UITypes.pas にリストがあります.日本語のキーボートとの違いは,ASCII の記号文字に相当するキーの一部てす.



図33
仮想キーコードの定数名を文字列で取得

リスト12
WM_KEYDOWN, WM_KEYUP メッセージで使用する LParam の計算方法
implementation

uses
  plvkEnum;

{$R *.dfm}

 const
    // 拡張キーの判定用
    LExtendKys = [$21..$2F, $5B..$5F, $A6..$B7, $90];

//=============================================================================
//  LParam の計算方法
//  Edit1 (上の TEdit) に仮想キーボード名または ASCII 文字を入力してクリック

//  ASCII 文字の数字と英大文字は ASCII コードと仮想キーコードが同じ
//  受信側の文字は [Shift] キーが押されていない時と同じ小文字になる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LVirtualKey : Cardinal;
  LScanCode   : Cardinal;
  LParamDown  : Cardinal;
  LParamUp    : Cardinal;
  LKeyName    : array [0..31] of Char;
begin
  Edit2.Text := '';
  Edit2.SetFocus;

  LVirtualKey := vkTextToVirtualKey(Trim(Edit1.Text));

  // スキャンコードを追加
  LScanCode  := MapVirtualKey(LVirtualKey, 0);
  LParamDown := $00000001 or (LScanCode shl 16);

  // 拡張キーの判定と使用可否
  if CheckBox1.Checked and (LVirtualKey in LExtendKys) then begin
   LParamDown := LParamDown or $01000000;
 end;
 LParamUp := LParamDown or $C0000000;

  // メッセージをポスト
  PostMessage(Edit2.Handle, WM_KEYDOWN, LVirtualKey, LParamDown);
  PostMessage(Edit2.Handle, WM_KEYUP,   LVirtualKey, LParamUp);


  // 取得した LParam の値を表示
  Memo1.Lines.Clear;
  FillChar(LKeyName, SizeOf(LKeyName), #0);
  GetKeyNameText(LParamDown, LKeyName, SizeOf(LKeyName));
  Memo1.Lines.Add('KeyName : [' + LKeyName + ']');
  Memo1.Lines.Add('LVirtualKey :  $' + IntToHex(LVirtualKey, 2));
  Memo1.Lines.Add('LParamDown  :  $' + IntToHex(LParamDown, 8));
  Memo1.Lines.Add('LParamUp    :  $' + IntToHex(LParamUp,   8));

  Edit1.SetFocus;
end;

//=============================================================================
//  仮想キーコードの値から仮想キーコードの定数名の文字列を取得
//  plvkEnum ユニットの関数のテスト
//
//  Edit2 に仮想キーコードの数値を入力してクリック
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LVirtualKey : Cardinal;
  LvkText     : string;
begin
  Edit2.Text := '';
  Edit2.SetFocus;

  LVirtualKey := StrToIntDef(Trim(Edit1.Text), $FFFF);
  if LVirtualKey = $FFFF then begin
    MessageBox(Handle, '数値に変換できません。', '失敗', MB_ICONINFORMATION);
    Exit;
  end;


  LvkText   := VirtualKeyToVKText(LVirtualKey);
  if LvkText = '' then Exit;

  Memo1.Lines.Clear;
  Memo1.Lines.Add(LvkText);

  Edit1.SetFocus;
end;


LParam パラメータの設定をしないで,0 にした場合は下図のような結果になります.
このコードの WM_KEYUP メッセージの送信では,LParam にキーを離すという情報がありません.WM_KEYDOWN, WM_KEYUP もキーを押すだけのメッセージになっています.



図34
LParam パラメータを設定した場合
図35
LParam パラメータの設定がない場合
  • LParam の値を 0 にした場合
  • 同じ仮想キーコードが 2 回送信されている

//=============================================================================
//  仮想キーコードを送信
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit1.Text := '';

  PostMessage(Edit1.Handle, WM_KEYDOWN, Ord('A'), $001E0001);
  PostMessage(Edit1.Handle, WM_KEYUP,   Ord('A'), $C01E0001);
end;

//=============================================================================
//  仮想キーコードを送信
//  LParam の値を 0 にして仮想キーコードを送信
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Edit1.Text := '';

  PostMessage(Edit1.Handle, WM_KEYDOWN, Ord('A'), 0);
  PostMessage(Edit1.Handle, WM_KEYUP,   Ord('A'), 0);
end;




16_WM_SYSKEYDOWN と WM_SYSKEYUP によるメニュー操作


WM_KEYDOWN と WM_KEYUP メッセージによる仮想キーコードの送信では,他のアプリ (正確には他のスレッド) に対して,キーの組み合わせの送信はできません.WM_SYSKEYDOWN と WM_SYSKEYUP メッセージを使用すると,[Alt] キーの組み合わせのキー送信が可能です.[Alt] キーの仮想キーコードは VK_MENU です.これはメニューの操作です.
下に示すサンプルは,メモ帳の [編集(E)] メニューの [日付と時刻(D)] を実行します.



図36
WM_SYSKEYDOWN と WM_SYSKEYUP メ メッセージによるメニューの操作例

リスト13
WM_SYSKEYDOWN と WM_SYSKEYUP メ メッセージによるメニューの操作例
//=============================================================================
//  WM_SYSKEYDOWN メッセージを使用したメニュー操作
//  メモ帳のメニューの [編集(E)] で [日付と時刻(D)] を実行
//  この操作のショートカットキーは [F5]
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  LTargetWnd := FindWindow('notepad', nil);
  if LTargetWnd = 0 then begin
    MessageBox(Handle, 'メモ帳が起動していません。', '情報', MB_ICONINFORMATION);
    Exit;
  end;
  // アクティブなウィンドウに対する操作となる
  SetForegroundWindow(LTargetWnd);


  // VK_MENU ([Alt]) キーに続いてキー [E] を送信
  // キー [E] の LParam に [Alt] が押されているフラグを追加しておく
  PostMessage(LTargetWnd, WM_SYSKEYDOWN, VK_MENU,  $20380001);
  PostMessage(LTargetWnd, WM_SYSKEYDOWN, Ord('E'), $20120001);

  // メニュー表示に使用したキーを離す ($E は 2 進演算で 1100 or 10)
  PostMessage(LTargetWnd, WM_SYSKEYUP, Ord('E'), $E0120001);
  PostMessage(LTargetWnd, WM_SYSKEYUP, VK_MENU,  $C0380001);

  // メニュー (コンテキストメニュー) のアクセラレータキー [D] を送信
  PostMessage(LTargetWnd, WM_KEYDOWN, Ord('D'), $00200001);
  PostMessage(LTargetWnd, WM_KEYUP,   Ord('D'), $C0200001);
end;


ショートカットキー送信操作用のユニットを使用すると,以下のどちらでも上のコードと同じ操作となります.


  SendShortCutKeyEx(LTargetWnd, 'Alt + E + D');
  SendShortCutKeyEx(LTargetWnd, 'F5');


[F10] もメニューキーですが,ショートカットキー送信操作用のユニットを使用する場合は,[F10] キーの操作をしてからアクセラレータキーの操作をしないと動作ません.


  SendShortCutKeyEx(LTargetWnd, 'F10');
  SendShortCutKeyEx(LTargetWnd, 'E + D');


ウィンドウのシステムメニューは [Alt] + [Space] で表示できます.下図は,コマンドプロンプトのウィンドウのシステムメニューを表示しています.



図37
WM_SYSKEYDOWN と WM_SYSKEYUP メ メッセージによるメニューの操作例
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  LTargetWnd := FindWindow('ConsoleWindowClass', nil);
  if LTargetWnd = 0 then Exit;
  // アクティブなウィンドウに対する操作となる
  SetForegroundWindow(LTargetWnd);

 // VK_MENU ([Alt]) キーに続いてキー [Space] を送信
  // キー [Space] の LParam に [Alt] が押されているフラグを追加しておく
  PostMessage(LTargetWnd, WM_SYSKEYDOWN, VK_MENU,  $20380001);
  PostMessage(LTargetWnd, WM_SYSKEYDOWN, VK_SPACE, $20390001);

  // メニュー表示に使用したキーを離す ($E は 2 進演算で 1100 or 10)
  PostMessage(LTargetWnd, WM_SYSKEYUP, VK_SPACE, $E0390001);
  PostMessage(LTargetWnd, WM_SYSKEYUP, VK_MENU,  $C0380001);
end;


[Print Screen] キー (仮想キーコードは VK_SNAPSHOT) や [Num Lock] キー (VK_NUMLOCK), [Windows] キー (VK_LWIN),[TAB] キー (VK_TAB) 等はシステムキーですが,WM_SYSKEYDOWN, WM_SYSKEYUP メッセージを使用しても機能しません.
[TAB] キーの場合は,WM_CHAR メッセージで ASCII 文字としてのコード $09 を,編集ウィンドウに送信すれば TAB 文字として処理できます.





17_スペースキー


ちょっと特殊なキー,仮想キーコードが VK_SPACE のスペースキーの送信例です.
TButton にフォーカスがある時,[Enter] キーを押すと,その TButton の OnClick イベントが発生しますが,スペースキーを押しても OnClick イベントが発生します.それをコードで実行するサンプルです.
VK_SPACE は,この場合,PostMessage 関数で送信しても,SendMessage 関数で送信しても動作します.送信先の TButton に自動的にフォーカス移動します.ただし,半角の空白文字として送信する場合は,これまでのテストの結果と同じで,SendMessage 関数で送信しても文字としては処理されません.

スペースキーは,リストの項目選択や,チェックボックスのチェック操作にも利用されています.



図38
スペースキーの送信
  • [Enter] キー押下と同じ動作になる

//=============================================================================
//  スペースキーを [Enter] キーの代わりに使用
//
//  SendMessage でも PostMessage でも有効
//  フォーカスが Button2 に移動する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMessage(Button2.Handle, WM_KEYDOWN, VK_SPACE, $00390001);
  SendMessage(Button2.Handle, WM_KEYUP,   VK_SPACE, $C0390001);
end;

//=============================================================================
//  .Button2 の OnClick イベント
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  MessageBox(Handle, 'Button2 Clicked !!', 'VK_SPACE', MB_ICONINFORMATION);
end;




18_ショートカットキー送信操作用ユニットのソースコード


作成したショートカットキー送信操作用のユニットのソースコードです.
リストにない仮想キーコード名も,Delphi の VCL に定義があれば,前項のウェブブラウザのサンプルのように Char 関数で文字に変換して引数に渡せば動作します.

処理の本体は,末尾に Ex がない SendShortCutKey という手続きです.この手続きは他の関数類を使用していません.この手続きのコードだけでショートカットキーの送信用に利用できます.
SendShortCutKey と SendShortCutKeyEx の違いの 1 つは,引数の文字列の指定方法です.以下は,どちらも指定したウィンドウハンドルのウィンドウを閉じます.VK_MENU は Alt キーのことです.
末尾に Ex がいない手続きでは,ASCII の記号文字はショートカットキーとして正常に機能しません.ASCII の記号文字のコードは,仮想キーコードの値と同じではありません.変換が必要です.


    SendShortCutKeyEx (LTargetWnd, 'Alt + F4');
    SendShortCutKey   (LTargetWnd, Char(VK_MENU) + Char(VK_F4));


テキストを文字単位で操作する時に,[Shift] キー等を押したままの状態で矢印キーを複数回操作することがあります.そのような場合は,第 3 引数にその回数を指定します.第 3 引数の規定値は 1 です.次のコードは,[Shift] キーを押したままの状態で Right キーを 7 回操作して 7 文字を選択します.そしてそれを削除します.Right は → (右方向矢印) キーの意味です.仮想キーコードの定数名は VK_RIGHT です.Delete は DELETE でも Del でも動作します.


    SendShortCutKeyEx (LTargetWnd, 'Shift + Right', 7);
    SendShortCutKeyEx (LTargetWnd, 'Delete');


1 文字単位で 7 文字を削除する場合は次のようになります.


  SendShortCutKeyEx(LTargetWnd, 'Delete', 7);


ショートカットキーの「キー」は,キーボードのキーのことです,文字ではありません.keybd_event 関数が処理するのは,仮想キーコードと呼ばれるキーのコード (値) です.
[/] という記述は,[] 内の文字の刻印があるキーを意味するために使用しています.


[Ctrl] + [,] は [Ctrl] + [<] と同じ  (どちらも同じキーの組み合わせ)
[Ctrl] + [/] は [Ctrl] + [?] と同じ  (どちらも同じキーの組み合わせ)


[備考]
keybd_event 関数, SendInput 関数を使用した処理が正常に行われなかった場合,システム全体のキーバインドが狂ってしまうことがあります.



リスト14
ショートカットキー送信操作用ユニットのソースコード
unit plSendShortCut;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Dialogs;

  function SendShortCutKeyEx(hWindow: HWND; AKeyText: string; ACount: Integer=1):
    string; overload;
  function SendShortCutKeyEx(AKeyText: string;
    ACount: Integer=1): string; overload;
  function SendShortCutKeyEx(hWindow: HWND; AShortCut: TShortCut): string; overload;
  function SendShortCutKeyEx(AShortCut: TShortCut): string; overload;

  procedure SendKeysASCII(hWindow: HWND; AKeys: string); overload;
  procedure SendKeysASCII(AKeys: string); overload;
  procedure SendInputText(hWindow: HWND; AText: string); overload;
  procedure SendInputText(AText: string); overload;
  procedure SendShortCutKey(hWindow: HWND; AKeyChars: string;
    ACount: Integer=1); overload;
  procedure SendShortCutKey(AKeyChars: string; ACount: Integer=1); overload;
  function GetShorCutChars(AShorCutText: string): string;
  function AsciiToVirtualKey(AChar: Char): Byte; overload;
  function AsciiToVirtualKey(AKeyByte: Byte): Byte; overload;

implementation

uses
  System.StrUtils,
  plvkEnum;

//-----------------------------------------------------------------------------
//  ショートカットキー送信用関数
//
//  引数には以下のような文字列でショートカットキーを指定する
//    Ctrl + C
//    Ctrl + Shift + A
//    Alt + F4 
//  VK_SNAPSHOT, vkSnapShot 等の文字列も使用可能
//
//  Shift + Right のショートカットで,Right キーだけを繰り返し操作する場合
//  ACount にその回数を指定する 
//-----------------------------------------------------------------------------
function SendShortCutKeyEx(hWindow: HWND; AKeyText: string;
  ACount: Integer=1): string;
begin
  Result:= GetShorCutChars(AKeyText);
  SendShortCutKey(hWindow, Result, ACount);
end;

//-----------------------------------------------------------------------------
//  ショートカットキー送信用関数
//  アクティブなウィンドウ用
//-----------------------------------------------------------------------------
function SendShortCutKeyEx(AKeyText: string; ACount: Integer=1): string;
begin
  Result:= GetShorCutChars(AKeyText);
  SendShortCutKey(Result, ACount);
end;

//-----------------------------------------------------------------------------
//  ショートカットキー送信用関数
//  引数が TShortCut 型の場合
//
//  TShortCut 型の値は TMenuItem.ShortCut プロパティで取得できる
//-----------------------------------------------------------------------------
function SendShortCutKeyEx(hWindow: HWND; AShortCut: TShortCut): string;
begin
  if hWindow = 0 then Exit;

  // hWindowは送信先ウィドウのウィンドウハンドル
  SetForegroundWindow(hWindow);
  SendShortCutKeyEx(AShortCut);
end;

//-----------------------------------------------------------------------------
//  ショートカットキー送信用関数
//  引数が TShortCut 型の場合
//  アクティブなウィンドウ用
//-----------------------------------------------------------------------------
function SendShortCutKeyEx(AShortCut: TShortCut): string;
var
  LShift : string;
begin
  // Vcl.Menus.ShortCutToText の結果は使用できない文字があるので使用しない
  LShift := '';
  if AShortCut and $2000 <> 0 then LShift := LShift + Char(VK_SHIFT);
  if AShortCut and $4000 <> 0 then LShift := LShift + Char(VK_CONTROL);
  if AShortCut and $8000 <> 0 then LShift := LShift + Char(VK_MENU);

  Result := LShift + Char(Byte(AShortCut));
  SendShortCutKey(Result);
end;

//-----------------------------------------------------------------------------
//  指定ウィンドウに文字として送信
//  送信可能なのは ASCII 文字の範囲
//
//  受信側では送信した仮想キーコードがそのまま取得できる
//  英大文字は Shift キー ($10) に続いて該当の仮想キーコード
//-----------------------------------------------------------------------------
procedure SendKeysASCII(hWindow: HWND; AKeys: string);
begin
  if hWindow = 0 then Exit;

  // hWindowは送信先ウィドウのウィンドウハンドル
  SetForegroundWindow(hWindow);
  SendKeysASCII(AKeys);
end;

//-----------------------------------------------------------------------------
//  アクティブなウィンドウに文字として送信
//-----------------------------------------------------------------------------
procedure SendKeysASCII(AKeys: string);
var
  LIndex    : Integer;
  LKeyWord  : WORD;
  LKeyByte  : Byte;
  LKeyState : TKeyboardState;
  LIsShift  : Boolean;
begin
  // 改行文字変更
  AKeys :=  AdjustLineBreaks(AKeys, tlbsLF);

  for LIndex := 1 to Length(AKeys) do begin
    // ASCII 文字を仮想キーコードの値に変換
    LKeyWord := VkKeyScanEx(Char(AKeys[LIndex]), GetKeyboardLayout(0));

    LIsShift := (LKeyWord and $100) <> 0;
    if Lo(LKeyWord) in [$41..$5A] then begin
      // 数字キーは Caps Lock されていたら Shift キー押下なしにする
      GetKeyboardState(LKeyState);
      if LKeyState[VK_CAPITAL] = 1 then LIsShift := not LIsShift;
    end;

    if LIsShift then begin
      keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), 0, 0);
    end;
    LKeyByte := Lo(LKeyWord);

    keybd_event(LKeyByte, MapVirtualKey(LKeyByte, 0),0, 0);
    keybd_event(LKeyByte, MapVirtualKey(LKeyByte, 0), KEYEVENTF_KEYUP, 0);

    if LIsShift then begin
      keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), KEYEVENTF_KEYUP, 0);
    end;
  end;
  Sleep(10);
end;

//-----------------------------------------------------------------------------
//  SendInput 関数を使用した文字列 (漢字を含む) の送信
//
//  TInput.ki.dwFlags に KEYEVENTF_UNICODE を含め
//  TInput.ki.wVk := 0 にすると文字の送信になる
//
//  受信側で受信できるのは文字数分の $E7 (キーボード以外からの入力手段)
//-----------------------------------------------------------------------------
procedure SendInputText(hWindow: HWND; AText: string);
begin
  if hWindow = 0 then Exit;

  // hWindowは送信先ウィドウのウィンドウハンドル
  SetForegroundWindow(hWindow);
  SendInputText(AText);
end;

//-----------------------------------------------------------------------------
//  SendInput 関数を使用した文字列 (漢字を含む) の送信
// アクティブなウィンドウ用
//-----------------------------------------------------------------------------
procedure SendInputText(AText: string);
var
  LInput : array [0..1] of TInput;
  LIndex : Integer;
begin
  // 改行文字変換
  AText := AdjustLineBreaks(AText, tlbsLF);

  for LIndex := 1 to Length(AText) do begin
    // キーを押す設定
    LInput[0].Itype          := INPUT_KEYBOARD;
    LInput[0].ki.wVk         := 0;
    LInput[0].ki.wScan       := Ord(AText[lIndex]);
    LInput[0].ki.dwFlags     := KEYEVENTF_UNICODE;
    LInput[0].ki.time        := 0;
    LInput[0].ki.dwExtraInfo := 0;

    // キーを離す設定
    LInput[1].Itype          := INPUT_KEYBOARD;
    LInput[1].ki.wVk         := 0;
    LInput[1].ki.wScan       := Ord(AText[lIndex]);
    LInput[1].ki.dwFlags     := KEYEVENTF_KEYUP or KEYEVENTF_UNICODE;
    LInput[1].ki.time        := 0;
    LInput[1].ki.dwExtraInfo := 0;

    SendInput(2, LInput[0], SizeOf(TInput));
  end;
  Sleep(10);
end;

//-----------------------------------------------------------------------------
//  指定ウィンドウにショートカットキーを送信
//
//  ACmdWnd   : 対象のウィンドウハンドル
//  AKeyChars : 仮想キーコードの文字列.GetShorCutChara 関数で作成できる
//              VK_CONTROL + C (Ctrl + C) の場合は #$11#$43 を渡す
//              これはChr(VK_CONTROL) + Chr('C') と同じ
//  ACount   : ショートカッキー操作の繰り返し回数
//              Ctrl + Right 等の Right キーだけを複数回繰り返し実行したい場合に指定
//              繰り返すのは最後のキーのみ
//
//  keybd_event 関数では,拡張キーの場合,KEYEVENTF_EXTENDEDKEY が必要
//  拡張キーは,主にテンキーの左側にある矢印キー等
//  [Windows] キーも拡張キー
//-----------------------------------------------------------------------------
procedure SendShortCutKey(hWindow: HWND; AKeyChars: string; ACount: Integer=1);
begin
  if hWindow = 0 then Exit;

  // hWindowは送信先ウィドウのウィンドウハンドル
  SetForegroundWindow(hWindow);
  SendShortCutKey(AKeyChars, ACount);
end;

//-----------------------------------------------------------------------------
//  アクティブなウィンドウにショートカットキーを送信
//-----------------------------------------------------------------------------
procedure SendShortCutKey(AKeyChars: string; ACount: Integer=1);

 //---------------------------------------------------------------------------
  procedure Keyevent(AByte: Byte; AFlag: Cardinal);
  const
    // 拡張キーの判定用
    LExtendKys = [$21..$2F, $5B..$5F, $A6..$B7, $90];
  var
    LKeyFlag : Cardinal;
  begin
    if AByte in LExtendKys then begin
      LKeyFlag := AFlag or KEYEVENTF_EXTENDEDKEY;
    end else begin
      LKeyFlag := AFlag;
    end;
    keybd_event(AByte, MapVirtualKey(AByte, 0), LKeyFlag, 0);
  end;
 //---------------------------------------------------------------------------

var
  LIndex    : Integer;
  LCount    : Integer;
  LKeyByte  : Byte;
begin
  if AKeyChars = '' then Exit;

  // キーを押したままの状態にする
  for LIndex := 1 to Length(AKeyChars) - 1 do begin
    LKeyByte := Ord(AKeyChars[LIndex]);
    Keyevent(LKeyByte, 0);
  end;

  // 最後のキーの繰り返し操作
  // 最低 1 回は実行
  LKeyByte := Ord(AKeyChars[Length(AKeyChars)]);
  for LCount := 1 to ACount do begin
    Keyevent(LKeyByte, 0);
    Keyevent(LKeyByte, KEYEVENTF_KEYUP);
  end;

  // キーを順番に離す操作
  for LIndex := Length(AKeyChars) - 1 downto 1 do begin
    LKeyByte := Ord(AKeyChars[LIndex]);
    Keyevent(LKeyByte, KEYEVENTF_KEYUP);
  end;
  // 少しタイムラグが必要 (キーバッファの処理か ?)
  Sleep(10);
end;

//-----------------------------------------------------------------------------
//  'Ctrl + C' 等の文字列を仮想キーコードの文字列に変換
//   + の両端に半角の空白があってもよい
//  文字を [+] のように [] で括ることも可能
//   'Ctrl+C' (または 'VK_CONTROL + C') の時は #$11#$43 を戻り値として返す
//
//  [Fn] キーには仮想キーコードはない ([Fn] + XX で特定の値が割り当てられている
//
//  uses に System.StrUtils が必要
//  uses に plvkEnum (添付のユニット) が必要
//-----------------------------------------------------------------------------
function GetShorCutChars(AShorCutText: string): string;
var
  LStrList : TStringList;
  LArgText : string;
  LvkText  : string;
  LResStr  : string;
  LByte    : Byte;
  LIndex   : Integer;
begin
  Result := '';
  if AShorCutText = '' then Exit;
  if Trim(AShorCutText)[1]  = '+' then Exit;

  AShorCutText := ReplaceText(AShorCutText, ' ', '');
  AShorCutText := ReplaceText(AShorCutText, '[+]', 'VK_ADD');
  AShorCutText := ReplaceText(AShorCutText, '++',  '+VK_ADD');

  LResStr := '';
  LStrList := TStringList.Create;
  try
    LStrList.Delimiter       := '+';
    LStrList.StrictDelimiter := True;
    LStrList.DelimitedText   := AShorCutText;

    for LIndex := 0 to LStrList.Count - 1 do begin
      LArgText := LStrList[LIndex];
      if (LeftStr(LArgText, 1) = '[') and (RightStr(LArgText, 1) = ']') then begin
        LArgText := Copy(LArgText, 2, Length(LArgText) -2);
      end;
      LvkText := UpperCase(LArgText);

      // 仮想キーコード名として使用可能な省略形式の文字列の判定
      // VK_XXX の VK_ も省略可能
      if LvkText = '←'          then LvkText := 'VK_LEFT';
      if LvkText = '↑'          then LvkText := 'VK_UP';
      if LvkText = '→'          then LvkText := 'VK_RIGHT';
      if LvkText = '↓'          then LvkText := 'VK_DOWN';
      if LvkText = 'ENTER'       then LvkText := 'VK_RETURN';
      if LvkText = 'CTRL'        then LvkText := 'VK_CONTROL';
      if LvkText = 'ALT'         then LvkText := 'VK_MENU';
      if LvkText = 'CAPSLOCK'    then LvkText := 'VK_CAPITAL';
      if LvkText = 'ESC'         then LvkText := 'VK_ESCAPE';
      if LvkText = 'PRTSC'       then LvkText := 'VK_SNAPSHOT';
      if LvkText = 'PRTSCN'      then LvkText := 'VK_SNAPSHOT';
      if LvkText = 'PRINTSCREEN' then LvkText := 'VK_SNAPSHOT';
      if LvkText = 'INS'         then LvkText := 'VK_INSERT';
      if LvkText = 'DEL'         then LvkText := 'VK_DELETE';
      if LvkText = 'PAGEUP'      then LvkText := 'VK_PRIOR';
      if LvkText = 'PAGEDOWN'    then LvkText := 'VK_NEXT';
      if LvkText = 'WINDOWS'     then LvkText := 'VK_LWIN';
      if LvkText = 'WIN'         then LvkText := 'VK_LWIN';

      if (Length(LArgText) > 1) and (LArgText[1] <> '$') then begin
        if LeftStr(UpperCase(LvkText), 2) <> 'VK' then LvkText := 'vk' + LvkText;
      end;

      LByte := vkTextToVirtualKey(LvkText);
      if LByte = $FF then Break;
      LResStr := LResStr + Char(LByte);
    end;
    Result := LResStr;
  finally
    FreeAndNil(LStrList);
  end;
end;

//-----------------------------------------------------------------------------
//  ASCII 文字を仮想キーーコードの値に変換する関数
//  文字 = と - を変換すると共に $BD になる
//
//  $20..$2F には VK_SPACE..VK_HELP までが割り当てられている 
//           ASCII 文字は !"#$%&'()*+,-./ ($20 は [Space])
//  $3A..$40 は未定義
//           ASCII 文字は :;<=>?@
//  $5B..$60 には VK_LWIN..VK_NUMPAD0 までが割り当てられている
//           ASCII 文字は [\]^_`
//  $7B..$7E には VK_F12..VK_F15 までが割り当てられている
//           ASCII 文字は {|}~
//-----------------------------------------------------------------------------
function AsciiToVirtualKey(AChar: Char): Byte;
var
  LKeyByte : Byte;
  LLayout  : HKL;
begin
  LKeyByte := Byte(AChar);
  Result := LKeyByte;
  if LKeyByte in [$20..$2F, $3A..$40, $5B..$60, $7B..$7E] then begin
    LLayout :=  GetKeyboardLayout(0);
    Result := Lo(VkKeyScanEx(AChar, LLayout));
  end;
end;

//-----------------------------------------------------------------------------
//  ASCII 文字コードを仮想キーコードの値に変換する関数
//-----------------------------------------------------------------------------
function AsciiToVirtualKey(AKeyByte: Byte): Byte;
begin
  Result := AsciiToVirtualKey(Char(AKeyByte));
end;

end.


仮想キーコードのリストと,その値を取得する関数のコードです.



リスト15
仮想キーコードのリストと関係関数のコード
unit plvkEnum;

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes;

  function vkTextToVirtualKey(vkText: string): Cardinal;
  function VirtualKeyToVKText(KeyByte: Byte): string;
  function EnumVkText(vkList: TStringList): Integer;

  implementation

uses
  System.StrUtils,
  System.TypInfo;

type
  //-----------------------------------------------------------------
  //  仮想キーコードの列挙型を定義
  //  Delphi XE5(UP2) Pro の System.UITypes.pas 内のリストから作成
  //  プログラムを作成して未定義の値の部分を追加
  //
  //  以下は削除 (値が重複しているので)
  //    vkHangul = $15
  //    vkHanja,= $19
  //  以下の位置を変更
  //    kEscape = $1B
  //
  //  THoge = (aaa = 10, bbb = 15, ccc = 55);
  //  のような列挙型も定義できる
  //  ただし TypeInfo を使用した情報の取得はできない
  //  (オンラインヘルプ参照)
  //  THoge は型情報を持っていません」のコンパイルエラーとなる
  //-----------------------------------------------------------------
  TplvkEnum = (
  { Virtual Keys, Standard Set }
  not_used_x00,       // = $00;
  vkLButton,          // = $01;  {   1 }
  vkRButton,          // = $02;  {   2 }
  vkCancel,           // = $03;  {   3 }
  vkMButton,          // = $04;  {   4 }
  vkXButton1,         // = $05;  {   5 }
  vkXButton2,         // = $06;  {   6 }
  not_used_x07,       // = $07;
  vkBack,             // = $08;  {   8 }
  vkTab,              // = $09;  {   9 }
  vkLineFeed,         // = $0A;  {  10 }
  not_used_x0B,       // = $0B;
  vkClear,            // = $0C;  {  12 }
  vkReturn,           // = $0D;  {  13 }
  not_used_x0E,       // = $0E;
  not_used_x0F,       // = $0F;
  vkShift,            // = $10;  {  16 }
  vkControl,          // = $11;  {  17 }
  vkMenu,             // = $12;  {  18 }
  vkPause,            // = $13;  {  19 }
  vkCapital,          // = $14;  {  20 }
  vkKana,             // = $15;  {  21 }
  not_used_x16,       // = $16;
  vkJunja,            // = $17;  {  23 }
  vkFinal,            // = $18;  {  24 }
  vkKanji,            // = $19;  {  25 }
  not_used_x1A,       // = $1A;
  vkEscape,           // = $1B;  {  27 }
  vkConvert,          // = $1C;  {  28 }
  vkNonConvert,       // = $1D;  {  29 }
  vkAccept,           // = $1E;  {  30 }
  vkModeChange,       // = $1F;  {  31 }
  vkSpace,            // = $20;  {  32 }
  vkPrior,            // = $21;  {  33 }
  vkNext,             // = $22;  {  34 }
  vkEnd,              // = $23;  {  35 }
  vkHome,             // = $24;  {  36 }
  vkLeft,             // = $25;  {  37 }
  vkUp,               // = $26;  {  38 }
  vkRight,            // = $27;  {  39 }
  vkDown,             // = $28;  {  40 }
  vkSelect,           // = $29;  {  41 }
  vkPrint,            // = $2A;  {  42 }
  vkExecute,          // = $2B;  {  43 }
  vkSnapshot,         // = $2C;  {  44 }
  vkInsert,           // = $2D;  {  45 }
  vkDelete,           // = $2E;  {  46 }
  vkHelp,             // = $2F;  {  47 }
  { vk0 thru vk9 are the same as ASCII '0' thru '9' ($30 - $39) }
  vk0,                // = $30;  {  48 }
  vk1,                // = $31;  {  49 }
  vk2,                // = $32;  {  50 }
  vk3,                // = $33;  {  51 }
  vk4,                // = $34;  {  52 }
  vk5,                // = $35;  {  53 }
  vk6,                // = $36;  {  54 }
  vk7,                // = $37;  {  55 }
  vk8,                // = $38;  {  56 }
  vk9,                // = $39;  {  57 }
  not_used_x3A,       // = $3A;
  not_used_x3B,       // = $3B;
  not_used_x3C,       // = $3C;
  not_used_x3D,       // = $3D;
  not_used_x3E,       // = $3E;
  not_used_x3F,       // = $3F;
  not_used_x40,       // = $40;
  { vkA thru vkZ are the same as ASCII 'A' thru 'Z' ($41 - $5A) }
  vkA,                // = $41;  {  65 }
  vkB,                // = $42;  {  66 }
  vkC,                // = $43;  {  67 }
  vkD,                // = $44;  {  68 }
  vkE,                // = $45;  {  69 }
  vkF,                // = $46;  {  70 }
  vkG,                // = $47;  {  71 }
  vkH,                // = $48;  {  72 }
  vkI,                // = $49;  {  73 }
  vkJ,                // = $4A;  {  74 }
  vkK,                // = $4B;  {  75 }
  vkL,                // = $4C;  {  76 }
  vkM,                // = $4D;  {  77 }
  vkN,                // = $4E;  {  78 }
  vkO,                // = $4F;  {  79 }
  vkP,                // = $50;  {  80 }
  vkQ,                // = $51;  {  81 }
  vkR,                // = $52;  {  82 }
  vkS,                // = $53;  {  83 }
  vkT,                // = $54;  {  84 }
  vkU,                // = $55;  {  85 }
  vkV,                // = $56;  {  86 }
  vkW,                // = $57;  {  87 }
  vkX,                // = $58;  {  88 }
  vkY,                // = $59;  {  89 }
  vkZ,                // = $5A;  {  90 }
  vkLWin,             // = $5B;  {  91 }
  vkRWin,             // = $5C;  {  92 }
  vkApps,             // = $5D;  {  93 }
  not_used_x5E,       // = $5E;
  vkSleep,            // = $5F;  {  95 }
  vkNumpad0,          // = $60;  {  96 }
  vkNumpad1,          // = $61;  {  97 }
  vkNumpad2,          // = $62;  {  98 }
  vkNumpad3,          // = $63;  {  99 }
  vkNumpad4,          // = $64;  { 100 }
  vkNumpad5,          // = $65;  { 101 }
  vkNumpad6,          // = $66;  { 102 }
  vkNumpad7,          // = $67;  { 103 }
  vkNumpad8,          // = $68;  { 104 }
  vkNumpad9,          // = $69;  { 105 }
  vkMultiply,         // = $6A;  { 106 }
  vkAdd,              // = $6B;  { 107 }
  vkSeparator,        // = $6C;  { 108 }
  vkSubtract,         // = $6D;  { 109 }
  vkDecimal,          // = $6E;  { 110 }
  vkDivide,           // = $6F;  { 111 }
  vkF1,               // = $70;  { 112 }
  vkF2,               // = $71;  { 113 }
  vkF3,               // = $72;  { 114 }
  vkF4,               // = $73;  { 115 }
  vkF5,               // = $74;  { 116 }
  vkF6,               // = $75;  { 117 }
  vkF7,               // = $76;  { 118 }
  vkF8,               // = $77;  { 119 }
  vkF9,               // = $78;  { 120 }
  vkF10,              // = $79;  { 121 }
  vkF11,              // = $7A;  { 122 }
  vkF12,              // = $7B;  { 123 }
  vkF13,              // = $7C;  { 124 }
  vkF14,              // = $7D;  { 125 }
  vkF15,              // = $7E;  { 126 }
  vkF16,              // = $7F;  { 127 }
  vkF17,              // = $80;  { 128 }
  vkF18,              // = $81;  { 129 }
  vkF19,              // = $82;  { 130 }
  vkF20,              // = $83;  { 131 }
  vkF21,              // = $84;  { 132 }
  vkF22,              // = $85;  { 133 }
  vkF23,              // = $86;  { 134 }
  vkF24,              // = $87;  { 135 }

  vkCamera,           // = $88;  { 136 }
  vkHardwareBack,     // = $89;  { 137 }
  not_used_x8A,       // = $8A;
  not_used_x8B,       // = $8B;
  not_used_x8C,       // = $8C;
  not_used_x8D,       // = $8D;
  not_used_x8E,       // = $8E;
  not_used_x8F,       // = $8F;

  vkNumLock,          // = $90;  { 144 }
  vkScroll,           // = $91;  { 145 }
  not_used_x92,       // = $92;
  not_used_x93,       // = $93;
  not_used_x94,       // = $94;
  not_used_x95,       // = $95;
  not_used_x96,       // = $96;
  not_used_x97,       // = $97;
  not_used_x98,       // = $98;
  not_used_x99,       // = $99;
  not_used_x9A,       // = $9A;
  not_used_x9B,       // = $9B;
  not_used_x9C,       // = $9C;
  not_used_x9D,       // = $9D;
  not_used_x9E,       // = $9E;
  not_used_x9F,       // = $9F;
  vkLShift,           // = $A0;  { 160 }
  vkRShift,           // = $A1;  { 161 }
  vkLControl,         // = $A2;  { 162 }
  vkRControl,         // = $A3;  { 163 }
  vkLMenu,            // = $A4;  { 164 }
  vkRMenu,            // = $A5;  { 165 }

  vkBrowserBack,      // = $A6;  { 166 }
  vkBrowserForward,   // = $A7;  { 167 }
  vkBrowserRefresh,   // = $A8;  { 168 }
  vkBrowserStop,      // = $A9;  { 169 }
  vkBrowserSearch,    // = $AA;  { 170 }
  vkBrowserFavorites, // = $AB;  { 171 }
  vkBrowserHome,      // = $AC;  { 172 }
  vkVolumeMute,       // = $AD;  { 173 }
  vkVolumeDown,       // = $AE;  { 174 }
  vkVolumeUp,         // = $AF;  { 175 }
  vkMediaNextTrack,   // = $B0;  { 176 }
  vkMediaPrevTrack,   // = $B1;  { 177 }
  vkMediaStop,        // = $B2;  { 178 }
  vkMediaPlayPause,   // = $B3;  { 179 }
  vkLaunchMail,       // = $B4;  { 180 }
  vkLaunchMediaSelect,// = $B5;  { 181 }
  vkLaunchApp1,       // = $B6;  { 182 }
  vkLaunchApp2,       // = $B7;  { 183 }
  not_used_xB8,       // = $B8;
  not_used_xB9,       // = $B9;

  vkSemicolon,        // = $BA;  { 186 }
  vkEqual,            // = $BB;  { 187 }
  vkComma,            // = $BC;  { 188 }
  vkMinus,            // = $BD;  { 189 }
  vkPeriod,           // = $BE;  { 190 }
  vkSlash,            // = $BF;  { 191 }
  vkTilde,            // = $C0;  { 192 }
  not_used_xC1,       // = $C1;
  not_used_xC2,       // = $C2;
  not_used_xC3,       // = $C3;
  not_used_xC4,       // = $C4;
  not_used_xC5,       // = $C5;
  not_used_xC6,       // = $C6;
  not_used_xC7,       // = $C7;
  not_used_xC8,       // = $C8;
  not_used_xC9,       // = $C9;
  not_used_xCA,       // = $CA;
  not_used_xCB,       // = $CB;
  not_used_xCC,       // = $CC;
  not_used_xCD,       // = $CD;
  not_used_xCE,       // = $CE;
  not_used_xCF,       // = $CF;
  not_used_xD0,       // = $D0;
  not_used_xD1,       // = $D1;
  not_used_xD2,       // = $D2;
  not_used_xD3,       // = $D3;
  not_used_xD4,       // = $D4;
  not_used_xD5,       // = $D5;
  not_used_xD6,       // = $D6;
  not_used_xD7,       // = $D7;
  not_used_xD8,       // = $D8;
  not_used_xD9,       // = $D9;
  not_used_xDA,       // = $DA;
  vkLeftBracket,      // = $DB;  { 219 }
  vkBackslash,        // = $DC;  { 220 }
  vkRightBracket,     // = $DD;  { 221 }
  vkQuote,            // = $DE;  { 222 }
  vkPara,             // = $DF;  { 223 }
  not_used_xE0,       // = $E0;
  not_used_xE1,       // = $E1;

  vkOem102,           // = $E2;  { 226 }
  vkIcoHelp,          // = $E3;  { 227 }
  vkIco00,            // = $E4;  { 228 }
  vkProcessKey,       // = $E5;  { 229 }
  vkIcoClear,         // = $E6;  { 230 }
  vkPacket,           // = $E7;  { 231 }
  not_used_xE8,       // = $E8;
  not_used_xE9,       // = $E9;
  not_used_xEA,       // = $EA;
  not_used_xEB,       // = $EB;
  not_used_xEC,       // = $EC;
  not_used_xED,       // = $ED;
  not_used_xEE,       // = $EE;
  not_used_xEF,       // = $EF;
  not_used_xF0,       // = $F0;
  not_used_xF1,       // = $F1;
  not_used_xF2,       // = $F2;
  not_used_xF3,       // = $F3;
  not_used_xF4,       // = $F4;
  not_used_xF5,       // = $F5;
  vkAttn,             // = $F6;  { 246 }
  vkCrsel,            // = $F7;  { 247 }
  vkExsel,            // = $F8;  { 248 }
  vkErEof,            // = $F9;  { 249 }
  vkPlay,             // = $FA;  { 250 }
  vkZoom,             // = $FB;  { 251 }
  vkNoname,           // = $FC;  { 252 }
  vkPA1,              // = $FD;  { 253 }
  vkOemClear,         // = $FE;  { 254 }
  vkNone              // = $FF;  { 255 }
  );

//-----------------------------------------------------------------------------
//  仮想キーコードの定数名の文字列から仮想キーコードの値を取得する関数
//  値が取得できない場合は $FF を返す
//  戻り値は 2 バイトの符号なし整数だが,下位バイトのみ有効
//
//  引数の文字列は VK_Shift, vkShift 等とする
//  大文字と小文字の区別はしない
//  1 文字だけの時は ASCII 文字のキーと解釈する
//-----------------------------------------------------------------------------
function vkTextToVirtualKey(vkText: string): Cardinal;
begin
  if Length(vkText) = 1 then begin
    Result := Lo(VkKeyScanEx(vkText[1], GetKeyboardLayout(0)));
  end else
  if (vkText[1] = '$') then begin
    Result := StrToIntDef(vkText, $FF);
  end else begin
    vkText := ReplaceText(vkText, '_', '');
    vkText := ReplaceText(vkText, 'vkHangul', 'vkKana');
    vkText := ReplaceText(vkText, 'vkHanja', 'vkKanji');
    Result := GetEnumValue(TypeInfo(TplvkEnum), vkText);
    if Result <= 0 then Result := $FF;
  end;
end;

//-----------------------------------------------------------------------------
//  仮想キーコードの値から仮想キーコードの定数名の文字列を取得する関数
//  これらの定数名の定義は System.UITypes.pas にある
//  定数の定義がない場合は空文字を返す
//-----------------------------------------------------------------------------
function VirtualKeyToVKText(KeyByte: Byte): string;
begin
  Result := GetEnumName(TypeInfo(TplvkEnum), KeyByte);

  if KeyByte = $15 then Result := 'vkKana';
  if KeyByte = $19 then Result := 'vkKanji';
  if ContainsText(Result, 'not_used') then Result := '';
end;

//-----------------------------------------------------------------------------
//  拡張キーコードの定数名を列挙
//-----------------------------------------------------------------------------
function EnumVkText(vkList: TStringList): Integer;
var
  LpInfo : PTypeInfo;
  LpData : PTypeData;
  LText  : string;
  LIndex : Integer;
begin
  LpInfo := TypeInfo(TplvkEnum);
  LpData := GetTypeData(LpInfo);

  for LIndex := LpData^.MinValue to LpData^.MaxValue do begin
    LText :=  GetEnumName(LpInfo, LIndex);
    if ContainsText(LText, 'not_used') then Continue;
    vkList.add(LText);
  end;
  Result := vkList.Count;
end;

end.