Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年11月23日] [更新 2019年11月26日] Delphi サンプルプログラム集
   
670_他のアプリへ文字列送信と取得
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
670_SendTextThreadAttach.zip [3,8291 KB] 2019年11月26日版 (EXE 同梱)







他のアプリへ文字列送信と取得


このページのサンプルは,開発者がコードの編集ができない他のアプリへの文字列,テキストの送受信のテストプログラムです.

通常,他のアプリ,他のプロセスのデータに対する操作は,共有メモリと言う仕組みを使用しないと出来ません.WM_SETTEXT メッセージや WM_GETTEXT メッセージは特別で,ウィンドウズが仮想メモリ空間のアドレスの問題を自動的に解決してくれる例外的なメッセージです.ただし,文字列の領域を予め確保しておく必要があります.

[650_他のアプリへデータ (文字列や画像) の送信 (Delphi 同士)] で使用している WM_COPYDATA も,テキスト以外の送受信ができる特別なメッセージです.しかし,操作される他のアプリ側にも,このメッセージの処理コードを書く必要があります.



[備考 1]
コマンドプロンプトも他のアプリ,別のアプリですが,本ページの方法では,文字列の送受信はできません.コマンドプロンプトについては,以下を参考にしてください.


[備考 2]
本ページのサンプルでは,他のアプリとして,Delphi で作成したプログラムとメモ帳を扱っていますが,対象のコントロールのウィンドウハンドルが取得できれば,操作は同じです.



このページのサンプルでは,文字列として以下の 4 つの型を使用しています.
ただし,これらの型が各々のサンプルの動作目的に適しているというわけではありません.使用方法の例として,参考としてテストしているだけです.


型宣言 領域確保と解放または破棄手順
String SetLength 手続きで領域を確保
PChar StrAlloc 関数で領域を確保
StrDispose 関数で破棄
PChar 型は Windows API の関数でよく使用される文字列の型
array [0..V - 1] of Char FillChar 手続きで V 文字分の領域を初期化できる
主に,ウィンドウのクラス名やパス名等,長さに制限ある文字列に対して使用
array of Char SetLength 手続きで領域を確保






01_他のアプリの入力フォーカスがあるコントロールへ文字列を送信


文字列 (テキスト) は,他のアプリのコントロールのウィンドウハンドルの値が分かれば WM_SETTEXT メッセージで送信ができます.
ここでは,受信側の入力フォーカスがあるコントロールを対象とします.本サンプルでは,入力フォーカスのコントロールのハンドルを AttachThreadInput 関数を利用して取得します.

以下は添付の ReceiverApp.exe の入力フォーカスがあるコントロールに文字列をメッセージで送信してセットします.文字列を送信する前に,添付の ReceiverApp.exe を起動しておきます.



図1
実行開始画面
  • 左が本サンプルのプログラム
  • 右は添付の ReceiverApp.exe
図2
Button1 をクリック
  • 右側のアプリのフォーカスがあるコントロールに文字列がセットされる
図3
Button2 をクリックした結果
  • Button3, Button4 をクリックしても結果は同じ

リスト1
他のアプリの入力フォーカスがあるコントロールへ文字列を送信
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
    function GetFocusWindow(hTarget: HWND): HWND;    
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//-----------------------------------------------------------------------------
//  入力フォーカスがあるコントロールのハンドルを取得
//
//  他アプリケーションのコントロールのハンドルは,コントロールのID値がわかれば
//  ID値から取得できるが,不明の場合はフォーカスのあるコントロールであればこの
//  関数で取得できる
//
//  GetForegroundWindowはトップウィンドウの取得,GetFocus単独では他のアプリの
//  場合常に0が返ってくる.AttachThreadInputを使用するとフォーカスのあるウィン
//  ドウ(コントロール)のハンドルを取得できる
//-----------------------------------------------------------------------------
function TForm1.GetFocusWindow(hTarget: HWND): HWND;
var
  dwTargetID : DWORD;
  dwActiveID : DWORD;
begin
  //操作対象ウィンドウのスレッドを取得
  dwTargetID := GetWindowThreadProcessId(hTarget, nil);
  //現在アクティブなウィンドウ(このアプリ)のスレッドを取得
  dwActiveID := GetCurrentThreadId;

  if hTarget <> GetForegroundWindow then begin
    //hTargetをZオーダのトップにしてアクティブに
    BringWindowToTop(hTarget);
    //Windows Vista,7の場合,これがないとアクティブにならない
    Application.ProcessMessages;
  end;

  if AttachThreadInput(dwTargetID, dwActiveID, True) then begin
    //フォーカスウィンドウの取得
    Result := GetFocus;
    //スレッドアタッチを切離す
    AttachThreadInput(dwTargetID, dwActiveID, False);
  end else begin
    Result := 0;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  送信用文字列としてString型を使用する例
