Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2012年03月08日] [更新 2015年04月28日] Delphi サンプルプログラム集
645_ウィンドウの階層のツリー表示
動作確認等 Windows XP(SP3) + Delphi XE Pro
Windows 7 U64(SP1) + Delphi XE Pro
645_WindowTree.zip [1,907 KB] 2012年05月23日版 (EXE 同梱)



  • 2012年03月18日
  • システム関係ダイアログの場合の記事を追加
  • 2012年05月10日
  • マイクロソフト提供ツールの紹介記事を追加
  • クラス名をコピーする機能を付けた
  • 2012年05月23日
  • 対象ウィンドウを囲む枠線を表示するサンプルを追加
  • 2015年04月28日
  • ツール関係の記事を [679_他のアプリの操作とUI Automation 関係インターフェイス] に移動




01_ウィンドウの階層をツリー表示してクラス名を調べる


Halbow 資料館にある同名のサンプルを,Delphi 2009 以降の開発環境でも使用可能にしたものです.
実際には,そのサンプルで使用している Windows の情報を取得する関数類の修正だけですが,コードを少し整備して,リサイズ等の機能を追加しています.関数類の名前は一切変更していません.

このプログラムは機能も単純で,しかもリアルタイムで情報を取得するわけではありませんが,筆者は結構重宝しています.特に,起動中のアプリやプログラムのウインドウのクラス名を調べたり,ダイアログのコントロールの ID 値を調べる時に使用しています.

コードの詳しい説明は,Halbow 資料館の記事を参考にしてください.



図1
設計時画面
図2
実行画面例
  • 自ウィンドウを選択
  • マウスの右ボタンクリックのメニューでクラス名のコピーが可能

リスト1
プロジェクトのソースコード
MainFormOnTaskbar を False にする
program Project1;

uses
  Forms,
  Main in 'Main.pas' {Form1},
  WinSearch in 'WinSearch.pas';

{$R *.RES}

begin
  Application.Initialize;
  //Delphi2007以上
  {$IF CompilerVersion > 18.49}
  Application.MainFormOnTaskbar := False;
  {$IFEND}
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


リスト2
ウィンドウの階層をツリー表示
unit Main;

interface

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

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    Splitter1: TSplitter;
    Panel3: TPanel;
    Panel1: TPanel;
    ListView1: TListView;
    Panel2: TPanel;
    Splitter2: TSplitter;
    Panel6: TPanel;
    Panel4: TPanel;
    ListView2: TListView;
    Panel5: TPanel;
    Label3: TLabel;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private 宣言 }
    IniFileName : String;
  public
    procedure EnumChildren(Node:TTreeNode);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses WinSearch, IniFiles;

//=============================================================================
//  本フォーム作成時の処理
//  情報表示用のTTreeViewとTListViewの位置とサイズの設定値をiniファイルから読み
//  出して設定
//
//  このサンプルプログラムは,Project1.dprのMainFormTaskbarを
//  Application.MainFormOnTaskbar := False;
//  としないと,アプリケーションウィンドウだけを単独検出してしまうので注意
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);

  //---------------------------------------------------------------------------
  procedure ListViewColumnAdd(LV: TListView; ACaption: String; AWidth: Integer);
  var
    LVColumn : TListColumn;
  begin
    LVColumn := LV.Columns.Add;
    LVColumn.Caption := ACaption;
    LVColumn.Width   := AWidth;
  end;
  //---------------------------------------------------------------------------

var
  AIni           : TInifile;
  FormTop        : Integer;
  FormLeft       : Integer;
  FormWidth      : Integer;
  FormHeight     : Integer;
  InfoPaneWidth  : Integer;
  ClassItemWidth : Integer;
  ClassExpWidth  : Integer;
  WinItemWidth   : Integer;
  WinExpWidth    : Integer;
