|
Delphi Programming / Object Pascal
[掲載 2012年05月15日] [更新 2015年04月26日] Delphi サンプルプログラム集 |
678_他のアプリの操作と IAccessible インターフェイス |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE(UP1) Pro + Firefox 13.0 以降 |
|
678_IAccessibleInterface.zip [4,127 KB] 2015年04月26日版 (EXE 同梱) |
- 2012年05月29日
- Firefox のボタンをクリックするサンプルコードを修正
- オブジェクトの階層をツリー表示するサンプルを追加
- 2012年06月01日
- イベントフックのサンプルコードを追加
- 2012年06月16日
- マウスカーソル下のオブジェクトの情報のサンプルを追加
- 2015年04月26日
- Firefox のボタンをクリックするサンプルコードを修正
FireFox のウィンドウハンドルの取得を真面目なコード (?) に変更して,[戻る] [進む] [閉じる] の全ての操作を実行可能にした
|
他のアプリの操作と IAccessible インターフェイス |
IAccessible インターフェイスのオブジェクトを使用した,他のアプリの操作サンプルです.
IAccessible インターフェイスを使用すると,ウィンドウハンドルを持たないコントロール等のオブジェクトの情報取得や操作が可能になります.
IAccessible インターフェイス関係は,uses に OleAcc を追加すると使用できます.
IAccessible のオブジェクトのプロパティの取得,設定は関数を使用しますが,実際には実装されていない関数もあるようです.また,IAccessible のオブジェクトやメソッドは,たとえ存在しても設定や取得,操作可能かは,アプリーケーション側の設定にもよります.詳しくは,各々の関数の説明で確認してください.
Delphi 2007 よりも前の Delphi で IAccessible インターフェイスの機能を使用するには,[IDL コンパイラを使用したタイプライブラリの作成と取り込み] の記事の方法で,Windows SDK の OleAcc.tlb から Accessibility_TLB.pas というユニットを作成します.
ただし,作成した Accessibility_TLB.pas で実装されているプロパティやメソッド類は,Delphi 2007 以降に実装されている OleAcc.pas と少し仕様が違っています.本ページのサンプルのコードの一部を修正する必要があります.
この種のファイルは,ネット上で同等のものが入手できることがあります. |
[備考]
他のアプリの操作では,ともかく最初は対象のウィンドウのハンドルの取得が必要です.ウインドウのハンドルは,操作に有効なウィンドウのハンドルが必要です.本ページの [ブラウザ Firefox... ] を参考にしてください.FindWindow で取得したウインドウが必ずしも有効なウィンドウであるという保証はありません.
|
01_toolStrip コントロール上のボタンをクリック |
添付の WPFApplication1.exe の toolStip コントロールの操作例です.
WPFApplication1.exe を起動してから操作します.この WPFApplication1.exe は C# で作成しています.ターゲットフレームワークを .NET Frameword 4.5 にしています.したがって,環境によっては動作しないかも知れません. |
|
図1
起動画面
|
|
図2
0 番のオブジェクト
- 0 番目のオブジェクトはない
- toolStrip 自身となる
- 自アプリの TPanel の Caption は,他アプリの name プロパティの値
|
|
図3
1 番目のボタンをクリック
|
これ以降のサンプルでもそうですが,IAccessible の各関数の引数の説明にある VARIANT 型 (バリアント型) は,Delphi では,TVariantArg 型 (ActiveX.pasで定義) として扱うことができます.
また,System.pas の TVarData 型も使用できますが,TVarData では,VT_I4 という値ではなく,次の参考リンクにあるように,Delphi の Integer 型を意味する varInteger 等も使用できます. |
TVarData 型を使用する例.ueses に ActiveX が必要 |
TVarData(varChild).VType := varInteger;
TVarData(varChild).VInteger := SpinEdit1.Value;
oAccessible.accDoDefaultAction(varChild);
TVariantArg 型を使用する例.uses に ActiveX が必要 |
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := SpinEdit1.Value;
oAccessible.accDoDefaultAction(varChild);
リスト1
他のアプリの toolStrip コントロールのボタンオブジェクトをクリック |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Spin, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
SpinEdit1: TSpinEdit;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses oleacc, ActiveX;
{$R *.dfm}
var
TargetClassName : String;
//-----------------------------------------------------------------------------
// ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassNameStr(hWindow: HWND): String;
var
Buffer : array[0..MAX_PATH-1] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then begin
Result := Buffer;
end else begin
Result := '';
end;
end;
//-----------------------------------------------------------------------------
// EnumWindowsのコールバック関数
// 指定したウィンドウクラスのウィンドウを検索する
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPalam: PNativeInt): Boolean; stdcall;
begin
Result := True;
if IsWindowVisible(hWindow) then begin
if Pos(TargetClassName, GetWindowClassNameStr(hWindow)) > 0 then begin
lPalam^ := hWindow;
Result := False;
end;
end;
end;
//=============================================================================
// [toolStrip]のボタンをクリック
// toolStripは.NET Framework(C#)用のコントロール
// 添付の操作対象アプリはC#で(.NET Framework 4 Client Profile)作成したもの
//
// usesに oleacc, ActiveXが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
hTargetWnd : HWND;
hToolStrip : HWND;
oAccessible : IAccessible;
varChild : OleVariant;
vValue : OleVariant;
StrText : WideString;
begin
//トップレベルのウィンドウからクラス名の一部が一致するウィンドウを検索
TargetClassName := 'WindowsForms10.Window.8.app';
hTargetWnd := 0;
EnumWindows(@EnumWindowProc, LPARAM(@hTargetWnd));
//toolStripのハンドルを取得
hToolStrip := FindWindowEx(hTargetWnd, 0, nil, 'toolStrip1');
if hToolStrip = 0 then begin
MessageDlg('操作対象コントロールが見つかりません', mtInformation, [mbOK], 0);
exit;
end;
Panel1.Caption := '';
//そのハンドルのIAccessibleオブジェクトを取得
if AccessibleObjectFromWindow(hToolStrip,
OBJID_CLIENT,
IID_IAccessible,
oAccessible) = S_OK then begin
//SpinEditの値と同じ番号のtoolStripオブジェクトのデフォルト動作を実行
//オブジェクトの全てがボタンとは限らないので注意
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := SpinEdit1.Value;
oAccessible.accDoDefaultAction(varChild);
//ついでにNameプロパティを取得して表示してみる
oAccessible.Get_accName(varChild, StrText);
Panel1.Caption := StrText;
end;
varChild := Unassigned;
vValue := Unassigned;
end;
end.
02_他のアプリの StatusBar のテキストを取得 |
Halbow 資料館の [第8章 他のアプリのウィンドウ情報の取得 - 共有メモリ] に Statusbar のテキストの取得と設定のサンプルがあります.
IAccessible を使用すると,取得だけであれば可能です.そのサンプルプログラムです.
put_accName というプロパティ設定用の関数がありますが,実装されていないようです. |
|
図4
起動画面
|
|
図5
[取得] ボタンをクリック
- 他のアプリの Statusbar の文字列を取得して TMemo に表示
|
リスト2
他のアプリの Statusbar のテキストを取得して表示 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses OleAcc, ActiveX;
{$R *.dfm}
//=============================================================================
// [取得]ボタンクリック
// 他のアプリのTStatusbarのテキストを取得して表示
//
// usesにOleAcc, ActiveXが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
hTargetForm : HWND;
hStatusbar : HWND;
oAccessible : IAccessible;
ChildCount : Integer;
i : Integer;
varChild : OleVariant;
StrText : WideString;
begin
Memo1.Lines.Clear;
//対象のStatusbarのハンドルを取得
hTargetForm := FindWindow('TTargetForm', nil);
hStatusbar := FindWindowEx(hTargetForm, 0, 'TStatusbar', nil);
if hStatusbar = 0 then begin
MessageDlg('操作対象アプリが見つかりません', mtInformation, [mbOK], 0);
exit;
end;
//そのハンドルのIAccessibleオブジェクトを取得
if AccessibleObjectFromWindow(hStatusbar,
OBJID_CLIENT,
IID_IAccessible,
oAccessible) = S_OK then begin
//その子オブジェクトの数を調査
//本来は,AccessibleChildren関数で操作可能を調査すべきだが,ここでは間違い
//なく操作可能であると判断
oAccessible.Get_accChildCount(ChildCount);
//オブジェクトの数だけテキストを取得
TVariantArg(varChild).vt := VT_I4;
for i := 1 to ChildCount do begin
TVariantArg(varChild).lVal := i;
oAccessible.Get_accName(varChild, StrText);
Memo1.Lines.Add(StrText);
end;
end;
varChild := Unassigned;
end;
end.
03_ブラウザ Firefox の [ 戻る ] [ 進む ] ボタンをクリック |
Firefox ブラウザの [戻る] と [進む] ボタンを,Delphi から操作するサンプルです.
まず,Firefox のハンドルを取得します.これらのボタンオブジェクトの name プロパティは,[戻る] と [進む] となっています.Firefox のハンドルの階層下のオブジェクトから,これらの name プロパティを持つオブジェクトを探し出すわけですが,この部分は別ユニットにしています.
[閉じる] ボタンのテストコードがありますが,Windows XP(SP3) + Firefox 12.0 では動作しましたが,Windows 7 U64(SP1) + Firefox 12.0 では動作しませんでした.
FireFox はウィンドウの階層構造が,バージョンによって変更されています.そこで,2015年04月26日の更新で,FireFox のウィンドウハンドルを,EnumWindows のコールバック関数内で検出する方法に変更しました.この変更で,FireFox 37.0.2 では [戻る] [進む] [閉じる] が全て動作するようになっています.この変更がない場合,FireFox 37.0.2 では全てのボタンが機能しません. |
|
図6
実行画面
- Firefox の [←] [→] ボタンをクリック
|
リスト3
Firefox ブラウザの [戻る] と [進む] ボタンを,Delphi 側から操作
ボタンオブジェクトの検索には,添付のユニット内の関数を使用 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, OleAcc;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
plSearchIAccObjName;
{$R *.dfm}
//対象のウィンドウの情報を格納するレコード型の定義
type
TTargetWndInfo = record
hWindow : HWND;
ClassName : String;
end;
PTargetWndInfo = ^TTargetWndInfo;
//-----------------------------------------------------------------------------
// EnumWindowsのコールバック関数
// 可視で他に所有されていないトップレベルのウィンドウだけを検出
// PTargetWndInfoはウィンドウの情報を格納するレコード型のポインタ
//-----------------------------------------------------------------------------
function EnumVisbleNoOwnedWndProc(hWindow: HWND; lData: PTargetWndInfo): BOOL; stdcall;
//---------------------------------------------------------------------------
// 引数のウィンドウがオーナーウィンドウであるかを調査
//---------------------------------------------------------------------------
function IsOwnedWindow(hWindow: HWND): Boolean;
begin
Result := Windows.GetWindow(hWindow, GW_OWNER) <> 0;
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウクラス名を取得
//-----------------------------------------------------------------------------
function GetClassNameStr(hWindow: HWND): string;
var
Buffer : array[0..MAX_PATH-1] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then Result := Buffer else Result := '';
end;
//-----------------------------------------------------------------------------
var
LClassName : String;
begin
Result := True;
if IsOwnedWindow(hWindow) then exit;
if not Windows.IsWindowVisible(hWindow) then exit;
//クラス名が同じだったら,そのウィンドウハンドルをコールバックの引数に返す
LClassName := UpperCase(GetClassNameStr(hWindow));
if LClassName = UpperCase(lData^.ClassName) then begin
lData^.hWindow := hWindow;
Result := False;
end;
end;
//=============================================================================
// [戻る]ボタンクリック
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LWndInfo : TTargetWndInfo;
LTargetWnd : HWND;
LBackAcc : IAccessible;
LChildID : Integer;
begin
//Firefoxのハンドルを取得
LWndInfo.ClassName := 'MozillaWindowClass';
LWndInfo.hWindow := 0;
//指定クラス名のウィンドウを検索
EnumWindows(@EnumVisbleNoOwnedWndProc, LPARAM(@LWndInfo));
LTargetWnd := LWndInfo.hWindow;
if LTargetWnd = 0 then begin
MessageDlg('Fire Foxが見つかりません', mtInformation, [mbOK], 0);
end else begin
//[戻る]ボタンのIAccessibleオブジェクトを取得
if LBackAcc = nil then begin
LBackAcc := SearchAccObjectFromWindow(LTargetWnd, '戻る', LChildID);
end;
if LBackAcc = nil then begin
MessageDlg('操作できません', mtInformation, [mbOK], 0);
end else begin
//[戻る]ボタンのデフォルト動作を実行
LBackAcc.accDoDefaultAction(LChildID);
end;
end;
end;
//=============================================================================
// [進む]ボタンクリック
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LWndInfo : TTargetWndInfo;
LTargetWnd : HWND;
LForewordAcc : IAccessible;
LChildID : Integer;
begin
//Firefoxのハンドルを取得
LWndInfo.ClassName := 'MozillaWindowClass';
LWndInfo.hWindow := 0;
//指定クラス名のウィンドウを検索
EnumWindows(@EnumVisbleNoOwnedWndProc, LPARAM(@LWndInfo));
LTargetWnd := LWndInfo.hWindow;
if LTargetWnd = 0 then begin
MessageDlg('Fire Foxが見つかりません', mtInformation, [mbOK], 0);
end else begin
if LForewordAcc = nil then begin
LForewordAcc := SearchAccObjectFromWindow(LTargetWnd, '進む', LChildID);
end;
//[進む]ボタンのIAccessibleオブジェクトを取得
if LForewordAcc = nil then begin
MessageDlg('操作できません', mtInformation, [mbOK], 0);
end else begin
//[進む]ボタンのデフォルト動作を実行
LForewordAcc.accDoDefaultAction(LChildID);
end;
end;
end;
//=============================================================================
// [閉じる]ボタンクリック
//
// Windows XP(SP3) + Firefox 12.0 では動作する
// Windows 7 U64(SP1) + Firefox 12.0 てば動作しない
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
LWndInfo : TTargetWndInfo;
LTargetWnd : HWND;
LCloseAcc : IAccessible;
LChildID : Integer;
begin
//Firefoxのハンドルを取得
LWndInfo.ClassName := 'MozillaWindowClass';
LWndInfo.hWindow := 0;
//指定クラス名のウィンドウを検索
EnumWindows(@EnumVisbleNoOwnedWndProc, LPARAM(@LWndInfo));
LTargetWnd := LWndInfo.hWindow;
if LTargetWnd = 0 then begin
MessageDlg('Fire Foxが見つかりません', mtInformation, [mbOK], 0);
end else begin
if LCloseAcc = nil then begin
LCloseAcc := SearchAccObjectFromWindow(LTargetWnd, '閉じる', LChildID);
end;
//[閉じる]ボタンのIAccessibleオブジェクトを取得
if LCloseAcc = nil then begin
MessageDlg('操作できません', mtInformation, [mbOK], 0);
end else begin
//[閉じる]ボタンのデフォルト動作を実行
LCloseAcc.accDoDefaultAction(LChildID);
end;
end;
end;
end.
IAccessible には,特定のプロパティの値を持つオブジェクトを検索するメソッドはありません.
全オブジェクトの中から検索することになりますが,同じプロパティの値を持つオブジェクトが複数存在することもあります.例えば,今回のサンプルで扱っている [戻る] は,コンテキストメニューにもあります.
そこで,以下のコードでは,非可視のオブジェクトを検索対象から除外しています.
関数は 2 つあり,1 つはウィンドウハンドルから検索,もう1つは IAccessible オブジェクトから検索する関数です. |
リスト4
指定ウィンドウ下のオブジェクトから,指定 Name プロパティのオブジェクトを検索する関数 |
unit plSearchIAccObjName;
interface
uses
Windows, SysUtils, Forms, ActiveX, OleAcc, Variants;
function SearchAccObjectFromWindow(Wnd: HWND; SearchName: String;
var ChildID: Integer): IAccessible;
function SearchAccChild(Acc: IAccessible; SearchName: String;
var ID: Integer): IAccessible;
//Delphi XEにないので定義
function AccessibleChildren(paccContainer: IAccessible; iChildStart,
cChildren: Longint; rgvarChildren: PVariantArg;
pcObtained: PLongint): HRESULT; stdcall; external 'oleacc.dll';
implementation
//=============================================================================
// 引数で指定するウィンドウ階層下のオブジェクトから,指定Nameプロパティを持つ
// オブジェクトを検索して返す関数
// ただし,非表示のオブジェクトを除く
//
// Wnd : 検索対象ウィンドウのハンドル
// SearchName : 検索文字列
// ChildID : 検索したオブジェクトの子オブジェクト番号(1,2,3, ...)
// 0の時は,戻り値は検索結果のオブジェクトのIAccessible
// 1以外の時の戻り値は,検索結果の直接の親のオブジェクト
// 戻り値は,検索結果のオブジェクトの直接の親のIAccessible
// 見つからなかった時はnilを返す
//
// この関数の呼び出し側では,戻り値とChildIDの値でオブジェクトを操作する
//=============================================================================
function SearchAccObjectFromWindow(Wnd: HWND; SearchName: String;
var ChildID: Integer): IAccessible;
var
oAccessible : IAccessible;
begin
Result := nil;
//WndのIAccessibleオブジェクトを取得
if AccessibleObjectFromWindow(
Wnd,
OBJID_WINDOW,
IID_IAccessible,
Pointer(oAccessible)) = S_OK then begin
Result := SearchAccchild(oAccessible, SearchName, ChildID);
end;
end;
//-----------------------------------------------------------------------------
// 検索対象の文字列かを検査する関数
// 非可視のオブジェクトは除外
//-----------------------------------------------------------------------------
function IsSearchName(AccChild: IAccessible; ChildID: Integer;
SearchName: String): Boolean;
var
varState : OleVariant;
VState : Cardinal;
varChild : OleVariant;
AccName : WideString;
begin
Result := False;
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
if AccChild.Get_accState(varChild, varState) = S_OK then begin
if TVariantArg(varState).vt = VT_I4 then begin
VState := TVariantArg(varState).lVal;
if (VState and STATE_SYSTEM_INVISIBLE) = 0 then begin
if AccChild.get_accName(varChild, AccName) = S_OK then begin
Result := UpperCase(Trim(AccName)) = UpperCase(SearchName);
end;
end;
end;
end;
end;
//=============================================================================
// 引数で指定するIAccessibleオブジェクト階層下から,指定のNameプロパティを持つ
// オブジェクトを検索して返す関数
// ただし,非表示のオブジェクトを除く
// この関数を再帰的に実行し,オブジェクトの階層下を検索していく
//
// Acc : 検索対象のIAccessibleオブジェクト
// SearchName : 検索文字列
// ID : 検索したオブジェクトの子オブジェクト番号(1,2,3, ...)
// 0の時は,戻り値は検索結果のオブジェクトのIAccessible
// 1以外の時の戻り値は,検索結果の直接の親のオブジェクト
// 戻り値は,検索結果のオブジェクトの直接の親のIAccessible
// 見つからなかった時はnilを返す
//
// この関数の呼び出し側では,戻り値とChildIDの値でオブジェクトを操作する
//=============================================================================
function SearchAccChild(Acc: IAccessible; SearchName: String;
var ID: Integer): IAccessible;
var
Children : array of TVariantArg;
ChildCount : Integer;
Obtained : Integer;
AccChDisp : IDispatch;
AccChild : IAccessible;
ChildID : Integer;
i : Integer;
j : Integer;
HasChild : Integer;
begin
Result := nil;
if Acc = nil then exit;
//子オブジェクトの数を調べる
if Acc.get_accChildCount(ChildCount) = S_OK then begin
Application.ProcessMessages;
SetLength(Children, ChildCount);
//バリアント配列の初期化
for i := 0 to ChildCount - 1 do begin
VariantInit(OleVariant(Children[i]));
end;
//アクセス可能なオブジェクトの数を調査
if AccessibleChildren(Acc, 0, ChildCount, @Children[0], @Obtained) = S_OK
then begin
for j := 0 to Obtained - 1 do begin
HasChild := -1;
ChildID := CHILDID_SELF;
//子オブジェクトがある
if Children[j].vt = VT_DISPATCH then begin
AccChDisp := IDispatch(Children[j].pdispVal);
if Assigned(AccChDisp) then begin
AccChDisp.QueryInterface(IID_IAccessible, AccChild);
ChildID := CHILDID_SELF;
HasChild := 1;
end;
end else begin
if Children[j].vt = VT_I4 then begin
//子オブジェクトはない
if Acc.get_accChild(OleVariant(Children[j]), AccChDisp) = S_FALSE
then begin
AccChild := Acc;
ChildID := Children[j].lVal;
HasChild := 0;
end else begin
//子オブジェクトがある
if Assigned(AccChDisp) then begin
AccChDisp.QueryInterface(IID_IAccessible, AccChild);
ChildID := CHILDID_SELF;
HasChild := 1;
end;
end;
end;
end;
//処理を1か所にまとめるためにHasChildをフラグとして使用
if HasChild >= 0 then begin
if IsSearchName(AccChild, ChildID, SearchName) then begin
ID := ChildID;
Result := AccChild;
break;
end;
if (HasChild = 1) then begin
Result := SearchAccChild(AccChild, SearchName, ID);
if Result <> nil then Break;
end;
end;
end;
end;
//バリアント配列の後始末
for i := 0 to Obtained - 1 do begin
VariantClear(OleVariant(Children[i]));
end;
end;
end;
end.
[645_ウィンドウの階層のツリー表示] で,オブジェクトのツリー表示をするプログラムを掲載していまが,これらは非可視のオブジェクトも表示します.
そこで,非可視のオブジェクトは対象外とし,可視状態のオブジェクトだけを表示するサンプルを作成してみました.テストしやすいように,ウィンドウを選択するダイアログを表示します.このダイアログは,[645_ウィンドウの階層のツリー表示] で使用している,WinSearch.pas をツリー表示部だけに変更したものです.選択ノードのウィンドウを赤線の枠で表示します.
また,オブジェクトのツリーでも,選択中のオブジェクトを赤い枠線で表示します.この枠線の表示には,透明なフォームを使用しています.
右のペインの TMemo には,現在選択中のノードに相当するオブジェクトの Name または Value プロパティの値を表示します.
[デフォルトアクション実行] と [値のセット] のボタンを追加していますが,対象のオブジェクトに対して,これらの機能が使用できるとは限りません.
[値のセット]は,TMemo に入力した値を,オブジェクトの Value プロパティに文字列を送信するものです.ただし,編集可能なテキストであって,且つ,読み出し専用となっていないオブジェクトに限ります.メモ帳等にはセットできます.ブラウザのページのテキストを変更することはできません. |
|
図7
設計時画面 |
下図が実行例です.エクスプローラのオブジェクトをツリー展開しています.
そのエクスプローラのツリーの[ピクチャ] をマウスで選択している状態です.該当するオブジェクトが赤い枠線で表示されています.この赤い矩形は,キャプチャ画像に後から追加描画したものではありません.デスクトップのキャプチャ画像そのものです.
[デフォルトアクション実行] ボタンをクリックすると,この [ピクチャ] フォルダを展開することができますが,ツリー表示は [ツリー更新] ボタンをクリックしないと更新されません. |
- 図8
- エクスプローラのオブジェクトをツリー表示
- ツリー表示部の,エクスプローラの [ピクチャ] をクリック
- 該当するオブジェクトが赤い枠線で囲まれる
|
|
リスト5
ウィンドウを選択して,IAccessible オブジェクトの階層をツリー表示
ただし,可視状態のオブジェクトのみ |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, oleacc, ExtCtrls, ComCtrls, ActiveX, Menus,
plWindowRectFormUnit, Buttons;
type
TForm1 = class(TForm)
TreeView1: TTreeView;
Panel1: TPanel;
Button2: TButton;
Splitter1: TSplitter;
Panel2: TPanel;
ListView1: TListView;
Button1: TButton;
RadioGroup1: TRadioGroup;
Memo1: TMemo;
PopupMenu1: TPopupMenu;
N1: TMenuItem;
Panel3: TPanel;
Button3: TButton;
PopupMenu2: TPopupMenu;
N2: TMenuItem;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
procedure TreeView1Deletion(Sender: TObject; Node: TTreeNode);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure N1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
procedure RadioGroup1Click(Sender: TObject);
procedure N2Click(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
private
{ Private 宣言 }
hTargetWnd : HWND;
TargetClassName : String;
RectForm : TplWindowRectForm;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses plEnumAccInfos, WindowTreeUnit, Clipbrd;
{$R *.dfm}
//=============================================================================
// フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
ListView1.PopupMenu := nil;
TreeView1.PopupMenu := nil;
end;
//=============================================================================
// フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if Assigned(RectForm) then begin
RectForm.Close;
FreeAndNil(RectForm);
end;
end;
//=============================================================================
// [ウィンドウ選択]ボタン
// ウィンドウのリスト表示用のフォームをモーダル表示して選択
//
// hTargetWnd : 対象のウィンドウのハンドル
// TargetClassName : 対象のウィンドウのクラス名
// は,モーダル表示したフォームから取得する
// 共にグローバル変数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
AForm : TWindowTreeForm;
dwID : Cardinal;
Ret : Integer;
begin
if Assigned(RectForm) then begin
RectForm.Close;
FreeAndNil(RectForm);
end;
TreeView1.PopupMenu := nil;
ListView1.PopupMenu := nil;
Ret := mrOk;
if Sender <> nil then begin
AForm := TWindowTreeForm.Create(Self);
try
Ret := AForm.ShowModal;
if Ret = mrOk then begin
Self.Update;
hTargetWnd := AForm.hTargetWnd;
TargetClassName := AForm.TargetClassName;
end;
finally
FreeAndNil(AForm);
end;
end;
if Ret = mrOk then begin
Screen.Cursor := crHourGlass;
TreeView1.Items.Clear;
ListView1.Items.Clear;
Memo1.Lines.Clear;
dwID := OBJID_CLIENT;
if RadioGroup1.ItemIndex = 0 then dwID := OBJID_WINDOW;
if RadioGroup1.ItemIndex = 1 then dwID := OBJID_CLIENT;
plEnumAccInfos.EnumAccObjectTree(hTargetWnd, TreeView1, dwID);
Screen.Cursor := crDefault;
end;
end;
//=============================================================================
// [ツリー更新]ボタン
// オブジェクトの再取得
// ボップアップメニューの[このオブジェクトをルートにして再取得]と同じコードを実行
// ただし,このオブジェクトは,トップノードのものとする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
if Assigned(RectForm) then begin
RectForm.Close;
FreeAndNil(RectForm);
end;
if TreeView1.Items.Count > 1 then begin
N2Click(nil);
end;
end;
//=============================================================================
// [全て展開]ボタン
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
if Assigned(RectForm) then begin
RectForm.Close;
FreeAndNil(RectForm);
end;
if TreeView1.Items.Count = 0 then exit;
TreeView1.Items.BeginUpdate;
TreeView1.FullExpand;
TreeView1.TopItem := TreeView1.Items[0];
TreeView1.Items.EndUpdate;
end;
//=============================================================================
// RadioGroup1のOnClickイベント
// [ウィンドウ選択]ボタンと同じコードを実行
//=============================================================================
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
Button1Click(nil);
end;
//=============================================================================
// [デフォルトアクション実行]ボタン
// オブジェクトのデフォルト動作を実行
// 全てのオブジェクトに対して有効なわけではない
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
pAccInfo : PplAccInfoRec;
Acc : IAccessible;
ChildID : Integer;
varChild : OleVariant;
begin
if TreeView1.Selected = nil then exit;
//NodeのDataプロパティからオブジェクトのデータを取出す
//このデータはレコード型に格納されている
pAccInfo := TreeView1.Selected.Data;
Acc := pAccInfo.pAcc;
ChildID := pAccInfo.ChildID;
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
Acc.accDoDefaultAction(varChild);
end;
//=============================================================================
// [値のセット]ボタン
// オブジェクトに対してput_accValue(DelphiではSet_accValue)メソッドを実行
// オブジェクトに値をセットする.編集可能なオブジェクトが対象
// 全てのオブジェクトに対して有効なわけではない
//=============================================================================
procedure TForm1.SpeedButton2Click(Sender: TObject);
var
pAccInfo : PplAccInfoRec;
Acc : IAccessible;
ChildID : Integer;
StrSet : WideString;
varChild : OleVariant;
begin
if TreeView1.Selected = nil then exit;
//NodeのDataプロパティからオブジェクトのデータを取出す
//このデータはレコード型に格納されている
pAccInfo := TreeView1.Selected.Data;
Acc := pAccInfo.pAcc;
ChildID := pAccInfo.ChildID;
//Memo1に入力した値を使用
StrSet := Memo1.Text;
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
Acc.Set_accValue(varChild, StrSet);
end;
//=============================================================================
// TreeView1のOnChangeイベント処理
//
// TreeView1の選択ノードが変更になったら,プロパティを取得して表示
//=============================================================================
procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
var
pAccInfo : PplAccInfoRec;
Acc : IAccessible;
ChildID : Integer;
StrText : WideString;
varChild : OleVariant;
RectWnd : TRect;
LineWidth : Integer;
LineColor : TColor;
ShowFlag : Cardinal;
begin
if Node = nil then begin
TreeView1.PopupMenu := nil;
exit;
end else begin
TreeView1.PopupMenu := PopupMenu2;
end;
if not Assigned(RectForm) then begin
RectForm := TplWindowRectForm.Create(Self);
RectForm.Show;
TreeView1.SetFocus;
end else begin
RectForm.Canvas.FillRect(RectForm.ClientRect);
end;
ShowFlag := SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOOWNERZORDER;
SetWindowPos(RectForm.Handle, HWND_TOPMOST, 0, 0, 0, 0, ShowFlag);
//プロパティをListView1に表示するメソッドを実行
plEnumAccInfos.AccObjectInfo(ListView1, Node);
//NodeのDataプロパティからオブジェクトのデータを取出す
//このデータはレコード型で格納されている
pAccInfo := Node.Data;
Acc := pAccInfo.pAcc;
ChildID := pAccInfo.ChildID;
Memo1.Clear;
//Valueプロパティの値を取得
StrText := plEnumAccInfos.GetAccObjectValue(Acc, ChildID);
//Valueプロパティの値が空の時は,Nameプロパティの値を取得
//複数行の場合があるのでTMemoに表示
if Trim(StrText) = '' then begin
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
if Acc.get_accName(varChild, StrText) = S_OK then begin
Memo1.Text := StrText;
Panel3.Caption := 'Name property';
end;
end else begin
Memo1.Text := StrText;
Panel3.Caption := 'Value property';
end;;
//TreeViewで選択したNodeに該当するオブジェクト囲む枠線を表示
//サイズと位置を取得
RectWnd := plEnumAccInfos.GetAccObjectRect(Acc, ChildID);
LineWidth := 4;
LineColor := clRed;
//枠線を描画
with RectForm.Canvas do begin
Pen.Color := LineColor;
Pen.Width := LineWidth;
MoveTo (RectWnd.Left, RectWnd.Top);
LineTo (RectWnd.Right, RectWnd.Top);
LineTo (RectWnd.Right, RectWnd.Bottom);
LineTo (RectWnd.Left, RectWnd.Bottom);
LineTo (RectWnd.Left, RectWnd.Top);
end;
Sleep(1);
end;
//=============================================================================
// TreeView1のOnDeletionイベント処理
//
// TreeViewのDataプロパティのデータを解放
//=============================================================================
procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
begin
if Assigned(Node.Data) then begin
Dispose(PplAccInfoRec(Node.Data));
Node.Data := nil;
end;
end;
//=============================================================================
// ListView1のOnSelectItemイベント処理
// Itemを選択したら,ポップアップメニューを使用可能にする
//=============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
begin
if Selected then begin
ListView1.PopupMenu := PopupMenu1;
end else begin
ListView1.PopupMenu := nil;
end;
end;
//=============================================================================
// ListView1のボップアップメニューの[コピー]
// PopupMenu1のAutoHotkeysとAutoLineReductionプロパティはmaManual
//=============================================================================
procedure TForm1.N1Click(Sender: TObject);
var
AIndex : Integer;
StrText : String;
begin
AIndex := ListView1.ItemIndex;
StrText := ListView1.Items[AIndex].Caption + Char(VK_TAB) +
ListView1.Items[AIndex].SubItems[0];
Clipboard.AsText := StrText;
end;
//=============================================================================
// TreeView1のボップアップメニューの[このオブジェクトをルートにして再取得]
// オブジェクトのカテゴリー(OBJID_WINDOW,OBJID_CLIENT)は現在の設定を使用
// ツリー更新にもこのコードを使用する
//
// PopupMenu2のAutoHotkeysとAutoLineReductionプロパティはmaManual
//=============================================================================
procedure TForm1.N2Click(Sender: TObject);
var
pAccInfo : PplAccInfoRec;
Acc : IAccessible;
ChildID : Integer;
RootNode : TTreeNode;
begin
if Assigned(RectForm) then begin
RectForm.Close;
FreeAndNil(RectForm);
end;
TreeView1.PopupMenu := nil;
ListView1.PopupMenu := nil;
Screen.Cursor := crHourGlass;
TreeView1.Items.BeginUpdate;
//全体の更新の場合
//ルートノードに該当するオブジェクトとする
if Sender = nil then begin
TreeView1.Selected := TreeView1.Items[0];
end;
//NodeのDataプロパティからオブジェクトのデータを取出す
//このデータはレコード型で格納されている
pAccInfo := TreeView1.Selected.Data;
Acc := pAccInfo.pAcc;
ChildID := pAccInfo.ChildID;
TreeView1.Items.Clear;
ListView1.Items.Clear;
Memo1.Lines.Clear;
//ルートノード
RootNode := plEnumAccInfos.AddChildNode(Acc, ChildID, TreeView1, nil);
//EnumAccTreeItems関数は,Acc階層下のオブジェクトを列挙するので子オブジェクト
//がない場合は実行しない
if (RootNode <> nil) and (ChildID = 0) then begin
//列挙実行
plEnumAccInfos.EnumAccTreeItems(Acc, TreeView1, RootNode);
RootNode.HasChildren := TreeView1.Items.Count > 1;
TreeView1.TopItem.Expand(False);
end;
TreeView1.Items.EndUpdate;
Screen.Cursor := crDefault;
end;
end.
次のコードが実際にオブジェクトの階層を検索する関数類です.関数の説明は省略します.上のサンプルコードやコード内のコメントで確認してください.
関数に WideString 型を使用しているのは,Delphi 2007 でも共通に使用可能にするためです. |
リスト6
指定ウィンドウまたは指定 IAccessible オブジェクトの階層をツリー表示するための関数類 |
unit plEnumAccInfos;
interface
uses
Windows, SysUtils, Forms, ActiveX, OleAcc, Variants, ComCtrls;
type
//オブジェクトの情報用のレコード型
//各ノードのDataぷパティに代入する
TplAccInfoRec = record
ChildID : Integer;
pAcc : IAccessible;
end;
PplAccInfoRec = ^TplAccInfoRec;
//公開関数類
procedure EnumAccObjectTree(hWindow: HWND; Tree: TTreeView; dwID: Cardinal);
procedure EnumAccTreeItems(Acc: IAccessible; ATree: TTreeView; ANode: TTreeNode);
function AddChildNode(AccChild: IAccessible; ChildID: Integer; ATree: TTreeView;
ANode: TTreeNode): TTreeNode;
procedure AccObjectInfo(LV : TListView; Node: TTreeNode);
function GetAccObjectValue(Acc: IAccessible; ChildID: Integer): String;
function GetAccObjectRect(Acc: IAccessible; ChildID: Integer): TRect;
function IsAccObjectVisible(Acc: IAccessible; ChildID: Integer): Boolean;
//以下の関数は,Delphi XEにないので定義
function WindowFromAccessibleObject(paccContainer: IAccessible; var phwnd: HWND):
HRESULT;stdcall; external 'oleacc.dll';
function AccessibleChildren(paccContainer: IAccessible; iChildStart,
cChildren: Longint; rgvarChildren: PVariantArg;
pcObtained: PLongint): HRESULT; stdcall; external 'oleacc.dll';
function AccessibleObjectFromPoint(ptScreen: TPoint; out ppacc: IAccessible;
out pvarChild): HRESULT; stdcall; external 'oleacc.dll';
function GetRoleTextW(dwRole: DWORD; lpszRole: PWideChar;
cchRoleMax: UINT): UINT; stdcall; external 'oleacc.dll';
function GetStateTextW(dwStateBit: DWORD; lpszStateBit: PWideChar;
cchStateBitMax: UINT): UINT; stdcall; external 'oleacc.dll';
implementation
var
//可視状態のオブジェクトのみを対象とする
OnlyVisible : Boolean = True;
//=============================================================================
// ハンドル値がhWindowのウィンドウのオブジェクトを再帰的に検索して列挙
// TreeViewのNodeに追加していく
//
// hWindow : ウィンドウのハンドル
// Tree : TreeView
// dwID : オブジェクトのカテゴリー
//=============================================================================
procedure EnumAccObjectTree(hWindow: HWND; Tree: TTreeView; dwID: Cardinal);
var
oAccessible : IAccessible;
RootNode : TTreeNode;
begin
Tree.Items.Clear;
//ウィンドウハンドルからIAccessibleオブジェクトを取得
if AccessibleObjectFromWindow(hWindow,
dwID,
IID_IAccessible,
Pointer(oAccessible)) <> S_OK then exit;
//ルートノード
RootNode := AddChildNode(oAccessible, CHILDID_SELF, Tree, nil);
Tree.Items.BeginUpdate;
//列挙実行
EnumAccTreeItems(oAccessible, Tree, RootNode);
Tree.Items.EndUpdate;
Tree.TopItem.Expand(False);
end;
//=============================================================================
// Acc階層下の子オブジェクトを列挙
// TreeViewにツリー表示する
//=============================================================================
procedure EnumAccTreeItems(Acc: IAccessible; ATree: TTreeView; ANode: TTreeNode);
var
Children : array of TVariantArg;
ChildCount : Integer;
Obtained : Integer;
AccChDisp : IDispatch;
AccChild : IAccessible;
ChildID : Integer;
i : Integer;
j : Integer;
AddNode : TTreeNode;
HasChild : Integer;
begin
if Acc = nil then exit;
//子オブジェクトの数を調べる
if (Acc.get_accChildCount(ChildCount) = S_OK) then begin
Application.ProcessMessages;
SetLength(Children, ChildCount);
//バリアント配列の初期化
for i := 0 to ChildCount - 1 do begin
VariantInit(OleVariant(Children[i]));
end;
//アクセス可能な子オブジェクトの数を調査
if AccessibleChildren(Acc, 0, ChildCount, @Children[0], @Obtained) = S_OK
then begin
for j := 0 to Obtained - 1 do begin
HasChild := -1;
ChildID := CHILDID_SELF;
//子オブジェクトがある
if Children[j].vt = VT_DISPATCH then begin
AccChDisp := IDispatch(Children[j].pdispVal);
if Assigned(AccChDisp) then begin
AccChDisp.QueryInterface(IID_IAccessible, AccChild);
ChildID := CHILDID_SELF;
HasChild := 1;
end;
end else begin
if Children[j].vt = VT_I4 then begin
//子オブジェクトはない
if Acc.get_accChild(OleVariant(Children[j]), AccChDisp) = S_FALSE
then begin
AccChild := Acc;
ChildID := Children[j].lVal;
HasChild := 0;
end else begin
//子オブジェクトがある
if Assigned(AccChDisp) then begin
AccChDisp.QueryInterface(IID_IAccessible, AccChild);
ChildID := CHILDID_SELF;
HasChild := 1;
end;
end;
end;
end;
//処理を1か所にまとめるためにHasChildをフラグとして使用
if HasChild >= 0 then begin
AddNode := AddChildNode(AccChild, ChildID, ATree, ANode);
if (AddNode <> nil) and (HasChild = 1) then begin
EnumAccTreeItems(AccChild, ATree, AddNode);
end;
end;
end;
end;
//バリアント配列の後始末
for i := 0 to Obtained - 1 do begin
VariantClear(OleVariant(Children[i]));
end;
end;
end;
//=============================================================================
// 以下の処理を行う関数
//
// TreeViewのノードに表示するテキスト作成
// TreeViewにノードを追加
// ノードのDataプロパティにオブジェクトの情報を,レコード型で代入
//
// 戻り値は,追加したノードのインスタンス
// 非可視のオブジェクトはノードに追加しないで,戻り値にnilを返す
//=============================================================================
function AddChildNode(AccChild: IAccessible; ChildID: Integer; ATree: TTreeView;
ANode: TTreeNode): TTreeNode;
var
AName : WideString;
varRole : OleVariant;
Len : Cardinal;
TmpStr : WideString;
varID : OleVariant;
StrText : WideString;
pAccInfo : PplAccInfoRec;
begin
Result := nil;
StrText := '';
//可視状態のオブジェクトのみの場合,非可視のオブジェクトは追加しない
if OnlyVisible then begin
if not IsAccObjectVisible(AccChild, ChildID) then exit;
end;
//Role(オブジェクトの機能,役割)プロパティを文字列として取得
TVariantArg(varID).vt := VT_I4;
TVariantArg(varID).lVal := ChildID;
if AccChild.get_accRole(varID, varRole) = S_OK then begin
if TVariantArg(varRole).vt = VT_I4 then begin
Len := GetRoleTextW(TVariantArg(varRole).lVal, nil, 0);
SetLength(TmpStr, Len);
GetRoleTextW(TVariantArg(varRole).lVal, PWideChar(TmpStr), Len + 1);
StrText := '[' + TmpStr + ']';
end else begin
StrText := '[' + String(varRole) + ']';
end;
end else begin
StrText := '[]';
end;
//Nameプロパティを取得して追加
//Roleプロパティの文字列の後ろに追加
if AccChild.get_accName(varID, AName) <> S_OK then AName := '';
StrText := StrText + ' "' + AName + '"';
//オブジェクトが可視か非可視かの文字列を追加
//もしかしたら,非可視のオブジェクトもツリー表示するテストをしたい人がいるかも
//しれないので追加
if not OnlyVisible then begin
if IsAccObjectVisible(AccChild, ChildID) then begin
StrText := StrText + ' ' + 'Visible';
end else begin
StrText := StrText + ' ' + 'InVisible';
end;
end;
Result := ATree.Items.AddChild(ANode, StrText);
//NodeのDataプロパティにオブジェクトの情報を代入
if Result <> nil then begin
New(pAccInfo);
pAccInfo.ChildID := ChildID;
pAccInfo.pAcc := AccChild;
Result.Data := pAccInfo;
end;
end;
//=============================================================================
// オブジェクトのプロパティの値を取得して,ListViewに追加していく
// get_accFocus関数とget_accSelection関数による値は省略
//
// ListViewのViewStyleがvsReportで,カラムが2つあると仮定している時のコード
//=============================================================================
procedure AccObjectInfo(LV : TListView; Node: TTreeNode);
//---------------------------------------------------------------------------
// ウィンドウのクラス名を取得する関数
//---------------------------------------------------------------------------
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;
//---------------------------------------------------------------------------
// ListViewにItemを追加するメソッド
//---------------------------------------------------------------------------
procedure ListViewItemAdd(LV: TListView; ACaption, SubItem: String);
var
LVItem : TListItem;
begin
LVItem := LV.Items.Add;
LVItem.Caption := ACaption;
LVItem.SubItems.Text := SubItem;
end;
//---------------------------------------------------------------------------
var
pAccInfo : PplAccInfoRec;
Acc : IAccessible;
ChildID : Integer;
hWindow : HWND;
StrText : WideString;
ATop : Integer;
ALeft : Integer;
AWidth : Integer;
AHeight : Integer;
varChild : OleVariant;
varState : OleVariant;
varRole : OleVariant;
TmpInt : Integer;
Len : Cardinal;
i : Integer;
StateID : Integer;
TmpStr : String;
begin
if Node.Data = nil then exit;
LV.Items.Clear;
//NodeのDataプロパティからオブジェクトのデータを取出す
pAccInfo := Node.Data;
Acc := pAccInfo.pAcc;
ChildID := pAccInfo.ChildID;
LV.Items.BeginUpdate;
StrText := '';
hWindow := 0;
if WindowFromAccessibleObject(Acc, hWindow) = S_OK then begin
ListViewItemAdd (LV, 'Class Name', GetClassNameStr(hWindow));
end;
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
if Acc.Get_accName(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Name', StrText);
end else begin
ListViewItemAdd (LV, 'Name', '-');
end;
StrText := '-';
if Acc.get_accRole(varChild, varRole) = S_OK then begin
if TVariantArg(varRole).vt = VT_I4 then begin
Len := GetRoleTextW(TVariantArg(varRole).lVal, nil, 0);
SetLength(StrText, Len);
GetRoleTextW(TVariantArg(varRole).lVal, PWideChar(StrText), Len + 1);
end;
end;
ListViewItemAdd (LV, 'Role', StrText);
if Acc.accLocation(ALeft, ATop, AWidth, AHeight, varChild) = S_OK then begin
if AWidth * AHeight <> 0 then begin
StrText := '( ' + IntToStr(ALeft) + ', ' +
IntToStr(ATop) + ', ' +
IntToStr(ALeft + AWidth) + ', ' +
IntToStr(ATop + AHeight) + ' )';
ListViewItemAdd (LV, 'Location', StrText);
end else begin
ListViewItemAdd (LV, 'Location', '-');
end;
end else begin
ListViewItemAdd (LV, 'Location', '-');
end;
//Get_accChildCount関数で取得すると非可視のオブジェクトも含まれるので
//ここではTreeViewの子ノードの数を表示
ListViewItemAdd (LV, 'ChildCount', IntToStr(Node.Count));
if Acc.Get_accDefaultAction(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'DefaultAction', StrText);
end else begin
ListViewItemAdd (LV, 'DefaultAction', '-');
end;
if Acc.Get_accDescription(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Description', StrText);
end else begin
ListViewItemAdd (LV, 'Description', '-');
end;
if Acc.Get_accHelp(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Help', StrText);
end else begin
ListViewItemAdd (LV, 'Help', '-');
end;
if Acc.Get_accHelpTopic(StrText, varChild, TmpInt) = S_OK then begin
ListViewItemAdd (LV, 'HelpTopic', StrText + ' ( ' + IntToStr(TmpInt) + ' )');
end else begin
ListViewItemAdd (LV, 'HelpTopic', '-');
end;
if Acc.Get_accKeyboardShortcut(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'KeyboardShortcut', StrText);
end else begin
ListViewItemAdd (LV, 'KeyboardShortcut', '-');
end;
//Stateプロパティの値を取得して,GetStateTextW関数で説明文字列に変換
//Stateは複数の場合がある
//Stateプロパティの値が2の倍数で増加していくことを利用してループ処理
StrText := '';
StateID := 1;
if Acc.Get_accState(varChild, varState) = S_OK then begin
if TVariantArg(varState).vt = VT_I4 then begin
TmpInt := TVariantArg(varState).lVal;
for i := 1 to 29 do begin
if (TmpInt and StateID) <> 0 then begin
Len := GetStateTextW(StateID, nil, 0);
SetLength(TmpStr, Len);
GetStateTextW(StateID, PWideChar(TmpStr), Len);
StrText := StrText + Trim(TmpStr) + '。' ;
end;
StateID := StateID * 2;
end;
end;
end;
ListViewItemAdd (LV, 'State', StrText);
//最初の1行のみの表示となる(出力先がTListViewなので複数行表示は不可)
if Acc.Get_accValue(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Value', StrText);
end else begin
ListViewItemAdd (LV, 'Value', '-');
end;
ListViewItemAdd (LV, 'ChildID', IntToStr(ChildID));
LV.Items.EndUpdate;
end;
//=============================================================================
// Valueプロパティ取得専用の関数
//=============================================================================
function GetAccObjectValue(Acc: IAccessible; ChildID: Integer): String;
var
varChild : OleVariant;
StrText : WideString;
begin
Result := '';
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
if Acc.Get_accValue(varChild, StrText) = S_OK then begin
Result := StrText;
end;
end;
//=============================================================================
// オブジェクトの位置とサイズを,TRectレコード型に取得する関数
//=============================================================================
function GetAccObjectRect(Acc: IAccessible; ChildID: Integer): TRect;
var
ATop : Integer;
ALeft : Integer;
AWidth : Integer;
AHeight : Integer;
varChild : OleVariant;
begin
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
if Acc.accLocation(ALeft, ATop, AWidth, AHeight, varChild) = S_OK then begin
Result.Top := ATop;
Result.Left := ALeft;
Result.Right := ALeft + AWidth;
Result.Bottom := ATop + AHeight;
end;
end;
//=============================================================================
// オブジェクトの可視状態を取得する関数
// 可視状態ならTrue,非可視であればFlaseを返す
// MSDNによれば,この値は必ずしも正確ではないようだ
// 親オブジェクトの状態も考慮すべきとある
//=============================================================================
function IsAccObjectVisible(Acc: IAccessible; ChildID: Integer): Boolean;
var
varChild : OleVariant;
varState : OleVariant;
VState : Cardinal;
begin
Result := True;
TVariantArg(varChild).vt := VT_I4;
TVariantArg(varChild).lVal := ChildID;
if Acc.Get_accState(varChild, varState) = S_OK then begin
if TVariantArg(varState).vt = VT_I4 then begin
VState := TVariantArg(varState).lVal;
Result := (VState and STATE_SYSTEM_INVISIBLE) = 0;
end;
end;
end;
end.
05_オブジェクトのイベントフック
- エクセルの起動と終了等を監視 |
IAccessible のイベントをフックするサンプルです.イベントのフックは,
- (1) SetWinEventHook 関数でコールバック関数を指定してフックを開始
- (2) コールバック関数内で処理を行う
必要であれば,AccessibleObjectFromEvent 関数を使用する
- (3) UnhookWinEvent 関数でフックを終了する
という手順になります.ここでは,アプリケーションの起動,終了,ウインドウの最小化を監視します.対象アプリをエクセルとしました.この他,どのようなことが可能であるかは,オブジェクトの階層をツリー表示のサンプルを操作して確認してください. |
|
図9
設計時画面 |
|
図10
実行例
|
リスト7
エクセルの起動,終了,ウィンドウの最小化を監視 |
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 FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
hEventHook : THandle;
hTargetWnd : HWND;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses OleAcc;
{$R *.dfm}
// Delphi XEにはないので定義
function AccessibleObjectFromEvent(hWnd : HWND; dwObjewctID : DWORD;
dwChildID : DWORD; out pacc : IAccessible; out pvarChild : OleVariant):
HResult; stdcall; external 'OLEACC.DLL';
//-----------------------------------------------------------------------------
// ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
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;
//-----------------------------------------------------------------------------
// フック関数SetWinEventHookのコールバック関数
//
// AccessibleObjectFromEventは使う必要がなければ使わない方がよい
// オブジェクトの数が多いアプリだと,初回の処理に時間がかかることがある
// ここではサンプルとして,使用する場面を考慮して書いてある
//-----------------------------------------------------------------------------
procedure WinEventProc(hWinEventHook: THandle; event: DWORD; hwnd: HWND;
idObject, idChild: Longint; idEventThread, dwmsEventTime: DWORD); stdcall;
var
acc : IAccessible;
varChild : OleVariant;
begin
//イベントの引数からAcc(IAccessible)とvarChild(OleVariant)を取得
//このサンプルでは,実際にはこれらの値を使用していない
if AccessibleObjectFromEvent(hwnd, idObject, idChild, acc, varChild) = S_OK
then begin
if Form1.hTargetWnd = 0 then begin
if GetClassNameStr(hwnd) = 'XLMAIN' then begin
if event = EVENT_OBJECT_CREATE then begin
Form1.hTargetWnd := hwnd;
Form1.Memo1.Lines.Add('エクセルが起動しました');
end;
end;
end else begin
if Form1.hTargetWnd = hwnd then begin
if event = EVENT_SYSTEM_MINIMIZESTART then begin
Form1.Memo1.Lines.Add('エクセル最小化されました');
end else
if event = EVENT_SYSTEM_MINIMIZEEND then begin
Form1.Memo1.Lines.Add('エクセルが元のサイズに戻りました');
end;
end else
if Form1.hTargetWnd <> 0 then begin
if GetClassNameStr(Form1.hTargetWnd) = '' then begin
Form1.hTargetWnd := 0;
Form1.Memo1.Lines.Add('エクセルが終了しました');
end;
end;
end;
end;
end;
//=============================================================================
// [Start HOOK] フック開始
// hTargetWnd : 対象アプリのハンドル(このサンプルではエクセル)
// hEventHook : フックのハンドル
//
// このサンプルでは,フック開始後,最初に起動したエクセルだけを対象にしている
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
if hEventHook = 0 then begin
hEventHook := SetWinEventHook(
EVENT_MIN,
EVENT_MAX,
0,
WinEventProc,
0,
0,
WINEVENT_OUTOFCONTEXT);
if hEventHook = 0 then begin
Memo1.Lines.Add('フック関数の登録失敗');
end else begin
Memo1.Lines.Add('フック開始');
Memo1.Lines.Clear;
end;
end;
end;
//=============================================================================
// [Stop HOOK] フックの終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
if hEventHook = 0 then begin
Memo1.Lines.Add('フックは終了しています');
end else begin
UnhookWinEvent(hEventHook);
hEventHook := 0;
hTargetWnd := 0;
Memo1.Lines.Add('フック終了');
end;
end;
//=============================================================================
// フォームの作成時 変数の初期化
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
hEventHook := 0;
hTargetWnd := 0;
end;
//=============================================================================
// フォームを閉じる時は,フックを終了させる
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if hEventHook <> 0 then UnhookWinEvent(hEventHook);
end;
end.
今度は,マウスカーソル下のオブジェクトの情報を取得してみます.
マウスカーソル下ですから,本来はマウスフックということになりますが,無理やり IAccessible のフックを使ってみることにします.
前のサンプルの [オブジェクトの階層をツリー表示] では,ツリー表示のオブジェクト名をマウスで選択し,そのオブジェクトを赤い枠線で囲みましたが,その逆の動作とも言えます.
IAccessible のフックでは,マウスの移動を捕らえることはできません.そこで,フックのコールバック関数内で,GetCursorPos 関数でマウスカーソル位置を取得して処理しています.
全画面サイズの透明なフォームを表示します.このフォームに.マウスカーソル下のオブジェクトを赤い枠線で表示するようにしています.また,[F11] キーをホットキーに登録しています,したがって,[F11] を押すとフックが終了して,そのオブジェクトの情報を確認することができます.
[370] キャプチャの範囲 - [マウスカーソル下のオブジェクト]] では,この方法を利用して,マウスカーソル下のオブジェクトのキャプチャを実装しています.
|
|
図11
設計時画面 |
|
図12
マウスカーソル下の情報実行例
- 対象のオブジェクトを赤い枠線で表示
- [Stop Hook] または [F11] キーで終了
|
リスト8
マウスカーソル下のオブジェクトを赤い枠線で表示 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, OleAcc, ActiveX, plWindowRectFormUnit;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ListView1: TListView;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
protected
procedure WMHotKey(var Message: TWMHotKey); message WM_HOTKEY;
private
{ Private 宣言 }
hEventHook : THandle;
RectForm : TplWindowRectForm;
hRectFormWnd : HWND;
OldRectWnd : TRect;
FlagEvent : Boolean;
RegistHotKeyID : Integer;
procedure AccObjectInfoToListView(Acc: IAccessible; varChild: OleVariant;
LV: TListView);
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// Delphi XEにはないので定義
function AccessibleObjectFromEvent(hWnd : HWND; dwObjewctID : DWORD;
dwChildID : DWORD; out pacc : IAccessible; out pvarChild : OleVariant):
HResult; stdcall; external 'OLEACC.DLL';
function AccessibleObjectFromPoint(ptScreen: TPOINT; out ppAcc: IAccessible;
out pvarChild: OleVariant): HRESULT ; stdcall; external 'OLEACC.DLL';
function WindowFromAccessibleObject(paccContainer: IAccessible; var phwnd: HWND):
HRESULT;stdcall; external 'OLEACC.DLL';
function GetRoleTextW(dwRole: DWORD; lpszRole: PWideChar;
cchRoleMax: UINT): UINT; stdcall; external 'OLEACC.DLL';
//-----------------------------------------------------------------------------
// フック関数SetWinEventHookのコールバック関数
//
// マウスポイント下のオブジェクトを検出して,そのオブジェクトを囲む矩形を描画
//-----------------------------------------------------------------------------
procedure WinEventProc(hWinEventHook: THandle; event: DWORD; hwnd: HWND;
idObject, idChild: Longint; idEventThread, dwmsEventTime: DWORD); stdcall;
//---------------------------------------------------------------------------
// 関数内関数
// オブジェクトAccの位置とサイズを取得
//---------------------------------------------------------------------------
function GetAccObjectRect(Acc: IAccessible; varChild: OleVariant): TRect;
var
ATop : Integer;
ALeft : Integer;
AWidth : Integer;
AHeight : Integer;
begin
if Acc.accLocation(ALeft, ATop, AWidth, AHeight, varChild) = S_OK then begin
Result := Rect(ALeft, ATop, ALeft + AWidth, ATop + AHeight);
end else begin
SetRectEmpty(Result);
end;
end;
//---------------------------------------------------------------------------
var
MousePos : TPoint;
pAcc : IAccessible;
varChild : OleVariant;
hTargetWnd : Windows.HWND;
RectAcc : TRect;
LineWidth : Integer;
LineColor : TColor;
begin
//イベント内の処理中は,イベント処理をしない
if Form1.FlagEvent then exit;
Form1.FlagEvent := True;
//マウスカーソル位置を取得
GetCursorPos(MousePos);
//マウスカーソル位置のIAccessibleオブジェクトを取得
//AccessibleObjectFromPoint関数の戻り値のvarChildのvtは,常にVT_I4となっている
if AccessibleObjectFromPoint(MousePos, pAcc, varChild) = S_OK then begin
//オブジェクトのサイズと位置を検出して,前のサイズと位置が異なっていれば処理
RectAcc := GetAccObjectRect(pAcc, varChild);
if not EqualRect(RectAcc, Form1.OldRectWnd) then begin
//このサンプルでは,オブジェクトを囲む矩形描画に透明なフォームを使用している
//このフォームを対象外とするため,マウスカーソル下のハンドルを比較
//オブジェクトを囲む矩形描画用のフォームだったら処理しない
if WindowFromAccessibleObject(pAcc, hTargetWnd) = S_OK then begin
if hTargetWnd <> Form1.hRectFormWnd then begin
Form1.OldRectWnd := RectAcc;
Form1.RectForm.Canvas.FillRect(Form1.RectForm.ClientRect);
//枠線のサイズと色を指定して描画
LineWidth := 4;
LineColor := clRed;
with Form1.RectForm.Canvas do begin
Pen.Color := LineColor;
Pen.Width := LineWidth;
MoveTo (RectAcc.Left, RectAcc.Top);
LineTo (RectAcc.Right, RectAcc.Top);
LineTo (RectAcc.Right, RectAcc.Bottom);
LineTo (RectAcc.Left, RectAcc.Bottom);
LineTo (RectAcc.Left, RectAcc.Top);
end;
//ListViewにオブジェクトの情報を表示
Form1.AccObjectInfoToListView(pAcc, varChild, Form1.ListView1);
Form1.RectForm.BringToFront;
end;
end;
end;
end;
Form1.FlagEvent := False;
varChild := Unassigned;
end;
//=============================================================================
// このフォームのOnCreateイベント
// フォームの作成時 変数の初期化
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
hEventHook := 0;
Button1.Enabled := True;
Button2.Enabled := False;
end;
//=============================================================================
// このフォームのOnCloseQueryイベント
// フォームを閉じる時は,フックを終了させる
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
Button2Click(nil);
end;
//=============================================================================
// WM_HOTKEYメッセージ処理
// 登録したホットキーを検出したら,フック終了
//=============================================================================
procedure TForm1.WMHotKey(var Message: TWMHotKey);
begin
if Message.HotKey = RegistHotKeyID then begin
Button2Click(nil);
end;
end;
//=============================================================================
// [Start HOOK]ボタン
// フック開始
// hEventHook : フックのハンドル
// hRectFormWnd : オブジェクトを囲む枠線を描画するためのフォームのハンドル
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
SetRectEmpty(OldRectWnd);
FlagEvent := False;
ListView1.Items.Clear;
if hEventHook = 0 then begin
hEventHook := SetWinEventHook(
EVENT_MIN,
EVENT_MAX,
0,
WinEventProc,
0,
0,
WINEVENT_OUTOFCONTEXT);
if hEventHook = 0 then begin
MessageDlg('フック関数の登録失敗', mtInformation, [mbOK], 0);
end else begin
//オブジェクトの周囲を囲む枠線用のフォームを作成して表示
//このフォームは透明なので,実際には見えない
if not Assigned(RectForm) then begin
RectForm := TplWindowRectForm.Create(Self);
RectForm.Show;
Sleep(100);
//そのフォームのハンドルを取得しておく
hRectFormWnd := FindWindow('TplWindowRectForm', nil);
end else begin
//表示中であれば,オブジェクトを囲む枠線を消去
RectForm.Canvas.FillRect(RectForm.ClientRect);
end;
Button1.Enabled := False;
Button2.Enabled := True;
//アトムテーブルに文字列(ホットキーを識別する文字列)を登録
//以後のホットキーの操作はこのResistHokkeyで行うことになる
RegistHotKeyID := GlobalAddAtom('plObjectInfoUnderMouse');
//ホットキーにとして[F11]を登録
//本サンプルが終了するとこの設定は無効となる
RegisterHotkey(Handle, RegistHotKeyID, 0, VK_F11);
end;
end;
end;
//=============================================================================
// [Stop HOOK]ボタン
// フックの終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
UnhookWinEvent(hEventHook);
hEventHook := 0;
FlagEvent := False;
Button1.Enabled := True;
Button2.Enabled := False;
if RegistHotKeyID <> 0 then begin
//アトムテーブルから関連した文字列を削除
GlobalDeleteAtom(RegistHotKeyID);
//ホットキーの登録解除
UnregisterHotKey(Handle, RegistHotKeyID);
end;
//オブジェクトの周囲を囲む枠線用のフォームを解放
if Assigned(RectForm) then begin
RectForm.Close;
FreeAndNil(RectForm);
end;
end;
//=============================================================================
// ListViewにオブジェクトAccの情報を取得して表示
//
// ListView1のViewStyleはvsReportで,カラムは2つ
// ListView1.Columns[1].AutoSize := Trueにしている
//=============================================================================
procedure TForm1.AccObjectInfoToListView(Acc: IAccessible; varChild: OleVariant;
LV: TListView);
//---------------------------------------------------------------------------
// 関数内関数
// ウィンドウのクラス名を取得する関数
//---------------------------------------------------------------------------
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;
//---------------------------------------------------------------------------
// 関数内関数
// ListViewにItemを追加するメソッド
//---------------------------------------------------------------------------
procedure ListViewItemAdd(LV: TListView; ACaption, SubItem: String);
var
LVItem : TListItem;
begin
LVItem := LV.Items.Add;
LVItem.Caption := ACaption;
LVItem.SubItems.Text := SubItem;
end;
//---------------------------------------------------------------------------
var
hWindow : HWND;
StrText : WideString;
ATop : Integer;
ALeft : Integer;
AWidth : Integer;
AHeight : Integer;
varRole : OleVariant;
Len : Cardinal;
begin
LV.Items.Clear;
LV.Items.BeginUpdate;
//このオブジェクトを有するウィンドウのハンドル
StrText := '';
hWindow := 0;
if WindowFromAccessibleObject(Acc, hWindow) = S_OK then begin
ListViewItemAdd (LV, 'Class Name', GetClassNameStr(hWindow));
end;
//Roleプロパティ
StrText := '-';
if Acc.get_accRole(varChild, varRole) = S_OK then begin
if TVariantArg(varRole).vt = VT_I4 then begin
Len := GetRoleTextW(TVariantArg(varRole).lVal, nil, 0);
SetLength(StrText, Len);
GetRoleTextW(TVariantArg(varRole).lVal, PWideChar(StrText), Len + 1);
end;
end;
ListViewItemAdd (LV, 'Role', StrText);
//Nameプロパティ
if Acc.Get_accName(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Name', StrText);
end else begin
ListViewItemAdd (LV, 'Name', '-');
end;
//Valueプロパティ
if Acc.Get_accValue(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Value', StrText);
end else begin
ListViewItemAdd (LV, 'Value', '-');
end;
//Descriptionプロパティ
if Acc.Get_accDescription(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'Description', StrText);
end else begin
ListViewItemAdd (LV, 'Description', '-');
end;
//オブジェクトの幅と高さ
AWidth := 0;
AHeight := 0;
if Acc.accLocation(ALeft, ATop, AWidth, AHeight, varChild) = S_OK then begin
ListViewItemAdd (LV, 'X', IntToStr(ALeft));
ListViewItemAdd (LV, 'Y', IntToStr(ATop));
ListViewItemAdd (LV, 'Width', IntToStr(AWidth));
ListViewItemAdd (LV, 'Height', IntToStr(AHeight));
end;
if Acc.Get_accDefaultAction(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'DefaultAction', StrText);
end else begin
ListViewItemAdd (LV, 'DefaultAction', '-');
end;
//キーボードショートカット
if Acc.Get_accKeyboardShortcut(varChild, StrText) = S_OK then begin
ListViewItemAdd (LV, 'KeyboardShortcut', StrText);
end else begin
ListViewItemAdd (LV, 'KeyboardShortcut', '-');
end;
varRole := Unassigned;
LV.Items.EndUpdate;
Sleep(1);
end;
end.
下のコードが,オブジェクトの周囲に赤い枠線を描画するフォームのコードです.
このフォームは透明にしています.マウスイベントは捕捉できません.マウスイベントも透過してしまいます.
このフォームの OnPaint イベント内で赤い枠線を描画した方が効率がいいかも知れません.
この透明なフォームは,タスクバー上のオブジェクトも選択可能にするために,常に最前面に表示するようにしています.ただし,同じ様に,常に最前面に表示するようなアプリケーションが起動している場合,動作の保障はありません.
|
[備考]
透明なフォームでも描画はできるわけです.したがって,見かけ上は「デスクトップにお絵かき」といったこともできます.しかも,意識的に消去しないかぎり,永続的に描画内容を表示できます.マウスやキーイベントは透過してしまいますから,グローバルフックを使用しないと捕捉できません.
|
リスト9
オブジェクトの周囲に赤い枠線を描画するための透明なフォーム
全画面表示で,タスクバー上にも表示する |
unit plWindowRectFormUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TplWindowRectForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
OriginalProc: TWndMethod;
procedure SubClassProc(var Message: TMessage);
public
{ Public 宣言 }
end;
var
plWindowRectForm: TplWindowRectForm;
implementation
{$R *.dfm}
//=============================================================================
// 元のWindowsProcを退避して新しいWindowProcを定義
// この設定は用途によってはOnShowで設定する必要があるかも知れない
// OnShow設定の場合は,OnCloseでWindowProcを元に戻しておいた方がいいだろう
//
// このフォームはオブジェクトインスペクタで以下の設定をしている
// この設定は,タスクバーの上にもこのフォームを重ねて表示する
// RectForm.BorderIcons := [biSystemMenu];
// RectForm.BorderStyle := bsNone;
// RectForm.Color := clWhite;
// RectForm.FormStyle := fsStayOnTop;
// RectForm.Position := poDesigned;
// RectForm.TransparentColor := True;
// RectForm.TransparentColorValue := clWhite;
// RectForm.WindowState := wsMaximized;
//=============================================================================
procedure TplWindowRectForm.FormCreate(Sender: TObject);
begin
SetWindowLong(Handle, GWL_STYLE, WS_POPUP);
OriginalProc := Self.WindowProc;
Self.WindowProc := SubClassProc;
end;
//=============================================================================
// WindowsProcを元に戻しておく
// このサンプルではOnCreateでWindowProcを設定しているので実際には不要
//=============================================================================
procedure TplWindowRectForm.FormDestroy(Sender: TObject);
begin
Self.WindowProc := OriginalProc;
end;
//=============================================================================
// WindowProcのサブクラス関数
// このフォームがZオーダのトップかを監視して設定
//=============================================================================
procedure TplWindowRectForm.SubClassProc(var Message: TMessage);
//Targetが最前面かを判定する関数
function IsTopMost(Target: HWND): boolean;
begin
Result := ((GetWindowLong(Target, GWL_EXSTYLE) and WS_EX_TOPMOST) <> 0);
end;
begin
//元のWindowProcを実行
OriginalProc(Message);
//このフォームが最前面でなければ最前面にする
if IsTopMost(Self.Handle) = False then begin
SetWindowPos(Self.Handle,
HWND_TOPMOST,
0,
0,
0,
0,
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOOWNERZORDER);
end;
end;
end.
07_SetWinEventHook 関数と UnhookWinEvent 関数 |
上のサンプルで使用している,フックの登録と解除の関数の引数と定数値の説明です.
詳しい内容は,参考リンク等,MSDNの記事を参考にしてください. |
SetWinEventHook (eventMin,
eventMax,
hmodWinEventProc,
pfnWinEventProc,
idProcess,
idThread,
dwFlags): THandle
引数 |
型 |
説明 |
eventMin |
DWORD |
表 1 の何れかの値.検出するイベント定数の最小値 |
eventMax |
DWORD |
表 1 のいずれかの値.検出するイベント定数の最大値 |
hmodWinEventProc |
HMODULE |
コールバック関数がDLL内にある場合は,DLLのハンドル
それ以外は0とする |
pfnWinEventProc |
TFNWinEventProc |
コールバック関数へのポインタ |
idProcess |
DWORD |
イベントを検出するプロセスの ID の指定
0 にすると現在のデスクトップ上の全てが対象 |
idThread |
DWORD |
イベントを検出するスレッドの ID の指定
0 にすると,現在のデスクトップ上の全てが対象 |
dwFlags |
DWORD |
フック関数の場所と,イベントスキップ動作の指定
表 2 の値の組み合わせ |
戻り値 |
THandle |
フック関数のハンドル
フック解除時に,UnhookWinEvent 関数の引数に使用 |
UnhookWinEvent (hWinEventHook): BOOL
引数 |
型 |
説明 |
hWinEventHook |
THandle |
SetWinEventHook 関数の戻り値を与える |
戻り値 |
BOOL |
成功した場合は True.それ以外は False を返す |
表1
イベントの定数値
Delphi XE の Windows.pas 内の定義リスト (EVENT_MINとEVENT_MAX 以外は値の小さい順)
UIA 関係 (Microsoft Windows User Interface Automation) は除く |
EVENT_MIN ( = $00000001 )
EVENT_MAX ( = $7FFFFFFF ) |
イベント値の最小値と最大値.この値を指定すると,可能な全てのイベント検出となる |
EVENT_SYSTEM_SOUND |
サウンドの再生 |
EVENT_SYSTEM_ALERT |
アラートの生成 |
EVENT_SYSTEM_FOREGROUND |
フォアグラウンドウィンドウの変更 |
EVENT_SYSTEM_MENUSTART |
メニューバーのメニュー項目の選択 |
EVENT_SYSTEM_MENUEND |
メニューバーからメニューが閉じられる |
EVENT_SYSTEM_MENUPOPUPSTART |
ポップアップメニューの表示 |
EVENT_SYSTEM_MENUPOPUPEND |
ポップアップメニューが閉じるイベント |
EVENT_SYSTEM_CAPTURESTART |
マウスキャプチャの開始 |
EVENT_SYSTEM_CAPTUREEND |
マウスキャプチャの解除 |
EVENT_SYSTEM_MOVESIZESTART |
ウィンドウの移動またはサイズ変更 |
EVENT_SYSTEM_MOVESIZEEND |
ウィンドウの移動やサイズ変更完了 |
EVENT_SYSTEM_CONTEXTHELPSTART |
コンテキスト依存ヘルプモード開始 |
EVENT_SYSTEM_CONTEXTHELPEND |
コンテキスト依存ヘルプモードの終了 |
EVENT_SYSTEM_DRAGDROPSTART |
ドラッグアンドドロップモードの開始 |
EVENT_SYSTEM_DRAGDROPEND |
ドラッグアンドドロップモードの終了 |
EVENT_SYSTEM_DIALOGSTART |
ダイアログボックスの表示 |
EVENT_SYSTEM_DIALOGEND |
ダイアログボックスの閉じるイベント |
EVENT_SYSTEM_SCROLLINGSTART |
スクロールバーのスクロール開始 |
EVENT_SYSTEM_SCROLLINGEND |
スクロールバーのスクロール終了 |
EVENT_SYSTEM_SWITCHSTART |
[Alt] + [Tab] キー押下の検出 |
EVENT_SYSTEM_SWITCHEND |
[Alt] + [Tab] キー操作により切換えの検出 |
EVENT_SYSTEM_MINIMIZESTART |
ウィンドウの最小化 |
EVENT_SYSTEM_MINIMIZEEND |
ウィンドウの最小化からの復元 |
EVENT_SYSTEM_DESKTOPSWITCH |
アクティブデスクトップの切り替え |
EVENT_SYSTEM_END |
システムイベントの最大値 |
EVENT_OEM_DEFINED_START |
OEM 用の予約値 |
EVENT_OEM_DEFINED_END |
OEM 用の予約値 |
EVENT_OBJECT_CREATE |
オブジェクトの作成 |
EVENT_OBJECT_DESTROY |
オブジェクトの破棄 |
EVENT_OBJECT_SHOW |
非可視オブジェクトの表示 (可視化) |
EVENT_OBJECT_HIDE |
オブジェクトの非可視 |
EVENT_OBJECT_REORDER |
コンテナオブジェクトの子オブジェクトの追加,削除,または配置変更
IE のページ内容の変更も含まれる |
EVENT_OBJECT_FOCUS |
キーボードフォーカスの検出 |
EVENT_OBJECT_SELECTION |
コンテナオブジェクト内の選択要素の変更 |
EVENT_OBJECT_SELECTIONADD |
選択されているコンテナオブジェクトへのオブジェクトの追加 |
EVENT_OBJECT_SELECTIONREMOVE |
コンテナオブジェクト内の子オブジェクトの削除 |
EVENT_OBJECT_SELECTIONWITHIN |
コンテナオブジェクト内の複数選択の変更
コールバック関数の引数はコンテナオブジェクトの値 |
EVENT_OBJECT_STATECHANGE |
オブジェクトのStateプロパティの変更
IEのページ内容変更も検出 |
EVENT_OBJECT_LOCATIONCHANGE |
位置,形状,サイズ等の変更
IE のページの内容変更も含む |
EVENT_OBJECT_NAMECHANGE |
オブジェクトの Name プロパティの変更 |
EVENT_OBJECT_DESCRIPTIONCHANGE |
オブジェクトの Description プロパティの変更 |
EVENT_OBJECT_VALUECHANGE |
オブジェクトの Value プロパティの変更
IE のウェブページの要素の内容変更等 |
EVENT_OBJECT_PARENTCHANGE |
オブジェクトの親オブジェクトの変更 |
EVENT_OBJECT_HELPCHANGE |
Help プロパティの変更 |
EVENT_OBJECT_DEFACTIONCHANGE |
オブジェクトの DefaultAction プロパティの変更 |
EVENT_OBJECT_ACCELERATORCHANGE |
オブジェクトの KeyboardShortcut プロパティの変更 |
EVENT_OBJECT_INVOKED |
オブジェクトの操作
例えばボタンクリック等の検出 |
EVENT_OBJECT_TEXTSELECTIONCHANGED |
オブジェクトのテキストの選択変更 |
EVENT_OBJECT_CONTENTSCROLLED |
スクロールウィンドウに関連付けされたオブジェクトのスクロール終了 |
EVENT_SYSTEM_ARRANGMENTPREVIEW |
プレビュー用の四角形表示 |
EVENT_OBJECT_END |
OBJECT イベント値のもっとも大きい値 |
コールバック関数の場所と,イベントのスキップ関係の設定である,dwFlag の指定は,以下のいずれかの組み合わせとなります.
SKIP 関係のフラグは,MSDN の記事が解読できませんでした.おそらく,コールバック関数の再登録の防止ではないかと思われます.動作の確認はしていません. |
dwFlags := WINEVENT_INCONTEXT;
dwFlags := WINEVENT_OUTOFCONTEXT;
dwFlags := WINEVENT_INCONTEXT or WINEVENT_SKIPOWNPROCESS;
dwFlags := WINEVENT_INCONTEXT or WINEVENT_SKIPOWNTHREAD;
dwFlags := WINEVENT_OUTOFCONTEXT or WINEVENT_SKIPOWNPROCESS;
dwFlags := WINEVENT_OUTOFCONTEXT or WINEVENT_SKIPOWNTHREAD;
表2
コールバック関数の場所と,イベントのスキップ関係の指定 |
WINEVENT_INCONTEXT |
コールバック関数を DLL 内に置く場合の指定 |
WINEVENT_OUTOFCONTEXT |
コールバック関数を同一プロセス内で実行する場合の指定 |
WINEVENT_SKIPOWNPROCESS |
フックを登録したプロセス内で発生したイベントを受信してから,このフックのインスタンスを防止する (?) |
WINEVENT_SKIPOWNTHREAD |
フックを登録したスレッド内で発生したイベントを受信してから,このフックのインスタンスを防止する(?) |
フックのコールバック関数の引数と,定数値等の説明です.他のフックのコールバック関数同様,関数の名前は任意ですが,引数の順番と型は一致させなければなりません.
引数,定数値の詳しい内容は,MSDNの記事等を参考にしてください.
必要であれば,イベント内で,AccessibleObjectFromEvent 関数を使用して,IAccessible オブジェクトのインスタンスや子オブジェクトを VARIANT 型として取得することができます. |
WinEventProc(hWinEventHook,
event,
hwnd,
idObject,
idChild,
idEventThread,
dwmsEventTime)
引数 |
型 |
説明 |
hWinEventHook |
THandle |
SetWinEventHook フック関数のハンドル |
event |
DWORD |
発生したイベント.表 1 のいずれかの値 |
hwnd |
HWND |
イベントを発生したウィンドウのハンドル |
idObject |
LONG |
イベントを発生したオブジェクトのカテゴリ.表 3 のいずれかの値 |
idChild |
LONG |
イベントを発生した子オブジェクトのID値
CHILDID_SELF の時はオブジェクト自身 |
dwEventThread |
DWORD |
イベントを発生したスレッド
またはイベントを発生したウィンドウのスレッド |
dwmsEventTime |
DWORD |
イベントの生成時刻で,ミリ秒単位 |
戻り値 |
- |
なし |
OBJID_ALERT |
ウィンドウまたはアプリ関連付けのアラート |
OBJID_CARET |
テキストエリアのキャレット |
OBJID_CLIENT |
ウィンドウのクライアント領域のオブジェクト |
OBJID_CURSOR |
マウスのポインタ |
OBJID_HSCROLL |
ウィンドウの水平スクロールバー |
OBJID_NATIVEOM |
サードパーティ独自の COM インターフェイス用 |
OBJID_MENU |
ウィンドウのメニューバー |
OBJID_QUERYCLASSNAMEIDX |
Oleacc.dll が内部で使用するオブジェクト識別子 |
OBJID_SIZEGRIP |
ウィンドウのサイズグリップ(ウィンドウの右下隅) |
OBJID_SOUND |
サウンドを再生しているアプリケーションの子オブジェクト |
OBJID_SYSMENU |
ウィンドウのシステムメニュー |
OBJID_TITLEBAR |
ウィンドウのタイトルバー |
OBJID_VSCROLL |
ウィンドウの垂直スクロールバー |
OBJID_WINDOW |
ウィンドウの子オブジェクト |
|