//
//  全て添付のReceiverApp.exeを起動してから実行する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LChar      : Char;
begin
  //受取る側のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //文字列を送る
    SendMessage(LRevHandle, WM_SETTEXT, 0, LPARAM(PChar(Edit1.Text)));

    //WM_CHARを使用すれば1文字分だけセットできる
    //第3引数にはキーストロークを指定する(ここでは無指定)
    LChar := 'A';
    SendMessage(LRevHandle, WM_CHAR, WPARAM(LChar), 0);
    //文字を直接セットする場合は以下でOK
    //SendMessage(LRevHandle, WM_CHAR, WPARAM('A'), 0);
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  送信用文字列としてPChar型を使用する例
//
//  String型の文字列をPChart型に代入するにはStrPCopyも使用できる
//  null終端文字(#0)は自動でセットされる
//  PChar型の文字列のサイズの設定は必要ない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : PChar;
begin
  //String型の文字列をChar型の静的配列の文字列に代入
  LBuffer := PChar(Edit1.Text);

  //受取る側のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //文字列を送る
    SendMessage(LRevHandle, WM_SETTEXT, 0, LPARAM(LBuffer));
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  送信用文字列としてChar型の静的配列を使用する例
//
//  Char型の静的配列は,変数の定義の際にその長さを設定する方式
//  String型の文字列をChar型の静的配列に代入するにはStrPCopyが使用できる
//  StrPCopyを使用するとnull終端文字(#0)は自動でセットされる
//
//  設定した文字列の長さが不足すると例外が発生するので注意
//  このサンプルような場面での使い方はあまり考えられない
//  Char型の静的配列は,文字数に制限がある場合によく使用される
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : array[0..MAX_PATH - 1] of Char;
begin
  //String型の文字列をChar型の静的配列の文字列に代入
  //代入可能な文字数はMAX_PATH
  StrPCopy(LBuffer, Edit1.Text);

  //受取る側のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //文字列を送る
    //先頭のアドレス指定なので@LBuffer[0]でもOK
    SendMessage(LRevHandle, WM_SETTEXT, 0, LPARAM(@LBuffer));
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  送信用文字列としてChar型の動的配列を使用する例
//
//  Char型の動的配列の場合は実行時に領域(サイズ)の確保が必要
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : array of Char;
begin
  //Char型配列の要素数を設定(null終端文字用に+1しておく)
  SetLength(LBuffer, Length(Edit1.Text) + 1); 
  //String型の文字列をChar型の動的配列の文字列に代入
  StrPCopy(PChar(LBuffer), Edit1.Text);

  //受取る側のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //文字列を送る
    SendMessage(LRevHandle, WM_SETTEXT, 0, LPARAM(PChar(LBuffer)));
  end;
end;

end.




02_他のアプリの入力フォーカスがあるコントロールの文字列を取得


文字列 (テキスト) は,他のアプリのコントロールのウィンドウハンドルの値が分かれば WM_GETTEXT メッセージを送信することで取得できます.
ここでは,受信側の入力フォーカスがあるコントロールを対象とします.本サンプルでは,入力フォーカスのコントロールのハンドルを AttachThreadInput 関数を利用して取得します.

以下は添付の ReceiverApp.exe の入力フォーカスがあるコントロールのテキストを取得します.取得用のメッセージを送信する前に,添付の ReceiverApp.exe を起動しておきます.



図4
起動時の画面
  • 左側が他のアプリ.これを起動して文字列を入力
  • 右が本サンプルのプログラム
図5
Button1 をクリックした結果
  • 左のアプリの文字列を取得して,本アプリの TEdit に表示
  • Button2, Button3, Button4 クリックでも結果は同じ