begin
  ListView1.ViewStyle := vsReport;
  ListView1.RowSelect := True;
  ListView2.ViewStyle := vsReport;
  ListView2.RowSelect := True;

  FormTop        := 0;
  FormLeft       := 0;
  FormWidth      := 0;
  FormHeight     := 0;
  InfoPaneWidth  := 0;
  ClassItemWidth := 0;
  ClassExpWidth  := 0;
  WinItemWidth   := 0;
  WinExpWidth    := 0;

  IniFileName := ChangeFileExt(ParamStr(0),'.ini');
  AIni := TIniFile.Create(IniFileName);
  try
    try
      FormTop        := AIni.ReadInteger('PosSize', 'FormTop',        0);
      FormLeft       := Aini.ReadInteger('PosSize', 'FormLeft',       0);
      FormWidth      := Aini.ReadInteger('PosSize', 'FormWidth',      0);
      FormHeight     := Aini.ReadInteger('PosSize', 'FormHeight',     0);
      InfoPaneWidth  := Aini.ReadInteger('PosSize', 'InfoPaneWidth',  0);
      ClassItemWidth := Aini.ReadInteger('PosSize', 'ClassItemWidth', 0);
      ClassExpWidth  := Aini.ReadInteger('PosSize', 'ClassExpWidth',  0);
      WinItemWidth   := Aini.ReadInteger('PosSize', 'WinItemWidth',   0);
      WinExpWidth    := Aini.ReadInteger('PosSize', 'WinExpWidth',    0);
    except
    end;
  finally
    FreeAndNil(AIni);
  end;

  if (FormWidth * FormHeight) = 0 then begin
    Self.Position  := poMainFormCenter;
  end else begin
    Self.Position := poDesigned;
    Self.Top      := FormTop;
    Self.Left     := FormLeft;
    Self.Width    := FormWidth;
    Self.Height   := FormHeight;
  end;
  if InfoPaneWidth <> 0 then Panel3.Width   := InfoPaneWidth;
  if ClassItemWidth = 0 then ClassItemWidth := 90;
  if ClassExpWidth  = 0 then ClassExpWidth  := 140;
  if WinItemWidth   = 0 then WinItemWidth   := 90;
  if WinExpWidth    = 0 then WinExpWidth    := 140;

  ListViewColumnAdd(ListView1, ' 項   目', ClassItemWidth);
  ListViewColumnAdd(ListView1, ' 内   容', ClassExpWidth);
  ListViewColumnAdd(ListView2, ' 項   目', WinItemWidth);
  ListViewColumnAdd(ListView2, ' 内   容', WinExpWidth);
end;

//=============================================================================
//  フォームを閉じる時はiniファイルへ位置やサイズを保存
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  AIni : TInifile;
begin
  AIni := TIniFile.Create(IniFileName);
  try
    try
      AIni.WriteInteger ('PosSize', 'FormTop',        Self.Top);
      Aini.WriteInteger ('PosSize', 'FormLeft',       Self.Left);
      Aini.WriteInteger ('PosSize', 'FormWidth',      Self.Width);
      Aini.WriteInteger ('PosSize', 'FormHeight',     Self.Height);
      Aini.WriteInteger ('PosSize', 'InfoPaneWidth',  Panel3.Width);
      Aini.WriteInteger ('PosSize', 'ClassItemWidth', ListView1.Column[0].Width);
      Aini.WriteInteger ('PosSize', 'ClassExpWidth',  ListView1.Column[1].Width);
      Aini.WriteInteger ('PosSize', 'WinItemWidth',   ListView2.Column[0].Width);
      Aini.WriteInteger ('PosSize', 'WinExpWidth',    ListView2.Column[1].Width);
    except
    end;
  finally
    FreeAndNil(AIni);
  end;
end;

//=============================================================================
//  トップレベルのウィンドウを取得してツリービューに表示
//  この時点では子ウィンドウ等は検索しない
//  各ノードにウィンドウハンドルを代入しておく
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  WA       : THWndArray;
  i        : Integer;
  Num      : Integer;
  StrItem  : String;
  StrClass : String;
  StrText  : String;
begin
  TreeView1.Items.Clear;
  Num := WinSearch.EnumVisbleNoOwnedWindows(WA);
  Label3.Caption := IntToStr(Num);

  if Num > 0 then begin
    for i := 1 to Num do begin
      StrClass := WinSearch.GetClassNameStr(WA[i]);
      StrText  := WinSearch.GetWindowTextStr(WA[i]);
      StrItem  := Format('%s ...... %s', [StrClass, StrText]);

      TreeView1.Items.AddObject(nil, StrItem, Pointer(WA[i]));
    end;
  end;
