Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2015年06月17日] [更新 2015年06月21日] Delphi 一般・その他
   
ウィンドウハンドルとアプリの起動
動作確認等 Windows 8.1 Pro64 + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0140]







01_ウィンドウハンドルを使用した簡単なテスト


次のコードを作成します.
これは,新規に作成したプロジェクトに,TButton を 2 つ配置してそのボタンのクリックイベントを作成しています.
EXE を起動してフォームを表示する時に Button2 のウィンドウハンドルの値を取得するようになっています.Button1 をクリックすると,取得しておいた Button2 のウィンドウハンドルの値を使用して,コードで Button2 をクリックするというテストです.


リスト1
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 Button2Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    FBtnHandle : HWND;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  Button2のウィンドウハンドルを取得しておく
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  FBtnHandle := Button2.Handle;
end;

//=============================================================================
//  Button2のウィンドウに対してメッセージを送る
//  ここではボタンクリックのメッセージ(BM_CLICK)とする
//  ウィンドウは,ウィンドウハンドルの値で識別する
//  取得しておいたButton2のウィンドウハンドルの値を使用する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMessage(FBtnHandle, BM_CLICK, 0, 0);
end;

//=============================================================================
//  Button2のOnClickイベントハンドラ
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  ShowMessage('クリックされた !!');
end;

end.


コンパイルして実行します.
Button1 をクリックすると,自動で Button2 のクリックイベントが実行され,下図のようにダイアログが表示されます.
ダイアログを閉じて,また Button1 をクリックすると同じダイアログが現れます.

EXE,つまりアプリケーションを起動して,ウィンドウコントロールが生成されると,そのコントロールのハンドルの値が自動で割り当てられます.これは Windows のシステムが決めています.このウィンドウハンドルの値は他のコントロールの値と同じになることはありません.つまり,ウインドウハンドルの値でコントロールを一意的に識別できるわけです.
ウィンドウハンドルは,ウィンドウを一意的に識別するための値です.





アプリの起動中に,対象のウィンドウを再生成しない限り,このウィンドウハンドルの値が変わることはありません.また,変更することもできません.したがって,Button1 をクリックすれば,最初に取得していた Button2 のウィンドウハンドルを使用して,何回でも自動で Button2 をクリックすることができるわけです.

対象のアプリケーションを閉じて,そのアプリケーションを再起動すると,ウィンドウハンドルは新しい異なる値になります.ウィンドウハンドルの値は,そのウィンドウ (ウィンドウコントロール) が生成される時に割り当てられます.
Delphi では,TButton, TPanel 等がウィンドウハンドルを持つコントロールです.TImage 等にはウィンドウハンドルはありません.

注意することがあります.他のアプリの場合,このテストの方法のように,そのアプリのウィンドウコントロールの操作が常に可能であるとは限りません.他のアプリの操作には制限があります.特に Widows Vista 以降では,この制限が厳しくなっています.
また,アプリケーションによっては,他のアプリ,プログラムからは操作できないようにしてある場合もあります.



[備考 1]
Delphi のコントロール,つまりコンポーネントの内,ウィンドウハンドルを持つコンポーネントには,ウインドウハンドルを取得する Handle というプロパティがあります.
ウィンドウハンドルの値は,32 ビットのアプリでは 32 ビットの整数値,64 ビットのアプリでは 64 ビットの整数値です.
NativeUInt, LongWord あるいは ウィンドウハンドルの型専用の HWND または THandle を使用しておくと,32 ビットの EXE を作成すると自動的に 32 ビットの値に,64 ビットの EXE を作成すると自動的に 64 ビットの値になります (プラットフォーム依存).古い Delphi のプログラムでは Cardinal 型を使用しているコードがあります.これは常に 32 ビットの値となります (プラットフォーム非依存).


[備考 2]
車の操作には「ハンドル」が必要です.Windows のウィンドウは,そのウィンドウのハンドルの値を使用して操作します.



