Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2019年08月09日] [更新 2019年08月09日] Delphi サンプルプログラム集
   
671_他のアプリへキー操作を送信
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
671_EmulateKeyPress.zip [4,386 KB] 2019年08月09日版 (EXE 同梱)






他のアプリへキー操作を送信


他のアプリケーションに対してキー操作を行うには,キー操作をエミュレートします.
キー操作対象のコントロールが配置されているウィンドウを前面に表示してアクティブにして (フォアグラウンドなウィンドウにして),キー入力のフォーカスを対象のコントロールします.

キー操作をエミュレートには keybd_event 関数あるいは SendInput 関数が使用できます.
[Shift] キー等との組み合わせのキー操作は,[Shift] キーの押下状態をキーバッファに格納します.キーバッファはスレッド単位のメモリ上のデータですから,他のプロセスへの送信はできません.また,他のプロセスからキーバッファの情報を取得することもできません.したがって,キーバッファの操作処理コードを必要とする WM_KEYDOWN, WM_KEYUP メッセージでは,他のアプリに対してキー操作を正しくエミュレートできません.

受信側のアプリがコーディング可能な自開発のアプリであれば,押下するキーコードを送信して,受信側で WM_KEYDOWN, WM_KEYUP メッセージを使用してキー操作をエミュレートすることが可能です.この方法であれば,受信側のウィンドウをフォアグラウンドにする必要がありません



以下のページで,keybd_event( 関数を使用したショートカットキー送信操作用のユニットを提供しています.そのユニットに実装している関数類を使用すると,他のアプリのショートカットキーの操作をエミュレートできます.






01_単一キーは WM_KEYDOWN と WM_KEYUP で送信可能


単一のキーであれば WM_KEYDOWN, WM_KEYUP メッセージでキー操作をエミュレート可能です.その例です.ただし [Enter] キーは LParam の値を適切に設定しないと正常に動作しません.また,他のキーも,実際にはLParam の設定が必要ですから,送信可能なキーは限られます.
このサンプルではメモ帳を起動して,メモ帳にキー操作を送信します.



リスト1
WM_KEYDOWN と WM_KEYUP を指定して [F1] と [Enter] キーの操作をエミュレート
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;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LExePath   : string;
  LParamText : string;
begin
  LExePath   := 'notepad.exe';
  LParamText := ExpandFileName('Document.txt');
  ShellExecute(0, '', PChar(LExePath), PChar(LParamText), nil, SW_SHOW);
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  LhWindow  : HWND;
  LhHelpWnd : HWND;
begin
  // メモ帳を閉じる
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow <> 0 then begin
    PostMessage(LhWindow, WM_CLOSE, 0, 0);
  end;

  // ヘルプのウィンドウを閉じる
  LhHelpWnd := FindWindow('HelpPane', nil);
  if LhHelpWnd <> 0 then begin
    PostMessage(LhHelpWnd, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  メモ帳のウィンドウにキー操作を送信
//  [F1] (ヘルプ)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhWindow : HWND;
begin
  // メモ帳のウィンドウハンドル
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow = 0 then exit;

  PostMessage(LhWindow, WM_KEYDOWN, VK_F1, $003B0001);
  PostMessage(LhWindow, WM_KEYUP,   VK_F1, $C03B0001);
end;

//=============================================================================
//  メモ帳の編集ウィンドウにキー操作を送信
//  [Enter] (エンたー)
//  VK_RETRUN は LParam の設定が必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LhWindow : HWND;
  LRevWnd  : HWND;
begin
  // メモ帳のウィンドウハンドル
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow = 0 then exit;

  //メモ帳の編集ウィンドウのハンドルを取得
  LRevWnd := FindWindowEx(LhWindow, 0, 'Edit', nil);
  if LRevWnd = 0 then exit;

  // [Enter] キーの送信
  // KEYUP は KEYDOWN の LParam で設定
  PostMessage(LRevWnd, WM_KEYDOWN, VK_RETURN, $001C0001);
  PostMessage(LRevWnd, WM_KEYDOWN, VK_RETURN, $C01C0001);  
end;

end.




02_他のアプリに keybd_event 関数でキー操作を送信


keybd_event 関数を使用してキー操作をエミュレートする例です.このサンプルではメモ帳を起動してテキストを全て選択します.



図1
keybd_event でキー操作
  • [Ctrl] + [A] を送信

リスト2
keybd_event 関数で [Ctrl] + [A] のキー操作をエミュレート
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 FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  テキストファイルを指定してメモ帳を起動
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LExePath   : string;
  LParamText : string;
begin
  LExePath   := 'notepad.exe';
  LParamText := ExpandFileName('Document.txt');
  ShellExecute(0, '', PChar(LExePath), PChar(LParamText), nil, SW_SHOW);
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  LhWindow : HWND;
begin
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow <> 0 then begin
    PostMessage(LhWindow, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  メモ帳の編集ウィンドウにキー操作を送信
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhWindow : HWND;
  LRevWnd  : HWND;
  LKeyByte : Byte;
begin
  // メモ帳のウィンドウハンドル
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow = 0 then exit;

  //メモ帳の編集ウィンドウのハンドルを取得
  LRevWnd := FindWindowEx(LhWindow, 0, 'Edit', nil);
  if LRevWnd = 0 then exit;

  // メモ帳をフォアグラウンドにする
  // BringWindowToTop は Windows Vista 以降用
  BringWindowToTop(LhWindow);
  // キー入力フォーカスをメモ帳の編集ウィンドウにする
  // または SetForegroundWindow を使用する
  PostMessage(LRevWnd, WM_SETFOCUS, 0, 0);

  //メモ帳に対して全て選択([Ctrl]+[A])を実行する
  LKeyByte := Ord('A');
  keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0),  0, 0);
  keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),    0, 0);
  keybd_event(LKeyByte,   MapVirtualKey(LKeyByte, 0),    KEYEVENTF_KEYUP, 0);
  keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0),  KEYEVENTF_KEYUP, 0);

  // このフォームを前面にする
  Sleep(100);
  BringWindowToTop(Handle);