end;

//=============================================================================
//  引数のノードに格納してあるウィンドウハンドルのウィンドウの全ての子ウィンド
//  ウを,この関数自身を再帰的に呼び出して(再帰処理)ツリービューに階層表示
//=============================================================================
procedure TForm1.EnumChildren(Node: TTreeNode);
var
  WA      : THWndArray;
  i       : Integer;
  Num     : Integer;
  S       : String;
  TN      : TTreeNode;
  StrItem : String;
begin
  if Node.Count > 0 then exit;

  Num := WinSearch.EnumDirectChildren(WA, HWND(Node.Data));
  if Num > 0 then begin
    for i := 1 to Num do begin
      if IsWindowVisible(WA[i]) then S := '' else S := '$';

      StrItem := WinSearch.GetClassNameStr(WA[i]) + S;
      TN      := TreeView1.Items.AddChildObject(Node, StrItem, Pointer(WA[i]));
      EnumChildren(TN);
    end;
  end;

  Num := WinSearch.EnumDirectOwnedWindows(WA, HWND(Node.Data));
  if Num > 0 then begin
    for i := 1 to Num do begin
      if IsWindowVisible(WA[i]) then S := '' else S := '$';

      StrItem := '*' + WinSearch.GetClassNameStr(WA[i]) + S;
      TN      := TreeView1.Items.AddChildObject(Node, StrItem, Pointer(WA[i]));
      EnumChildren(TN);
    end;
  end;
end;

//=============================================================================
//  ツリービューのノードの項目名部分をクリックしたら子ウィンドウを列挙
//  一度展開すると,ツリーを閉じても前の情報のまま
//  新しい情報にするには,[Button1]をクリックして再取得する必要あり
//=============================================================================
procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin
  if (Node.Level = 0) and (Node.Count = 0) then begin
    EnumChildren(Node);
    Node.Expand(True);
  end;

  ListView1.Items.Clear;
  WinSearch.ClassInfos(ListView1, HWND(Node.Data));

  ListView2.Items.Clear;
  WinSearch.WindowInfos(ListView2, HWND(Node.Data));
end;

end.


上のサンプルプログラムでは,次のユニットを使用しています.
このコードでは,整数型 (HWND 型)の 配列にウィドウのハンドル値を格納するようにしていますが,下記の [05_コールバック関数の引数 - TList] のように TList を使用してもいいかも知れません.


リスト3
上のコードで使用しているユニット
ウィンドウのハンドルやクラス情報を取得するための関数類を収めたユニット
unit WinSearch;

interface

uses
  Windows, Messages, SysUtils, Classes, ComCtrls, Forms;

type
  //ウィンドウハンドル値の格納用配列
  //最初の要素(0)はカウンタとして使用
  THWndArray = array[0..500] of HWND;
  PHWndArray = ^THWndArray;

function IsChildWindow(Handle: HWND): Boolean;
function GetClassNameStr(hWindow: HWND): String;
function GetWindowTextStr(hWindow: HWND): String;
procedure ClassInfos(LV: TListView; hWindow: HWND);
procedure WindowInfos(LV: TListView; hWindow: HWND);
function IsOwnedWindow(hWindow: HWND): Boolean;
function AEnumWindows(var WA: THWndArray): Integer;
function EnumVisbleNoOwnedWindows(var WA: THWndArray): Integer;
function EnumVisibleNoOwnedWindowAsZOrder(var WA: THWndArray): Integer;
function EnumWindowsOfClassNameAsZOrder(var WA: THWndArray;
  ClassName: String): Integer;
function AEnumThreadWindows(var WA: THWndArray; OneOfHandles: HWND): Integer;
function AEnumChildWindows(var WA: THWndArray; hParent: HWND): Integer;
function FindChildOfClass(hParent: HWND; ClassName: String): HWND;
function EnumDirectChildren(var WA: THWndArray; hParent: HWND): Integer;
function GetMostTopLevelWindow(hWindow: HWND): HWND;
function EnumDirectOwnedWindows(var WA: THWndArray; hOwner: HWND): Integer;

implementation