Dephi XE5(UP2) Pro における HWND と THandle の定義は以下のように,元はどちらも NativeUint 型となっています.HWND は主に Windows API でウィンドウハンドルを扱う時,THandle は Delphi のコントロール類のハンドル値に使われています.
ウィンドウハンドルの変数に NativeUInt (符号なし整数型) ではなく,HWND や THandle を使用しておけば,その変数がウインドウハンドルを扱っていることが明確になります.もちろん,NativeUInt 等を使用してはいけないという規則はありません.
Windows API の関数では UINT_PTR 等も使われています.当サイトのサンプルでも,Windows API の定義をそのまま利用して UINT_PTR を使用しているコードがあります.


  HWND = type UINT_PTR;
  UINT_PTR = System.UIntPtr; 
  System.UIntPtr = NativeUInt;

  THandle = System.THandle;
  System.THandle = NativeUInt;




02_Windows に付属のメモ帳や電卓の起動


Windows に付属している [アクセサリ] の電卓やメモ帳は,例えば次のように,ShellExecute 関数を使用して起動できます.
一般的に,他のアプリを起動するには,そのアプリの EXE ファイルのフルパスを指定する必要がありますが,電卓やメモ帳は,フルパスでなくても起動することができます.ただし,これは,起動に使用する関数や,その関数の使用方法にもよります.
メモ帳を起動する時に,開くファイルを指定して起動することもできます.そのサンプルが本ページの最後の方にあります.



リスト2
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 Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI;  //Delphi XE以前の場合は ShellAPI

{$R *.dfm}

//=============================================================================
//  Windows付属のメモ帳を起動する
//  メモ帳のプログラム名はnotepad.exe
//  本当はフルパス(C:\..... \notepad.exe)にすべきであるが
//  メモ帳の場合はnotepad.exeで起動できる
//
//  ShellExecuteの使用にはusesにWinapi.ShellAPIが必要(Delphi XE5の場合)
//  ShellExecuteの使用にはusesにShellAPIが必要(Delphi XE以前の場合)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShellExecute(Handle, '', 'notepad.exe', nil, nil, SW_SHOW);
end;

//=============================================================================
//  Windowsに付属の電卓を起動する
//  電卓のプログラム名はcalc.exe
//  本当はフルパス(C:\..... \calc.exe)にすべきであるが
//  電卓の場合はcalc.exeで起動できる
//
//  ShellExecuteの使用にはusesにWinapi.ShellAPIが必要(Delphi XE5の場合)
//  ShellExecuteの使用にはusesにShellAPIが必要(Delphi XE以前の場合)
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  ShellExecute(Handle, '', 'calc.exe', nil, nil, SW_SHOW);
end;

end.


下図は,Button1 をクリックしてメモ帳を起動したところです.



下図は,Button2 をクリックして電卓を起動したところです.



[備考 1]
64 ビット版の Windows 7, Windows 8.1 には 32 ビット版と 64 ビット版の,2 つのメモ帳と電卓があります.32 ビットの EXE からは 32 ビット版のメモ帳または電卓が起動します.64 ビットの EXE からは 64 ビット版のメモ帳または電卓が起動します.


[備考 2]
メモ帳や電卓はシステム的にパスが通った場所 (ディレクトリ) にあります.システム的にパスが通った場所にあるプログラムやファイルはディレクトリ名を省略して開くことができます.
このシステム的にパスが通った場所は環境変数の %PATH% で確認できます.





03_Windows に付属のメモ帳や電卓が起動しているか調べる


Windows に付属している [アクセサリ] の電卓やメモ帳が起動しているかを調べる例です.
このサンプルでは,対象のクラス名のウィンドウハンドルを FindWindow 関数で調べます.対象のウィンドウが起動していれば,FindWindow の戻り値は,そのウィンドウハンドルの値となります.