end;

end.




03_拡張キーの操作には KEYEVENTF_EXTENDEDKEY が必要


前項のサンプルと同じく,メモ帳を起動して keybd_event 関数でキー操作を送信します.
このサンプルでは [Shift] + [→] で 1 文字づつ文字を選択しますが,テンキーや矢印キー,[Home] キー等の拡張キーの操作には KEYEVENTF_EXTENDEDKEY フラグが必要です.
拡張キーは,古いキーボードにはないキーのことです.古いキーボードとは,主にテンキー (10 キー) の左側にある矢印キー等がない時代のキーボードのことです.筆者自身,拡張キーの定義に関する知識がありませんが,対象のキーが拡張キーであるかは調べることはできます.それには,WM_KEYDWN 等のメッセージを受信して,LParam の拡張キーのフラグを調べます.

以下の記事の [ショートカットキーとコマンドの送信テスト用プログラム] のサンプルに,拡張キーの判定を行うコードを掲載しています.



図2
keybd_event でのキー操作
  • [Shift] + [→] を送信
  • KEYEVENTF_EXTENDEDKEY フラグ必要

リスト3
keybd_event 関数で [Shift] + [→] のキー操作をエミュレート
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 FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  テキストファイルを指定してメモ帳を起動
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LExePath   : string;
  LParamText : string;