//=============================================================================
//  引数のウィンドウが子ウィンドウかを調査
//  子ウィンドウは,そのウィンドウスタイルにWS_CHILDを含んでいることを利用
//=============================================================================
function IsChildWindow(Handle: HWND): Boolean;
begin
  Result := (GetWindowLong(Handle, GWL_STYLE) and WS_CHILD) <> 0;
end;

//=============================================================================
//  ウィンドウのクラス名を取得する関数
//=============================================================================
function GetClassNameStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  System.FillChar(Buffer, SizeOf(Buffer), #0);
  Len := Windows.GetClassName(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//=============================================================================
//  ウィンドウのタイトルを取得する関数
//=============================================================================
function GetWindowTextStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  System.FillChar(Buffer, SizeOf(Buffer), #0);
  Len := Windows.GetWindowText(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//=============================================================================
//  ウィンドウのクラス情報をリストビューに追加
//=============================================================================
procedure ClassInfos(LV: TListView; hWindow: HWND);

  //---------------------------------------------------------------------------
  procedure ListViewItemAdd(LV: TListView; ACaption, SubItem: String);
  var
    LVItem : TListItem;
  begin
    LVItem               := LV.Items.Add;
    LVItem.Caption       := ACaption;
    LVItem.SubItems.Text := SubItem;
  end;
  //---------------------------------------------------------------------------

var
  WC     : TWndClass;
  S      : String;
  Value  : Integer;
  FmtStr : String;
begin
  S := GetClassNameStr(hWindow);
  ListViewItemAdd (LV, 'ClassName', S);
  Windows.GetClassInfo(hInstance, PChar(S), WC);

  Value  := Windows.GetClassLong(hWindow, GCL_STYLE);
  FmtStr := Format('$%.8x', [Value, 8]);
  ListViewItemAdd (LV, 'Style', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_WNDPROC);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'WndProc', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_HMODULE);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'hModule', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_CBCLSEXTRA);
  FmtStr := Format('$%.8x bytes', [Value]);
  ListViewItemAdd (LV, 'ClsExtra', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_CBWNDEXTRA);
  FmtStr := Format('$%.8x bytes', [Value]);
  ListViewItemAdd (LV, 'WndExtra', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_HICON);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'hIcon', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_HBRBACKGROUND);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'hBrush', FmtStr);

  Value  := Windows.GetClassLong(hWindow, GCL_HCURSOR);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'hCursor', FmtStr);
end;

//=============================================================================
//  ウィンドウの各種情報をリストビューに追加
//=============================================================================
procedure WindowInfos(LV: TListView; hWindow: HWND);

  //---------------------------------------------------------------------------
  procedure ListViewItemAdd(LV: TListView; ACaption, SubItem: String);
  var
    LVItem : TListItem;
  begin
    LVItem               := LV.Items.Add;
    LVItem.Caption       := ACaption;
    LVItem.SubItems.Text := SubItem;
  end;
  //---------------------------------------------------------------------------

var
  Value  : Integer;
  FmtStr : String;
begin
  ListViewItemAdd (LV, 'TEXT', GetWindowTextStr(hWindow));

  FmtStr := Format('$%.8x (%d)', [hWindow, hWindow]);
  ListViewItemAdd (LV, 'HANDLE', FmtStr);

  Value  := Windows.GetWindowLong(hWindow, GWL_EXSTYLE);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'EXSTYLE', FmtStr);

  Value  := Windows.GetWindowLong(hWindow, GWL_STYLE);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'STYLE', FmtStr);

  Value  := Windows.GetWindowLong(hWindow, GWL_WNDPROC);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'WNDPROC', FmtStr);

  Value  := Windows.GetWindowLong(hWindow, GWL_HINSTANCE);
  FmtStr := Format('$%.8x', [Value]);
  ListViewItemAdd (LV, 'HINSTANCE', FmtStr);

  Value  := Windows.GetWindowLong(hWindow, GWL_HWNDPARENT);
  FmtStr := Format('$%.8x (%d)', [Value, Value]);
  ListViewItemAdd (LV, 'HWNDPARENT', FmtStr);

  Value  := Windows.GetWindowLong(hWindow, GWL_ID);
  FmtStr := Format('$%.8x (%d)', [Value, Value]);
  ListViewItemAdd(LV, 'ID', FmtStr);