一般的に,他のアプリを操作する際には,対象となるウィンドウハンドルの値が必要ですが,目的のウィンドウのハンドルが,常に FindWindow 関数で取得できるとは限りません.




リスト3
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 Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1; 

implementation

{$R *.dfm}

//=============================================================================
//  Windows付属のメモ帳が起動しているか確認
//  ウィンドウのクラス名で確認する方法
//  メモ帳のウィンドウのクラス名はNotepad
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LClassName : String;
  LHandle    : HWND;
begin
  LClassName := 'Notepad';
  LHandle := FindWindow(PChar(LClassName), nil);

  if LHandle = 0 then begin
    ShowMessage('メモ帳は起動していません');
  end else begin
    ShowMessage('メモ帳は起動しています');
  end;
end;

//=============================================================================
//  Windows付属の電卓が起動しているか確認
//  ウィンドウのクラス名で確認する方法
//  電卓のウィンドウのクラス名はCalcFrame
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LClassName : String;
  LHandle    : HWND;
begin
  LClassName := 'CalcFrame';
  LHandle := FindWindow(PChar(LClassName), nil);

  if LHandle = 0 then begin
    ShowMessage('電卓は起動していません');
  end else begin
    ShowMessage('電卓は起動しています');
  end;
end;

end.


[備考]
メモ帳等,他のアプリの操作には,そのアプリのウインドウハンドルの値が必要です.ウィンドウハンドルは,一般的にそのウィンドウのクラス名を元に取得する方法が使われています. メモ帳のクラス名は Notepad です.Delphi で新規にプロジェクトを作成するとメインフォームかできます.この時のフォームのクラス名は TForm1 です.
FindWindow 関数では,第 2 引数のタイトル名でもウィンドウの検索ができます.このタイトルは,Delphi のフォームで言えば,フォームの Caption プロパティの値のことです. アプリケーションあるいはウィンドウのクラス名は,そのアプリの開発者が決めています.一般的に公開されているものではありません.クラス名を調べる方法は少し面倒です.メモ帳や電卓等,代表的なアプリケーションであればネット上に情報があります.




04_Windows に付属のメモ帳や電卓が起動していたら閉じる


Windows に付属している [アクセサリ] の電卓やメモ帳が起動していたら閉じる例です.
電卓またはメモ帳のウィンドウハンドルに対して,WM_CLOSE というメッセージを送信します.メッセージの送信には PostMessage という関数を使用します.

電卓やメモ帳は閉じることができますが,WM_CLOSE メッセージでは閉じないように設計しているアプリケーションもあります.そのようなアプリケーションは WM_CLOSE メッセージを送信しても閉じることはできません.その場合は,他の方法を使用します.



リスト4
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 Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  Windows付属のメモ帳が起動しているか確認
//  起動していたら閉じる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LClassName : String;
  LHandle    : HWND;