begin
  LExePath   := 'notepad.exe';
  LParamText := ExpandFileName('Document.txt');
  ShellExecute(0, '', PChar(LExePath), PChar(LParamText), nil, SW_SHOW);
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  LhWindow : HWND;
begin
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow <> 0 then begin
    PostMessage(LhWindow, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  メモ帳の編集ウィンドウにキー操作を送信
//  [Shift]+[→]
//  拡張キーの操作には KEYEVENTF_EXTENDEDKEY フラグが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevWnd    : HWND;
  LLoopCnt   : Integer;
  LKeyFlag   : Byte;
begin
  // メモ帳のウィンドウハンドル
  LhWindow := FindWindow('Notepad', nil);
  if LhWindow = 0 then exit;

  //メモ帳の編集ウィンドウのハンドルを取得
  LRevWnd := FindWindowEx(LhWindow, 0, 'Edit', nil);
  if LRevWnd = 0 then exit;

  // メモ帳をフォアグラウンドにする
  // BringWindowToTop は Windows Vista 以降用
  BringWindowToTop(LhWindow);
  // キー入力フォーカスをメモ帳の編集ウィンドウにする
  // または SetForegroundWindow を使用する
  PostMessage(LRevWnd, WM_SETFOCUS, 0, 0);

  //メモ帳に対して全て 1 文字選択([Shift]+[→])を実行する
  for LLoopCnt := 1 to 14 do begin
    LKeyFlag := KEYEVENTF_EXTENDEDKEY;
    keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), 0, 0);
    keybd_event(VK_RIGHT, MapVirtualKey(VK_RIGHT, 0), LKeyFlag, 0);

    LKeyFlag := KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP;
    keybd_event(VK_RIGHT, MapVirtualKey(VK_RIGHT, 0), LKeyFlag, 0);
    keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), KEYEVENTF_KEYUP, 0);
  end;

  // このフォームを前面にする
  Sleep(100);
  BringWindowToTop(Handle);
end;

end.




04_受信アプリが自開発の時のキー操作エミュレート


受信側のアプリを自分で開発,コーディングするのであれば WM_KEYDOWN, WM_KEYUP メッセージによるキー操作のエミュレートが可能です.
もちろん送信側からは直接これらのメッセージでキー操作を送信できませんが,キーコードの情報を受信側に送信して,受信側で処理することになります.以下は送信側のコード例です.
参考リンクの記事のサンプルのように,キーコードの情報をレコード型に格納して WM_COPYDATA メッセージで送信します.レコード型の hRevWnd メンバの値が,実際にキー操作を受け取るコントロールのウィンドウハンドルの値です.



図3
受信側をフォアグラウンドにしないキー操作の送信

リスト4
受信側のアプリをフォアグラウンドしないでキー操作をエミュレート
unit SenderUnit;

interface

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

type
  TSenderForm = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  SenderForm: TSenderForm;

implementation

uses
  Winapi.ShellAPI;

{$R *.DFM}

//-----------------------------------------------
//  押下キーデータのレコード型
//-----------------------------------------------
type
  TKeyDataInfo = record
    hRevWnd : HWND;
    Key     : Byte;
    shift   : TShiftState
  end;

//=============================================================================
//  フォーム生成時の処理
//  受信側のアプリを起動
//=============================================================================
procedure TSenderForm.FormCreate(Sender: TObject);
var
  LExePath : string;