end;

//=============================================================================
//  引数のウィンドウがオーナーウィンドウであるかを調査
//=============================================================================
function IsOwnedWindow(hWindow: HWND): Boolean;
begin
  Result := Windows.GetWindow(hWindow, GW_OWNER) <> 0;
end;

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//-----------------------------------------------------------------------------
function EnumWndProc(hWindow: HWND; lData: LPARAM): BOOL; stdcall;
var
  pWA : PHWndArray;
begin
  pWA           := PHWndArray(lData);
  pWA^[0]       := pWA^[0] + 1;
  pWA^[pWA^[0]] := hWindow;

  Result := True;
end;

//=============================================================================
//  EnumWindowsのラッパー
//  全てのトップレベルウィンドウの列挙
//=============================================================================
function AEnumWindows(var WA: THWndArray): Integer;
begin
  WA[0] := 0;
  EnumWindows(@EnumWndProc, LPARAM(@WA));
  Result := WA[0];
end;

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//-----------------------------------------------------------------------------
function EnumVisbleNoOwnedWndProc(hWindow: HWND; lData: LPARAM): BOOL; stdcall;
var
  pWA : PHWndArray;
begin
  Result := True;
  if IsOwnedWindow(hWindow) then exit;
  if not Windows.IsWindowVisible(hWindow) then exit;

  pWA           := PHWndArray(lData);
  pWA^[0]       := pWA^[0] + 1;
  pWA^[pWA^[0]] := hWindow;
end;

//=============================================================================
//  可視で所有されていないトップレベルウィンドウの列挙
//=============================================================================
function EnumVisbleNoOwnedWindows(var WA: THWndArray): Integer;
begin
  WA[0] := 0;
  EnumWindows(@EnumVisbleNoOwnedWndProc, LPARAM(@WA));
  Result := WA[0];
end;

//=============================================================================
//  上のGetWindow版
//  Z-Orderの高い方から列挙する
//=============================================================================
function EnumVisibleNoOwnedWindowAsZOrder(var WA: THWndArray): Integer;
var
  hWindow : HWND;
begin
  WA[0]   := 0;
  hWindow := GetWindow(Application.Handle, GW_HWNDFIRST);

  while (hWindow <> 0) do begin
    if (not IsOwnedWindow(hWindow)) and
       Windows.IsWindowVisible(hWindow) then begin
      WA[0]     := WA[0] + 1;
      WA[WA[0]] := hWindow;
    end;
    hWindow := Windows.GetWindow(hWindow, GW_HWNDNEXT);
  end;
  Result := WA[0];
end;

//=============================================================================
//  ClassNameのウィンドウをZ-Orderの高い方から列挙する
//=============================================================================
function EnumWindowsOfClassNameAsZOrder(var WA: THWndArray;
  ClassName: String): Integer;
var
  hWindow : HWND;
begin
  WA[0]   := 0;
  hWindow := Windows.GetWindow(Application.Handle, GW_HWNDFIRST);

  while (hWindow <> 0) do begin
    if (not IsOwnedWindow(hWindow))     and
       Windows.IsWindowVisible(hWindow) and
       (UpperCase(GetClassNameStr(hWindow)) = UpperCase(ClassName)) then begin
      WA[0]     := WA[0] + 1;
      WA[WA[0]] := hWindow;
    end;
    hWindow := Windows.GetWindow(hWindow, GW_HWNDNEXT);
  end;
  Result := WA[0];
end;

//-----------------------------------------------------------------------------
//  EnumThreadWindowsのコールバック関数
//-----------------------------------------------------------------------------
function EnumThreadWndProc(hWindow: HWND; lData: LPARAM): BOOL; stdcall;
var
  pWA : PHWndArray;
begin
  pWA           := PHWndArray(lData);
  pWA^[0]       := pWA^[0] + 1;
  pWA^[pWA^[0]] := hWindow;

  Result := True;
end;