リスト2
他のアプリの入力フォーカスがあるコントロールの文字列を取得
最初のサンプルで作成した GetFocusWindow メソッドを使用している
//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  String型を使用する例
//
//  String型はSetLengthで文字列の長さを設定する
//  Windows APIの関数によっては長さの設定が不要な場合もある
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : string;
begin
  //対象のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //受信バッファとしての文字列の領域(長さ)を設定
    //取得可能な文字数は(MAX_PATH-1)
    SetLength(LBuffer, MAX_PATH);
    //文字列を取得するメッセージを送る
    SendMessage(LRevHandle, WM_GETTEXT, Length(LBuffer), LPARAM(PChar(LBuffer)));

    //取得した文字列を本アプリのEdit1.Textにセット
    Edit1.Text := LBuffer;
    SetForegroundWindow(Handle);
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  PChar型で取得する例
//
//  StrAllocで文字列の長さを設定する(終端文字のnull分を含める必要あり)
//  StrAllocで設定した長さは領域はStrBufSizeで取得できる
//  StrAllocで設定した領域はStrDisposeで開放が必要
//  Windows APIの関数によっては長さの設定が不要な場合もある
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : PChar;
begin
  //対象のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //受信バッファとしての文字列の領域(長さ)を設定
    //取得可能な文字数は(MAX_PATH)
    LBuffer := StrAlloc(MAX_PATH + 1);
    try
      //文字列を取得するメッセージを送る
      SendMessage(LRevHandle, WM_GETTEXT, StrBufSize(LBuffer), LPARAM(LBuffer));

      //取得した文字列を本アプリのEdit1.Textにセット
      Edit1.Text := LBuffer;
      SetForegroundWindow(Handle);
    finally
      StrDispose(LBuffer);
    end;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  Char型の静的配列で取得する例