begin
  LExePath := ExpandFileName('.\Receiver\Receiver.exe');
  ShellExecute(0, '', PChar(LExePath), nil, nil, SW_SHOW);
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TSenderForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  LhWindow : HWND;
begin
  LhWindow := FindWindow('TReceivForm', nil);
  if LhWindow <> 0 then begin
    PostMessage(LhWindow, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  キー操作データの送信
//  [Enter]
//=============================================================================
procedure TSenderForm.Button1Click(Sender: TObject);
var
  LKeyInfo  : TKeyDataInfo;
  LCopyDS   : TCopyDataStruct;
  LhWindow  : HWND;
  LRevWnd   : HWND;
begin
  // 受取る側のウィンドウハンドル
  LhWindow := FindWindow('TReceivForm', nil);
  if LhWindow = 0 then exit;
  // 指定 ID 値のウィンドウのハンドル
  LRevWnd := GetDlgItem (LhWindow, 1000);
  if LRevWnd = 0 then Exit;

  LKeyInfo.hRevWnd := LRevWnd;
  LKeyInfo.Key     := VK_RETURN;
  LKeyInfo.shift   := [];

  LCopyDS.dwData := 1234;
  LCopyDS.cbData := SizeOf(LKeyInfo);
  LCopyDS.lpData := @LKeyInfo;

  SendMessage(LhWindow, WM_COPYDATA, Handle, LPARAM(@LCopyDS));
end;

//=============================================================================
//  キー操作データの送信
//  [Shft] + [→]
//=============================================================================
procedure TSenderForm.Button2Click(Sender: TObject);
var
  LKeyInfo  : TKeyDataInfo;
  LCopyDS   : TCopyDataStruct;
  LhWindow  : HWND;
  LRevWnd   : HWND;
begin
  // 受取る側のウィンドウハンドル
  LhWindow := FindWindow('TReceivForm', nil);
  if LhWindow = 0 then exit;
  // 指定 ID 値のウィンドウのハンドル
  LRevWnd := GetDlgItem (LhWindow, 1000);
  if LRevWnd = 0 then Exit;

  LKeyInfo.hRevWnd := LRevWnd;
  LKeyInfo.Key     := VK_RIGHT;
  LKeyInfo.shift   := [ssShift];

  LCopyDS.dwData := 1234;
  LCopyDS.cbData := SizeOf(LKeyInfo);
  LCopyDS.lpData := @LKeyInfo;

  SendMessage(LhWindow, WM_COPYDATA, Handle, LPARAM(@LCopyDS));
  SendMessage(LhWindow, WM_COPYDATA, Handle, LPARAM(@LCopyDS));
  SendMessage(LhWindow, WM_COPYDATA, Handle, LPARAM(@LCopyDS));
end;

end.


受信側のコード例です.
WM_KEYDOWN, WM_KEYUP では LParma の値の設定とキーバッファの処理が必要です,これらの処理を実装したコードが Swiss DelphiCenter にあります.それを利用しています.



リスト5
受信側のアプリのコード
unit ReceiverUnit;

interface

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

type
  TReceivForm = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  protected
    procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  procedure PostKeyExHWND(hWindow: HWnd; key: Word; const shift: TShiftState;
    specialkey: Boolean);

var
  ReceivForm: TReceivForm;

implementation

{$R *.DFM}

//-----------------------------------------------
//  押下キーデータのレコード型
//  送信側とデータのサイズと型を一致させておく
//-----------------------------------------------
type
  TKeyDataInfo = record
    hRevWnd : HWND;
    Key     : Byte;
    shift   : TShiftState
  end;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TReceivForm.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Text := '日本語 ABC テスト文字列';
  Memo1.SelStart := 8;
  Memo1.HideSelection := False;

  // 他のアプリから見つけやすいように ID 値を設定しておく
  // 最後に設定すること
  SetWindowLong(Memo1.Handle, GWL_ID, 1000);
end;

//=============================================================================
//  受信データの処理
//  受信データを元に WM_KEYDOWN, WM_KEYUP メッセージを対象のコントロールにsポスト
//=============================================================================
procedure TReceivForm.WMCopyData(var Message: TWMCopyData);
var
  LKeyInfo : TKeyDataInfo;
  LKey     : Byte;
  LShift   : TShiftState;
begin
  if Message.CopyDataStruct.dwData = 1234 then begin
    // 受信データを取得
    LKeyInfo := TKeyDataInfo(Message.CopyDataStruct^.lpData^);

    LKey    := LKeyInfo.Key;
    LShift  := LKeyInfo.shift;
    PostKeyExHWND(LKeyInfo.hRevWnd, LKey, LShift, False);
  end;
end;

{***********************************************************}
{3. With keybd_event API}

procedure PostKeyExHWND(hWindow: HWnd; key: Word; const shift: TShiftState;
  specialkey: Boolean);
{************************************************************
 * Procedure PostKeyEx
 *
 * Parameters:
 *  hWindow: target window to be send the keystroke
 *  key    : virtual keycode of the key to send. For printable
 *           keys this is simply the ANSI code (Ord(character)).
 *  shift  : state of the modifier keys. This is a set, so you
 *           can set several of these keys (shift, control, alt,
 *           mouse buttons) in tandem. The TShiftState type is
 *           declared in the Classes Unit.
 *  specialkey: normally this should be False. Set it to True to
 *           specify a key on the numeric keypad, for example.
 *           If this parameter is true, bit 24 of the lparam for
 *           the posted WM_KEY* messages will be set.
 * Description:
 *  This procedure sets up Windows key state array to correctly
 *  reflect the requested pattern of modifier keys and then posts
 *  a WM_KEYDOWN/WM_KEYUP message pair to the target window. Then
 *  Application.ProcessMessages is called to process the messages
 *  before the keyboard state is restored.
 * Error Conditions:
 *  May fail due to lack of memory for the two key state buffers.
 *  Will raise an exception in this case.
 * NOTE:
 *  Setting the keyboard state will not work across applications
 *  running in different memory spaces on Win32 unless AttachThreadInput
 *  is used to connect to the target thread first.
 *Created: 02/21/96 16:39:00 by P. Below
 ************************************************************}
type
  TBuffers = array [0..1] of TKeyboardState;
var
  pKeyBuffers: ^TBuffers;
  lParam: LongInt;
begin
  (* check if the target window exists *)
  if IsWindow(hWindow) then
  begin
    (* set local variables to default values *)
    pKeyBuffers := nil;
    lParam := MakeLong(0, MapVirtualKey(key, 0));

    (* modify lparam if special key requested *)
    if specialkey then
      lParam := lParam or $1000000;

    (* allocate space for the key state buffers *)
    New(pKeyBuffers);
    try
      (* Fill buffer 1 with current state so we can later restore it.
         Null out buffer 0 to get a "no key pressed" state. *)
      GetKeyboardState(pKeyBuffers^[1]);
      FillChar(pKeyBuffers^[0], SizeOf(TKeyboardState), 0);

      (* set the requested modifier keys to "down" state in the buffer*)
      if ssShift in shift then
        pKeyBuffers^[0][VK_SHIFT] := $80;
      if ssAlt in shift then
      begin
        (* Alt needs special treatment since a bit in lparam needs also be set *)
        pKeyBuffers^[0][VK_MENU] := $80;
        lParam := lParam or $20000000;
      end;
      if ssCtrl in shift then
        pKeyBuffers^[0][VK_CONTROL] := $80;
      if ssLeft in shift then
        pKeyBuffers^[0][VK_LBUTTON] := $80;
      if ssRight in shift then
        pKeyBuffers^[0][VK_RBUTTON] := $80;
      if ssMiddle in shift then
        pKeyBuffers^[0][VK_MBUTTON] := $80;

      (* make out new key state array the active key state map *)
      SetKeyboardState(pKeyBuffers^[0]);
      (* post the key messages *)
      if ssAlt in Shift then
      begin
        PostMessage(hWindow, WM_SYSKEYDOWN, key, lParam);
        PostMessage(hWindow, WM_SYSKEYUP, key, lParam or $C0000000);
      end
      else
      begin
        PostMessage(hWindow, WM_KEYDOWN, key, lParam);
        PostMessage(hWindow, WM_KEYUP, key, lParam or $C0000000);
      end;
      (* process the messages *)
      Application.ProcessMessages;

      (* restore the old key state map *)
      SetKeyboardState(pKeyBuffers^[1]);
    finally
      (* free the memory for the key state buffers *)
      if pKeyBuffers <> nil then
        Dispose(pKeyBuffers);
    end; { If }
  end;
end; { PostKeyEx }

end.