//=============================================================================
//  EnumThreadWindows() のラッパー
//  スレッド内のトップレベルウィンドウの列挙
//=============================================================================
function AEnumThreadWindows(var WA: THWndArray; OneOfHandles: HWND): Integer;
begin
  WA[0] := 0;
  EnumThreadWindows(Windows.GetWindowThreadProcessId(OneOfHandles,
                    nil),
                    @EnumThreadWndProc,
                    LPARAM(@WA));
  Result := WA[0];
end;

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//-----------------------------------------------------------------------------
function EnumChildWndProc(hWindow: HWND; lData: LPARAM): BOOL; stdcall;
var
  pWA : PHWndArray;
begin
  pWA           := PHWndArray(lData);
  pWA^[0]       := pWA^[0] + 1;
  pWA^[pWA^[0]] := hWindow;

  Result := True;
end;

//=============================================================================
//  EnumChildWindows() のラッパー
//  子,孫,ひ孫,...全部列挙
//=============================================================================
function AEnumChildWindows(var WA: THWndArray; hParent: HWND): Integer;
begin
  WA[0] := 0;
  EnumChildWindows(hParent, @EnumChildWndProc, LPARAM(@WA));
  Result := WA[0];
end;

//=============================================================================
//  上で列挙したうち,特定のクラス名のもののハンドルを返す
//=============================================================================
function FindChildOfClass(hParent: HWND; ClassName: String): HWND;
var
  WA  : THWndArray;
  i   : Integer;
  Num : Integer;
begin
  Result := 0;
  Num := AEnumChildWindows(WA, hParent);
  if Num > 0 then begin
    for i := 1 to Num do begin
      if UpperCase(GetClassNameStr(WA[i])) = UpperCase(ClassName) then begin
        Result := WA[i];
        break;
      end;
    end;
  end;
end;

//=============================================================================
//  直接の子ウィンドウのみ列挙する
//=============================================================================
function EnumDirectChildren(var WA: THWndArray; hParent: HWND): Integer;
var
  hWindow : HWND;
begin
  WA[0] := 0;
  hWindow := Windows.GetWindow(hParent, GW_CHILD);
  while (hWindow <> 0) do begin
    WA[0]     := WA[0] + 1;
    WA[WA[0]] := hWindow;
    hWindow   := Windows.GetWindow(hWindow, GW_HWNDNEXT);
  end;
  Result := WA[0];
end;

//=============================================================================
//  任意のウィンドウから最上位のトップレベルウィンドウを取得する
//=============================================================================
function GetMostTopLevelWindow(hWindow: HWND): HWND;
var
  hAncestor : HWND;
  hPrev     : HWND;
begin
  hPrev := hWindow;
  hAncestor := Windows.GetParent(hPrev);
  while hAncestor <> 0  do begin
    hPrev     := hAncestor;
    hAncestor := Windows.GetParent(hPrev);
  end;

  hAncestor := hPrev;
  while hAncestor <> 0  do begin
    hPrev     := hAncestor;
    hAncestor := Windows.GetWindow(hPrev, GW_OWNER);
  end;
  Result := hPrev;
end;

//=============================================================================
//  直接のOwnedウィンドウを列挙する
//=============================================================================
function EnumDirectOwnedWindows(var WA: THWndArray; hOwner: HWND): Integer;
var
  WWA : THWndArray;
  i   : Integer;
  Num : Integer;
begin
  Result := 0;
  Num := AEnumThreadWindows(WWA, hOwner);
  if Num > 0 then begin
    for i := 1 to Num do begin
      if Windows.GetWindow(WWA[i], GW_OWNER) = hOwner then begin
        Inc(Result);
        WA[Result] := WWA[i];
      end;
    end;
  end;
end;

end.





02_マウスカーソル下のウィンドウの情報


マウスカーソル下のウィンドウのハンドル取得のサンプルが,下記のリンクの記事にあります.





03_システム関係ダイアログの場合


図は IE (InternetExplorer) 8.0 の印刷の設定の際に現れる [ページ設定] ダイアログです.
このようなダイアログを遠隔操作するには,これらのウィンドウハンドルや入力項目の ID 値が必要となります.このダイアログのウィンドウのオーナーはシステムです,上のプログラムではオーナーを持つウィンドウはツリー表示しません.


図3
IE 8.0の[ページ設定]