begin
  LClassName := 'Notepad';
  LHandle := FindWindow(PChar(LClassName), nil);

  if LHandle = 0 then begin
    ShowMessage('メモ帳は起動していません');
  end else begin
    PostMessage(LHandle, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  Windows付属の電卓が起動しているか確認
//  起動していたら閉じる
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LClassName : String;
  LHandle    : HWND;
begin
  LClassName := 'CalcFrame';
  LHandle := FindWindow(PChar(LClassName), nil);

  if LHandle = 0 then begin
    ShowMessage('電卓は起動していません');
  end else begin
    PostMessage(LHandle, WM_CLOSE, 0, 0);
  end;
end;

end.


[備考 1]
メッセージをウィンドウに送信するのに PostMessage という関数を使用していますが,メッセージの送信には SendMessage という関数もあります.どちらの関数を使用するかはメッセージの種類,あるいは処理内容によって異なります.


[備考 2]
PostMessage 関数,SendMessage 関数のパラメータと戻り値は,第 2 引数のメッセージの種類によって異なります.それらの意味と内容は Windows API 関係の記事で調べることになります.





05_エクセルが起動していたら閉じる


起動しているエクセルを閉じるには,上のコードの LClassName の値をエクセルの値に変更します.



リスト5
//=============================================================================
//  エクセルが起動しているか確認
//  起動していたら閉じる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LClassName : String;
  LHandle    : HWND;
begin
  LClassName := 'XLMAIN';
  LHandle := FindWindow(PChar(LClassName), nil);

  if LHandle = 0 then begin
    ShowMessage('エクセルは起動していません');
  end else begin
    PostMessage(LHandle, WM_CLOSE, 0, 0);
  end;
end;




06_自アプリで起動した他のアプリケーションを閉じる


上のサンプルではメモ帳や電卓,あるいはエクセルを起動するために ShellExecute という関数を使用しています.
自分のプログラムからこれらのアプリ,あるいは他のアプリを起動して,そのアプリだけを閉じるようにするためには,他のアプリケーションを起動する時に ShellExecuteEx 関数や CreateProcess 関数を使用して少し高度な「技」を使います.以下の記事にサンプルコードを掲載しています.






07_メモ帳を起動してテキストファイルを表示する


メモ帳を起動するだけでなく,起動したメモ帳でテキストファイルを表示することもできます.
以下のように,ShellExecute 関数の第 4 引数に,表示するテキストファイルのフルパスを PChar 型で指定します.



リスト6
//=============================================================================
//  Windows付属のメモ帳を起動してテキストファイルを表示する
//  メモ帳のプログラム名はnotepad.exe
//  本当はフルパス(C:\..... \notepad.exe)にすべきであるが
//  メモ帳の場合はnotepad.exeで起動できる
//
//  ShellExecuteの使用にはusesにWinapi.ShellAPIが必要(Delphi XE5の場合)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath : string;
begin
  LFilePath := 'D:\000\テスト用ファイル.txt';
  ShellExecute(Handle, '', 'notepad.exe', PChar(LFilePath), nil, SW_SHOW);
end;




08_Windows API と Windows API で使用する文字列


本ページで使用している ShellExecute, FindWindow, PostMessage 等は Delphi のオリジナルの関数類ではありません.Windows API の関数です.
Windows API は Windows が提供している機能の総称ですが,Windows が提供している関数類のことを指すこともあります.当サイトの記事には Windows API の関数,関数の引数についての説明は基本的に掲載していません.記事中の参考リンクや検索エンジンで確認してください.

Windows API や C, C++ で使用している文字列は,文字列の最後を示す文字が null (#0) であることから null 終端文字列 (ナル終端文字列あるいはヌル終端文字列)と呼ばれています.
Delphi で null 終端文字列を扱うには,文字列を PChar 型で定義するか,string 型の文字列変数を PChar でキャスト (型変換) します.Windows API の関数の文字列引数に文字列を直接与える時は PChar でキャストする必要はありません.Delphi が自動で処理してくれます.
Windows API の関数で文字列を扱う場合,Char 型の文字配列を使用することもあります.Char 型は 1 文字の変数の型です.



[備考]
#0 はコードポイント (文字コード) の値が 0 (ゼロ) である文字,つまり Null (ヌル,ナル) 文字を意味します.$0 と書くと 16 進数で表示した数値の 0 (ゼロ) を意味します.単に 0 と書くと 10 進数表示の数値のゼロです.
例えば以下を実行するとどちらも'東'という文字を表示します.東という文字のコードポイントの値は 10 進数では 26481 で,16 進数では 6771 だからです.
    ShowMessage(#26481);
    ShowMessage(#$6771);

Null 文字や改行文字,あるいは TAB 文字 (タブ文字) は,画面では文字として表示できない「文字」です.これらの文字は ASCII 制御文字 (アスキー制御文字) と呼ばれています,例えば
    ShowMessage(#0);
を実行しても何の文字も表示しません.