//
//  Char型の静的配列は,変数の定義の時に長さを設定する方式
//  Char型の静的配列は,FillChar手続きで初期化できる
//  ウインドウのタイトルやファイル名等,長さ制限のある文字列の取得に適している
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : array[0..MAX_PATH - 1] of Char;
begin
  //対象のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //バッファとしての文字列の初期化
    //null文字で埋めないと取得した文字列にゴミが入ることがある
    //取得可能な文字数は(MAX_PATH)
    FillChar(LBuffer, Sizeof(LBuffer), #0);
    //文字列を取得するメッセージを送る
    SendMessage(LRevHandle, WM_GETTEXT, Length(LBuffer), LPARAM(@LBuffer));

    //取得した文字列を本アプリのEdit1.Textにセット
    //Edit1.Text := string(LBuffer);とキャストすれば確実
    Edit1.Text := LBuffer;
    SetForegroundWindow(Handle);
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  Char型の動的配列で取得する例
//
//  Char型の動的配列はSetLengthで文字列の長さを設定する
//  String型へはstring,またはPCharでキャストすれば代入可能
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LhWindow   : HWND;
  LRevHandle : HWND;
  LBuffer    : array of Char;
begin
  //対象のトップウィンドウのハンドル
  LhWindow := FindWindow('TReceivAppForm', nil);

  if LhWindow <> 0 then begin
    //フォーカスのあるコントロールのハンドルを取得
    LRevHandle := Self.GetFocusWindow(LhWindow);

    //Char型動的配列の長さを設定
    //取得可能な文字数は(MAX_PATH-1)
    SetLength(LBuffer, MAX_PATH);
    //文字列を取得するメッセージを送る
    SendMessage(LRevHandle, WM_GETTEXT, Length(LBuffer), LPARAM(PChar(LBuffer)));

    //取得した文字列を本アプリのEdit1.Textにセット
    //Edit1.Text := PChar(LBuffer); でもOK
    Edit1.Text := string(LBuffer);
    SetForegroundWindow(Handle);
  end;
end;




03_メモ帳への文字列送信


次はメモ帳への文字列の送信です.同じく AttachThreadInput 関数を使用しています.
メモ帳の場合,メモ帳の編集コントロールであるエディタ部分が操作対象です.これは,最初のサンプルでは,フォームではなく,フォーム上に配置した TEdit が操作対象であるのと同じです.つまり,メモ帳というフォーム上の編集用のコントロールが操作対象です.メモ帳というのはアプリケーションの名前です.



[備考 1]
入力コントロールのハンドルを取得するのに,AttachThreadInput 関数を使用する必然性はありません.本サンプルで言えば,対象はメモ帳の編集ウインドウです.そのウインドウハンドルが取得できさえすれば手順は同じです.


[備考 2]
メモ帳が編集可能になるまで,すなわち,メモ帳の起動が完了するのを待つために,本サンプルでは Sleep 関数を使用しています.この方法は,メモ帳の起動が完了する前に,メモ帳に対して,何らかの操作をすると例外が発生することがあります.通常はこのような方法は使いません.以下のページの記事とサンプルを参考にしてください.
一般的に,アプリケーションが表示されても,直ちに操作可能になるとは限りません.


図6
起動時して [メモ帳起動] クリック
図7
Button2 をクリック
  • メモ帳に文字列がセットされる
  • Button3, Button4, Button5 クリックでも結果は同じ

リスト3
メモ帳への文字列送信
最初のサンプルで作成した GetFocusWindow メソッドを使用している
//=============================================================================
//  フォーム表示開始時の処理
//  テスト用の文字列をセット
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Button2.Enabled := False;
  Button3.Enabled := False;
  Button4.Enabled := False;
  Button5.Enabled := False;

  with Memo1.Lines do begin
    Clear;
    BeginUpdate;
    Add('Delphiはウインドウズ用の開発言語です');
    Add('開発言語は他にもいろいろあります');
    Add('山登りは苦しいけど,頂上に立った時は感激です');
    Add('このプログラムはDelphiで作成されています');
    Add('開発言語にはインタープリタというのもあります');
    Add('Delphi2009からDelphiはユニコード化されました');
    Add('文字列の扱いが違ってきて,コードの移植が重要です');
    EndUpdate;
  end;
end;

//=============================================================================
//  フォームを閉じる時の処理
//  テストなので終了時はメモ帳も強制的に閉じる
//  ただし,このアプリで起動したメモ帳のみ
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if FhNotepad <> 0 then begin
    SendMessage(FhNotepad, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  メモ帳を起動
//
//  (1)メモ帳を起動し,そのハンドルを取得
//  (2)起動直後は,メモ帳がアクティブなウィンドウ
//  (3)そのウィンドウで入力フォーカスがあるウインドウがメモ帳の編集ウィンドウ
//
//  アプリの起動完了を検出するにはShellExecuteEx,CreateProcess 関数等を使用する
//
//  ShellExecuteの使用にはusesにShellAPOが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShellExecute(Handle, '', PChar('Notepad.exe'), nil, nil, SW_SHOW);

  //起動完了待ちの代用
  //メモ帳が表示されても必ずもハンドルが有効になっているとは限らないので注意
  Sleep(2000);

  //メモ帳のハンドル
  FhNotepad := GetForegroundWindow;
  if FhNotepad > 0 then begin
    //受取る側のトップウィンドウのハンドルを取得.ここではメモ帳の編集ウィンドウ
    FhNotepadEdit := Self.GetFocusWindow(FhNotepad);

    Button2.Enabled := True;
    Button3.Enabled := True;
    Button4.Enabled := True;
    Button5.Enabled := True;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  メモ帳の例
//  送信用文字列としてString型を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //文字列を送る
  SendMessage(FhNotepadEdit, WM_SETTEXT, 0, LPARAM(PChar(Memo1.Text)));
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  メモ帳の例
//  送信用文字列としてPChar型を使用する例
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LBuffer : PChar;
begin
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //String型の文字列をChar型の静的配列の文字列に代入
  LBuffer := PChar(Memo1.Lines.Text);

  //文字列を送る
  SendMessage(FhNotepadEdit, WM_SETTEXT, 0, LPARAM(LBuffer));
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  メモ帳の例
//  送信用文字列としてChar型の静的配列を使用する例
//
//  設定した文字列の長さが不足すると例外が発生するので注意
//  このサンプルような場面での使い方はあまり考えられない
//  Char型の静的配列は,文字数に制限がある場合によく使用される
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LBuffer : array[0..MAXWORD] of Char;
begin
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //String型の文字列をChar型の静的配列の文字列に代入
  StrPCopy(LBuffer, Memo1.Lines.Text);

  //文字列を送る
  //先頭のアドレス指定なので@LBuffer[0]でもOK
  SendMessage(FhNotepadEdit, WM_SETTEXT, 0, LPARAM(@LBuffer));
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールに文字列をセットする
//  メモ帳の例
//  送信用文字列としてChar型の動的配列を使用する例
//
//  Char型の動的配列の場合は実行時に領域(サイズ)の確保が必要
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  LBuffer : array of Char;
begin
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //Char型配列の要素数を設定(null終端文字用に+1しておく)
  SetLength(LBuffer, Length(Memo1.Lines.Text) + 1); 
  //String型の文字列をChar型の動的配列の文字列に代入
  StrPCopy(PChar(LBuffer), Memo1.Lines.Text);

  //文字列を送る
  SendMessage(FhNotepadEdit, WM_SETTEXT, 0, LPARAM(PChar(LBuffer)));
end;


上のサシンプルコードは文字列の送信ですが,文字列ではなく,単一の文字であれば以下のように WM_CHAR メッセージで送信できます.



//=============================================================================
//  起動中のメモ帳に文字(文字列ではない)を送る
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhNotepad     : HWND;
  LhNotepadEdit : HWND;
begin
  //メモ帳のウィンドウハンドルを取得
  LhNotepad := FindWindow('Notepad', nil);
  if LhNotepad = 0 then begin
    MessageBox(Handle, 'メモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の編集ウィンドウのハンドルを取得
  LhNotepadEdit := FindWindowEx(LhNotepad, 0, 'Edit', nil);

  //そのウィンドウハンドルに文字を送信
  if LhNotepadEdit > 0 then begin
    PostMessage(LhNotepadEdit, WM_CHAR, Ord('日'), 0);;
    PostMessage(LhNotepadEdit, WM_CHAR, Ord('本'), 0);
  end;
end;




04_メモ帳からの文字列取得と文字列の長さの設定


次はメモ帳から文字列を取得する例です.同じく AttachThreadInput 関数を使用しています.
メモ帳の場合,メモ帳の編集コントロールであるエディタ部分が操作対象です.これは,最初のサンプルで,フォームではなく,フォーム上に配置した TEdit が操作対象であるのと同じです.つまり,メモ帳というフォーム上の編集用のコントロールが操作対象です.メモ帳というのはアプリケーションの名前です.

本サンプルでは,まず,WM_GETTEXTLENGTH メッセージでメモ帳のテキストの長さを取得します.その長さに応じて,メモ帳のテキストを取得するバッファとしての文字列の長さを設定してから,メモ帳のテキストを取得するようにしています.

設定する文字列の長さ,これは領域確保とか,サイズの設定とも言いますが,これが適切でないと例外が発生します.



[備考 1]
入力コントロールのハンドルを取得するのに,AttachThreadInput 関数を使用する必然性はありません.本サンプルで言えば,対象はメモ帳の編集ウインドウです.そのウインドウハンドルが取得できさえすれば手順は同じです.


[備考 2]
メモ帳が編集可能になるまで,すなわち,メモ帳の起動が完了するのを待つために,本サンプルでは Sleep 関数を使用しています.この方法は,メモ帳の起動が完了する前に,メモ帳に対して,何らかの操作をすると例外が発生することがあります.通常はこのような方法は使いません.以下のページの記事とサンプルを参考にしてください.
一般的に,アプリケーションが表示されても,直ちに操作可能になるとは限りません.


図8
起動時して [メモ帳起動] クリック
  • メモ帳にテキストを入力
図9
Button2 をクリック
  • メモ帳のテキストが TMemo にセットされる
  • Button3, Button4, Button5 クリックでも結果は同じ

リスト4
メモ帳からの文字列取得
最初のサンプルで作成した GetFocusWindow メソッドを使用している
//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Button2.Enabled := False;
  Button3.Enabled := False;
  Button4.Enabled := False;
  Button5.Enabled := False;
end;

//=============================================================================
//  フォームを閉じる時の処理
//  テストなので終了時はメモ帳も強制的に閉じる
//  ただし,このアプリで起動したメモ帳のみ
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if FhNotepad <> 0 then begin
    SendMessage(FhNotepad, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  メモ帳を起動
//
//  (1)メモ帳を起動し,そのハンドルを取得
//  (2)起動直後は,メモ帳がアクティブなウィンドウ
//  (3)そのウィンドウで入力フォーカスがあるウインドウがメモ帳の編集ウィンドウ
//
//  アプリの起動完了を検出するにはShellExecuteEx,CreateProcess 関数等を使用する
//
//  ShellExecuteの使用にはusesにShellAPOが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShellExecute(Handle, '', PChar('Notepad.exe'), nil, nil, SW_SHOW);

  //起動完了待ちの代用
  //メモ帳が表示されても必ずもハンドルが有効になっているとは限らないので注意
  Sleep(2000);

  //メモ帳のハンドル
  FhNotepad := GetForegroundWindow;
  if FhNotepad > 0 then begin
    //受取る側のトップウィンドウのハンドルを取得.ここではメモ帳の編集ウィンドウ
    FhNotepadEdit := Self.GetFocusWindow(FhNotepad);

    Button2.Enabled := True;
    Button3.Enabled := True;
    Button4.Enabled := True;
    Button5.Enabled := True;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  メモ帳の例
//  String型を使用する例
//
//  String型はSetLengthで文字列の長さを設定する
//  Windows APIの関数によっては長さの設定が不要な場合もある
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LBuffer  : string;
  LTextLen : Integer;
begin
  Memo1.Lines.Clear;
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の文字列の長さを調査
  LTextLen := SendMessage(FhNotepadEdit, WM_GETTEXTLENGTH, 0, 0);
  if LTextLen > 0 then begin
    //文字列格納用の領域を確保
    //+1は終端文字の分
    SetLength(LBuffer, LTextLen + 1);

    //メモ帳の編集ウィンドウに文字列を取得するメッセージを送る
    SendMessage(FhNotepadEdit, WM_GETTEXT, Length(LBuffer), LPARAM(PChar(LBuffer)));

    //取得した文字列を本アプリのMemo1にセット
    Memo1.Lines.Text := LBuffer;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  メモ帳の例
//  PChar型で取得する例
//
//  StrAllocで文字列の長さを設定する(終端文字のnull分を含める必要あり)
//  StrAllocで設定した長さは領域はStrBufSizeで取得できる
//  StrAllocで設定した領域はStrDisposeで開放が必要
//  Windows APIの関数によっては長さの設定が不要な場合もある
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LBuffer  : PChar;
  LTextLen : Integer;
begin
  Memo1.Lines.Clear;
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の文字列の長さを調査
  LTextLen := SendMessage(FhNotepadEdit, WM_GETTEXTLENGTH, 0, 0);
  if LTextLen > 0 then begin
    //受信バッファとしての文字列の領域(長さ)を設定
    //+1は終端文字の分
    LBuffer := StrAlloc(LTextLen + 1);
    try
      //文字列を取得するメッセージを送る
      SendMessage(FhNotepadEdit, WM_GETTEXT, StrBufSize(LBuffer), LPARAM(LBuffer));

      //取得した文字列を本アプリのMemo1にセット
      Memo1.Lines.Text := LBuffer;
    finally
      StrDispose(LBuffer);
    end;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  メモ帳の例
//  Char型の静的配列で取得する例
//
//  設定した文字列の長さが不足すると例外が発生するので注意
//  Char型の静的配列は変数の定義の時に長さを設定する方式
//  Char型の静的配列はFillChar手続きで初期化できる
//  ウインドウのタイトルやファイル名等,長さ制限のある文字列の取得に適している
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LBuffer  : array[0..MAXWORD] of Char;
  LTextLen : Integer;
begin
  Memo1.Lines.Clear;
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の文字列の長さを調査
  LTextLen := SendMessage(FhNotepadEdit, WM_GETTEXTLENGTH, 0, 0);
  if LTextLen > 0 then begin
    //バッファとしての文字列の初期化
    //null文字で埋めないと取得した文字列にゴミが入ることがある
    //実際に取得可能な文字数は(MAXWORD)まで
    FillChar(LBuffer, Sizeof(LBuffer), #0);

    //メモ帳の編集ウィンドウに文字列を取得するメッセージを送る
    SendMessage(FhNotepadEdit, WM_GETTEXT, Length(LBuffer), LPARAM(@LBuffer));

    //取得した文字列を本アプリのMemo1にセット
    //Memo1.Lines.Text := string(LBuffer);とキャストすれば確実
    Memo1.Lines.Text := LBuffer;
  end;
end;

//=============================================================================
//  他のアプリのフォーカスのあるコントロールの文字列を取得する
//  Char型の動的配列で取得する例
//
//  Char型の動的配列はSetLengthで文字列の長さを設定する
//  String型へはstring,またはPCharでキャストすれば代入可能
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  LBuffer  : array of Char;
  LTextLen : Integer;
begin
  Memo1.Lines.Clear;
  //メモ帳の編集ウィンドウのハンドルを確認
  if not IsWindowEnabled(FhNotepadEdit) then begin
    MessageBox(Handle, '操作可能なメモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の文字列の長さを調査
  LTextLen := SendMessage(FhNotepadEdit, WM_GETTEXTLENGTH, 0, 0);
  if LTextLen > 0 then begin
    //文字列格納用の領域を確保
    //+1は終端文字の分
    SetLength(LBuffer, LTextLen + 1);

    //メモ帳の編集ウィンドウに文字列を取得するメッセージを送る
    SendMessage(FhNotepadEdit, WM_GETTEXT, Length(LBuffer), LPARAM(PChar(LBuffer)));

    //取得した文字列を本アプリのMemo1にセット
    //Memo1.Lines.Text := PChar(LBuffer); でもOK
    Memo1.Lines.Text := string(LBuffer);
  end;
end;




05_起動中のメモ帳への文字列送信と文字列取得


上のサンプルは,自分自身でメモ帳を起動しました.
本サンプルでは,すでに起動中のメモ帳に対して操作します.そのため,メモ帳のクラス名をを使用して,起動している最初のメモ帳のウィンドウハンドルを取得します.そのメモ帳のウィンドウハンドルから,メモ帳の編集ウィンドウのハンドルを取得します.
メモ帳の編集ウィンドウのクラス名は Edit です.

操作対象のウィンドウハンドルが取得できれば,手順はこれまでのサンプルと同じです.そこで,本項のサンプルは,String 型を使用した例だけとしています.



図10
実行時画面
  • メモ帳を起動してからボタンをクリック

リスト5
起動中のメモ帳への文字列送信と文字列取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  テスト用の文字列をセット
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  with Memo1.Lines do begin
    Clear;
    BeginUpdate;
    Add('Delphiはウインドウズ用の開発言語です');
    Add('これはテスト用の文字列です');
    EndUpdate;
  end;
end;

//=============================================================================
//  起動中のメモ帳に文字列を送る
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhNotepad     : HWND;
  LhNotepadEdit : HWND;
begin
  //メモ帳のウィンドウハンドルを取得
  LhNotepad := FindWindow('Notepad', nil);
  if LhNotepad = 0 then begin
    MessageBox(Handle, 'メモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の編集ウィンドウのハンドルを取得
  LhNotepadEdit := FindWindowEx(LhNotepad, 0, 'Edit', nil);

  if LhNotepadEdit <> 0 then begin
    //そこにテキストを送る
    SendMessage(LhNotepadEdit, WM_SETTEXT, 0, LPARAM(PChar(Memo1.Text)));
  end;
end;

//=============================================================================
//  起動中のメモ帳からテキストを取得
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  FhNotepad     : HWND;
  FhNotepadEdit : HWND;
  LTextLen      : Integer;
  LBuffer       : string;
begin
  //メモ帳のウィンドウハンドルを取得
  FhNotepad := FindWindow('Notepad', nil);
  if FhNotepad = 0 then begin
    MessageBox(Handle, 'メモ帳が起動していません', '情報', MB_OK);
    exit;
  end;

  //メモ帳の編集ウィンドウのハンドルを取得
  FhNotepadEdit := FindWindowEx(FhNotepad, 0, 'Edit', nil);

  //メモ帳の文字列の長さを調査
  LTextLen := SendMessage(FhNotepadEdit, WM_GETTEXTLENGTH, 0, 0);
  if LTextLen > 0 then begin
    //文字列格納用の領域を確保
    SetLength(LBuffer, LTextLen + 1);

    //メモ帳の編集ウィンドウに文字列を取得するメッセージを送る
    SendMessage(FhNotepadEdit, WM_GETTEXT, Length(LBuffer), LPARAM(PChar(LBuffer)));

    //取得した文字列を本アプリのMemo1にセット
    Memo1.Lines.Text := LBuffer;
  end;
end;

end.




06_直前にアクティブだったウィンドウに対する文字列の送受信


ここまでのサンプルは,ウィンドウハンドルを持つコントロールが対象でした.
一方,ウィンドウハンドルを持たないコントロールに対しては,メッセージを使用しての文字列送信や取得はできません.例えば,ブラウザの入力欄もこれに相当します.
以下のサンプルは,このような入力コントロールに対する文字列の送受信のテストです.

この操作には,クリップボードを利用することで可能になります.
  • (1) 対象のコントロールにマウスクリック等でフォーカスを移動.つまり入力状態にする
    これは,対象のコントロールの親ウィンドウが最前面のウィンドウとなることを意味する
  • (2) 自アプリをアクティブなウィンドウにする.つまりフォーカスを移動する
  • (3) 直前にアクティブだったウィンドウを探す.これは対象のコントロールの親ウィンドウ
  • (4) そのウィンドウをアクティブにして,コピーや貼り付けのキーイベントの操作を実行する

という手順で行います.
これは,フォーカスのあるオブジェクトは,システム全体でキーボード 入力を受けつける唯一のオブジェクトであることを利用しています.キーボードフォーカスのあるオブ ジェクトは,アクティブ ウィンドウまたはアクティブ ウィンドウの子オブジェクトです.そのウィンドウに対してキーイベントの操作を実行すれば,そのウィンドウの,入力フォーカスのあるオブジェクトが受け取ります.オブジェクトを所有するウィンドウに,SendMessage でキーコードを送信しても該当操作はできません.

ブラウザの入力欄はウィンドウではない (ウィンドウハンドルを持たない) オブジェクトです.したがって,当然ですが,上のサンプルのように SendMessage 等のメッセージを使用した文字列の操作はできません.SendMessage の第 1 引数は,操作対象のウィンドウハンドルの値です.

他のアプリに対して,コピー操作 ([Ctrl] + [C]) を実行し,自アプリで,クリップボードの内容を取得する場合,このサンプルではテキストですが,


    Memo1.Text := Clipboard.AsText;

のように直接取得するコードにすると,
   「クリップボードアクセスが拒否されました.を開けません」
と, 下図のようにエラーが発生して,取得できないことがあります.この現象は,自アプリを起動して,最初に他のアプリに対して [Ctrl] + [C] の操作を実行した場合は発生しません.その後,他のクリップボードの操作を行った後,再度,他のアプリに対して同じ操作を実行すると発生します.
この現象に対する対処方法がマイクロソフトの記事にありますが,

    ≪このエラーを解決するには
      クリップボードを使用しているほかのプログラムを終了して、やり直します。≫

とあります.
しかし,残念ながら,そういうわけにはいきません (現在,この記事は消失しています).


図11
開けません


このサンプルでは,クリップボードの監視メッセージの中で,この Clipboard.AsText を実行してクリップボードのデータを取得しています.
クリップボードを監視していると,他のアプリへの送信の際にも,その内容を取得してしまいますので,フラグを使用して,他のアプリから取得する時だけ,クリップボードからテキストデータを取得するようにしています.

以下の図は,このサンプルの実行例です.当サイトのトップページを Fire Fox で表示し,Google の検索欄に対して操作しています.



[備考]
ウェブブラウザの入力欄等は HTML コードで作成されています.このような 入力欄に対する操作は,以下のリンクの記事のように,専用の方法があります.


図12
設計時画面

図13
送信
  • 他のアプリの,入力フォーカスがあるオブジェクトに文字列がセットされる
  • 本サンプルでは,カーソル位置への挿入
図14
受信
  • 他のアプリの,入力フォーカスがあるオブジェクトの文字列を取得して表示
  • 本サンプルでは,カーソル位置への挿入

リスト6
直前にアクティブであったウィンドウに対する送受信
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Clipbrd;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  protected
    procedure WMDrawClipboard(var Message: TWMDrawClipboard);
      message WM_DRAWCLIPBOARD;
  private
    { Private 宣言 }
    FhNextClipboardViewer : HWND;
    FReceiveFlag : Boolean;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  テスト用の文字列をセット
//  クリップボードの監視を開始
//  flagReceive の値は,他のアプリに [Ctrl] + [C] の操作をする直前に True にする
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FReceiveFlag := False;
  // クリップボードビューアとして登録
  FhNextClipboardViewer := SetClipboardViewer(Self.Handle);

  Memo1.Text := '文字列送信テスト';
end;

//=============================================================================
//  フォームの OnDestroy 処理
//  クリップボードのチェーンからこのフォームを削除する
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  ChangeClipboardChain(Self.Handle, FhNextClipboardViewer);
end;

//=============================================================================
//  直前にアクティブだったウィンドウに文字列を送信
//  入力フォーカスがあったコントロールが文字列を受信する
//
//  uses に Clipbrd が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhWindow : HWND;
  LKeyByte : Byte;
begin
  // テスト用の文字列をクリップボードに格納
  Clipboard.AsText := Memo1.Text;

  // 直前にアクティブだったウィンドウを取得
  LhWindow := Handle;
  LhWindow := GetNextWindow(LhWindow, GW_HWNDNEXT);
  while not IsWindowVisible(LhWindow) do begin
    LhWindow := GetNextWindow(LhWindow, GW_HWNDNEXT);
  end;

  //見つかったらそのウィンドウを前面にもってくる
  SetForegroundWindow(LhWindow);

  // [Ctrl] + [V] のキー操作
  LKeyByte := Ord('V');

  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);
end;

//=============================================================================
//  直前にアクティブだったウィンドウの文字列を取得
//  入力フォーカスがあったコントロールの文字列を取得する
//
//  uses に Clipbrd が必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LhWindow : HWND;
  LKeyByte : Byte;
  LKeyFlag : Cardinal;
begin
  // クリップボード監視を有効にする
  FReceiveFlag := True;

  // 直前にアクティブだったウィンドウを取得
  LhWindow := Handle;
  LhWindow := GetNextWindow(LhWindow, GW_HWNDNEXT);
  while not IsWindowVisible(LhWindow) do begin
    LhWindow := GetNextWindow(LhWindow, GW_HWNDNEXT);
  end;

  // 見つかったらそのウィンドウを前面にもってくる
  SetForegroundWindow(LhWindow);

  // [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);
  Application.ProcessMessages;

  // [Ctrl] +[C] のキー操作 コピー
  LKeyByte := Ord('C');

  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);
  Application.ProcessMessages;
  Sleep(100);

  // [HOME] のキー操作 選択解除のつもり
  LKeyFlag := KEYEVENTF_KEYUP or KEYEVENTF_EXTENDEDKEY;
  keybd_event(VK_HOME, MapVirtualKey(VK_HOME, 0), 0, 0);
  keybd_event(VK_HOME, MapVirtualKey(VK_HOME, 0), LKeyFlag, 0);
end;

//=============================================================================
//  クリップボードビューアとしての監視
//  クリップボードにデータが発生した時の処理
//=============================================================================
procedure TForm1.WMDrawClipboard(var Message: TWMDrawClipboard);
begin
  if not FReceiveFlag then exit;

  if (Clipboard.HasFormat(CF_TEXT)) then begin
    Memo1.Text  := Clipboard.AsText;
    SetForegroundWindow(Handle);
    FReceiveFlag := False;
  end;
end;

end.