これらのウィンドウをツリー表示で表示するには,コールバック関数の EnumVisbleNoOwnedWndProc を次のリストのコードのように修正します.修正して実行した結果が下図です.

このウィンドウの階層のツリー表示プログラムでウィンドウハンドルやID値がわかれば,[016_プリンタ設定ダイアログの遠隔操作] のように,ダイアログの遠隔操作ができるようになります.


図4
[ページ設定] ウィンドウ

リスト4
システム関係ダイアログ等も検出してツリーに含める方法
IsOwnedWindow (hWindow) then exit; を削除,またはコメントアウトする
//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//-----------------------------------------------------------------------------
function EnumVisbleNoOwnedWndProc(hWindow: HWND; lData: LPARAM): BOOL; stdcall;
var
  pWA : PHWndArray;
begin
  Result := True;
//  if IsOwnedWindow(hWindow) then exit;
  if not Windows.IsWindowVisible(hWindow) then exit;

  pWA           := PHWndArray(lData);
  pWA^[0]       := pWA^[0] + 1;
  pWA^[pWA^[0]] := hWindow;
end;




04_対象ウィンドウを囲む枠線を表示


ウィンドウのツリー表示部で,ウィンドウを選択すると,そのウィンドウを囲む枠線を表示させるようにしたサンプルです.非表示のウィドウの時は表示しません.
枠線の表示には,WindowRectForm という別のフォームを使用しています.このフォームには,常に最前面に表示するためのコードを追加しています.追加したコードは,uses 部と,TreeView1 のOnChange イベントだけです.


図5
ウィンドウを囲む枠線を表示

リスト5
ウィンドウを囲む枠線を表示するコードを追加
procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
var
  RectWnd    : TRect;
  RectWidth  : Integer;
  RectHeight : Integer;
  LineWidth  : Integer;
  LineColor  : TColor;
begin
  if (Node.Level = 0) and (Node.Count = 0) then begin
    EnumChildren(Node);
    Node.Expand(True);
  end;

  ListView1.Items.Clear;
  WinSearch.ClassInfos(ListView1, HWND(Node.Data));

  ListView2.Items.Clear;
  WinSearch.WindowInfos(ListView2, HWND(Node.Data));

  if ListView1.Items.Count > 0 then ListView1.PopupMenu := PopupMenu1;

  //TreeViewの選択したNodeに該当するウィンドウを囲む枠線を表示
  //必要なければ以下を全て削除する
  if TreeView1.Selected = nil then begin
    WindowRectForm.Visible := False;
  end else begin
    if not Assigned(WindowRectForm) then begin
      WindowRectForm := TWindowRectForm.Create(Self);
    end;

    //対象のウィンドウが非表示の時は枠を表示しない
    if IsWindowVisible(HWND(Node.Data)) then begin
      WindowRectForm.Refresh;
      WindowRectForm.Visible := True;
      GetWindowRect(HWND(Node.Data), RectWnd);

      LineWidth   := 4;
      LineColor   := clRed;
      //サイズと位置を設定し最前面に表示する
      RectWnd.Left   := RectWnd.Left   - LineWidth;
      RectWnd.Top    := RectWnd.Top    - LineWidth;
      RectWnd.Right  := RectWnd.Right  + LineWidth;
      RectWnd.Bottom := RectWnd.Bottom + LineWidth;
      RectWidth      := RectWnd.Right  - RectWnd.Left;
      RectHeight     := RectWnd.Bottom - RectWnd.Top;

      WindowRectForm.SetBounds(RectWnd.Left, RectWnd.Top, RectWidth, RectHeight);

      //枠線を描画
      with WindowRectForm do begin
        Canvas.Pen.Color := LineColor;
        Canvas.Pen.Width := LineWidth;

        Canvas.MoveTo(LineWidth, LineWidth);
        Canvas.LineTo(Width - LineWidth, LineWidth);
        Canvas.LineTo(Width - LineWidth, Height - LineWidth);
        Canvas.LineTo(LineWidth, Height - LineWidth);
        Canvas.LineTo(LineWidth, LineWidth);
      end;
      WindowRectForm.Update;
    end else begin
      WindowRectForm.Visible := False;
    end;
  end;
end;