UI Automation Integration

作成日: 2025年1月4日

使用モデル OpenAI 01 by OpenAI01

質問

---
START OF THE DELPHI UI AUTOMATION FRAMEWORK PART

Do you know what DelphiUIAutomation is?

Below are its documentation file README.md.


README.md:
```
# DelphiUIAutomation
Delphi classes that wrap the [MS UIAutomation library](https://msdn.microsoft.com/en-us/library/vstudio/ms753388(v=vs.100).aspx).

DelphiUIAutomation is a framework for automating rich client applications based on Win32 (and specifically tested with Delphi XE5). It is written in Delphi XE5 and it requires no use of scripting languages.

Tests and automation programs using DelphiUIAutomation can be written with Delphi and used in any testing framework available to Delphi.

It provides a consistent object-oriented API, hiding the complexity of Microsoft's UIAutomation library and windows messages.

## Getting started

The DelphiUIAutomation library is a wrapper for the UIAutomationClient library, which has been extracted into the UIAutomationClient_TLB source file. As the generated code is large and complex, this has been wrapped up in a number of units, each providing classes that encapsulate part of this library (together with other utility methods).

### Initialise the libray

```pascal
TUIAuto.CreateUIAuto; // Initialise the library
```

### Launching an application

The TAutomationApplication class provides functionality to start and attach to an application. There are 3 class methods provided to do this.

* Launch - this will launch the application supplied, and pass in any supplied arguments
* Attach - this will attach to an already launched application, based on the executable name
* LaunchOrAttach - this will either attach to an already launched application, or launch the application.

The snippet below will check whether the Project1.exe is running, and attach if it is, or it will start the application, and then attach to it.

```pascal
Application := TAutomationApplication.LaunchOrAttach(
'democlient\Win32\Debug\Project1.exe',
''
```

When attached to an application, it is possible to call further methods on it

* Kill - Will kill the process associated with the application
* WaitWhileBusy - Waits for the application to be idle, this has an optional timeout (in milliseconds) associated with it
* SaveScreenshot - Takes a screenshot of the application, and then saves this to a file.

### Getting hold of a window

To get a 'desktop' window (i.e. one that appears in the Windows tasks bar), then the TAutomationDesktop class provides a class function that returns a TAutomationWindow object.

```pascal
var
notepad : IAutomationWindow;
...

notepad := TAutomationDesktop.GetDesktopWindow('Untitled - Notepad');
notepad.Focus;
```

This will find (it is there) a window that has the given title, and set focus to it. This window is independant of the overalll application, and might not even be associated with the same application that is being automated.

To get an 'application' window, i.e. one associated with another window, first the parent window must be found, and then the target child can be found using the ''Window'' method. In the example below, the child window 'Security' of the notepad window is searched for.

```pascal
var
security : IAutomationWindow
...

security := notepad.Window('Security');
```

### Finding a control

Each control contained in a window can be identified by the index of that control OR sometimes (this depends on the control type) by the text associated with it. For example, in order to get the textbox associated with the connection window (and assuming that it is the 1st Edit box on the window), the following code will find the editbox, and change the text to be USER1.

```pascal
var
user : IAutomationEditBox;

user := connect.GetEditBoxByIndex(0);
user.Text := 'USER1';
```

### Invoking actions on controls

In order to click the 'OK' button associated with the connection window, it can be found by the text associated with the button, the following code will find the button, and call the click event.

```pascal
var
btnOK : IAutomationButton;
...

btnOK := connect.GetButton('OK');
btnOk.Click;
```

# Current supported controls

The currently supported controls are ...

* TButton
* TCheckBox
* TComboBox
* TEditBox
* TRadioButton
* TStatusBar
* TStringGrid (using an extended TStringGrid control that implements UIAutomation patterns)
* TPageControl
* TTab
* TTextBox
* TTreeView and TTreeViewItem


[More details, and the status of currently supported controls](https://github.com/jhc-systems/DelphiUIAutomation/wiki/CurrentSupportedControls)

# Added Automation Support for Controls

Many Delphi controls do not implement the automatin interfaces in the same manner as Visual Studio does in WPF, so that the Automation ID and Name are not 'properly' populated, so the controls can only be found by knowing their position within the tree, and cannot be found via the name or ID. The controls below extend the basic controls to export these values, amongst other properties.

## TEdit & TCombobox

The [controls sub-project](https://github.com/jhc-systems/DelphiUIAutomation/tree/master/controls) extends the automation properties of the TEdit and TComboBox, to simulate the way that WPF populates the Automation ID and the name with the NAME of the actual control, not a random value.

## TStringGrid

The [controls sub-project](https://github.com/jhc-systems/DelphiUIAutomation/tree/master/controls) allows the automation of some of the elements of the TStringGrid. It extends the control to allow it to interact with the MS-UIAutomation libraries.

```pascal
var
grid : IAutomationStringGrid;
items : TObjectList<TAutomationStringGridItem>;
item : TAutomationStringGridItem;
item1 : IAutomationStringGridItem;
...

// Get the first string grid associated with the window
grid := enquiry.GetStringGridByIndex(0);

// Show what the value is (i.e. the contents of the selected cell)
writeln ('Value is ' + grid.Value);

// Get the cell at 3,3 and shows it's value
writeln ('Item @ 3-3 is ' +grid.GetItem(3,3).Name);

// Get the selected cell
item := grid.Selected;

// Show the value of the selected cell (should be the same as the Grid's value
writeln ('Selected is ' + item.Name);

// Get the list of column headers (i.e. first fixed row)
write ('Column Headers : ');
items := grid.ColumnHeaders;

for item in items do
begin
writeln (item.Name);
end;

// Select the item at 2,4
item1 := grid.GetItem(2,4);

// Show that selection has changed.
writeln ('Selected is ' + grid.Selected.Name);

```

## TTreeView and TTreeViewItem
```pascal
// Get the 0th treeview
tv1 := enquiry.getTreeViewByIndex(0);

// Get the item with the following text
tvi := tv1.GetItem('Sub-SubItem');

// Select the item
tvi.select;
```

## Navigating to specific elements in the StringGrid and right-clicking

As the automation does not expose the cells fully (as they do not technically exist in the TStringGrid), it is necessary to do the following ..

```pascal
// Get the grid item
item := grid.GetItem(3,3);

// Select it
item.Select;

// Create a mouse to move the pointer
mouse := TAutomationMouse.Create;

// Get the bounding rectangle of the item (this is relative to the grid)
itemRect := item.BoundingRectangle;

// Get the overall grid bounding rectangle
gridRect := grid.BoundingRectangle;

// Move to the correct location, offsetting to make sure the mouse point is inside the cells itself
mouse.Location := TPoint.Create(gridRect.left + itemRect.left +15, gridRect.Top + itemRect.top +15);
mouse.LeftClick;
mouse.RightClick;
```

# Contributors
* [Mark Humphreys](https://github.com/markhumphreysjhc)
* Robert Deutschmann - Example Howto and AutomatedButton

# License
Apache Version 2.0 Copyright (C) 2015

See license.txt for details.

# See also
* [Microsoft Accessibility](https://msdn.microsoft.com/en-us/library/vstudio/ms753388(v=vs.100).aspx)
* [UIAutomation for Powershell](http://uiautomation.codeplex.com/documentation)
* [TestStack.White](https://github.com/TestStack/White)
* [UI Automation - A wrapper around Microsoft's UI Automation libraries.](https://github.com/vijayakumarsuraj/UIAutomation)

```


END OF DELPHI UI AUTOMATION FRAMEWORK PART
---
START OF MY PROJECT PART


See, this is a project (pScannerTest.dproj) that I'm doing in Delphi to parse the elements of any window or interface opened in Windows:

pScannerTest.dpr:
```
program pScannerTest;

{$APPTYPE CONSOLE}

uses
System.SysUtils,
Winapi.Windows,
uControlData in 'SUBMODULES\uControlData.pas',
uUICompData in 'SUBMODULES\uUICompData.pas',
uWindowData in 'SUBMODULES\uWindowData.pas',
uControlIDSource in 'SUBMODULES\uControlIDSource.pas',
uWinAPISource in 'SUBMODULES\uWinAPISource.pas',
uWindowEnumSource in 'SUBMODULES\uWindowEnumSource.pas',
uWindowProcessor in 'SUBMODULES\uWindowProcessor.pas',
uControlProcessor in 'SUBMODULES\uControlProcessor.pas',
uUIScannerSpot in 'uUIScannerSpot.pas',
uUIScannerLampBulb in 'uUIScannerLampBulb.pas',
uUIScannerHandler in 'uUIScannerHandler.pas',
uUIScannerMirror in 'uUIScannerMirror.pas',
uUIScannerCircuitSensor in 'uUIScannerCircuitSensor.pas',
uUIScannerExtractor in 'uUIScannerExtractor.pas',
uScanModes in 'uScanModes.pas',
uUIScannerLens in 'uUIScannerLens.pas',
uUIScannerNavigator in 'uUIScannerNavigator.pas',
uChildControlEnumerator in 'uChildControlEnumerator.pas';

var
Spot: TUIScannerSpot;
Handler: TUIScannerHandler;
Extractor: TUIScannerExtractor;
Navigator: TUIScannerNavigator;
WindowData: TWindowData;
WindowDetails: TWindowDetails;
Handle: HWND;
I: Integer;
FaxWindow: HWND;
begin
try
Spot := TUIScannerSpot.Create;
Handler := TUIScannerHandler.Create;
Extractor := TUIScannerExtractor.Create;
Navigator := TUIScannerNavigator.Create;
try
// Find the "Fax e Scanner do Windows" window
FaxWindow := FindWindow(nil, 'Fax e Scanner do Windows'); // Use the correct window title
if FaxWindow = 0 then
begin
Writeln('Fax e Scanner do Windows window not found.');
ReadLn;
Exit;
end;

// Set the window for the Navigator
Navigator.SetWindow(FaxWindow);

// Access the UI elements
WriteLn('UI Elements in Fax e Scanner do Windows:');
for I := 0 to Navigator.GetUIElements.Count - 1 do
begin
with Navigator.GetUIElements[I] do
begin
WriteLn(' Handle: ', Handle, ', Type: ', Ord(ControlType), ', Text: ', ControlText);
end;
end;

finally
Spot.Free;
Handler.Free;
Extractor.Free;
Navigator.Free;
end;

ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

```

Below are the units in the `[root folder of my project]` folder of the project (which is this project of mine, pScannerTest.dproj).

uChildControlEnumerator.pas:
```
unit uChildControlEnumerator;

interface

uses
Winapi.Windows,
System.SysUtils,
System.Generics.Collections,
uControlData,
uWinAPISource,
uControlProcessor;

type
TChildControlEnumerator = class
private
FWinAPISource: TWindowsAPISource;
FControlProcessor: TControlProcessor;
FControlList: TList<TControlData>;
function GetControlCount: Integer;
public
constructor Create;
destructor Destroy; override;
procedure EnumerateControls(AParentWindow: HWND);
function GetControl(Index: Integer): TControlData;
property ControlCount: Integer read GetControlCount;
property ControlList: TList<TControlData> read FControlList;
end;

var
GlobalEnumChildEnumerator: TChildControlEnumerator = nil;

function EnumChildProc(AHandle: HWND; AParam: LPARAM): BOOL; stdcall;

implementation

constructor TChildControlEnumerator.Create;
begin
inherited Create;
FWinAPISource := TWindowsAPISource.GetInstance;
FControlProcessor := TControlProcessor.Create;
FControlList := TList<TControlData>.Create;
end;

destructor TChildControlEnumerator.Destroy;
begin
FControlList.Free;
FControlProcessor.Free;
inherited Destroy;
end;

procedure TChildControlEnumerator.EnumerateControls(AParentWindow: HWND);
begin
FControlList.Clear;
GlobalEnumChildEnumerator := Self;
EnumChildWindows(AParentWindow, @EnumChildProc, 0);
GlobalEnumChildEnumerator := nil;
end;

function EnumChildProc(AHandle: HWND; AParam: LPARAM): BOOL; stdcall;
var
ControlData: TControlData;
begin
Result := True;

if not Assigned(GlobalEnumChildEnumerator) then
Exit;

try
// Determine control type using uWinAPISource and uControlProcessor
ControlData := TControlData.Create(AHandle, GlobalEnumChildEnumerator.FControlProcessor.DetermineControlType(AHandle), '', GlobalEnumChildEnumerator.FWinAPISource.GetWindowRect(AHandle));
try
// Process control to get additional data
GlobalEnumChildEnumerator.FControlProcessor.ProcessControl(ControlData);

// Add control data to the list
GlobalEnumChildEnumerator.FControlList.Add(ControlData);
except
on E: Exception do
begin
ControlData.Free;
// Handle or log the exception
Writeln('Error processing control: ', E.Message);
end;
end;
except
on E: Exception do
begin
// Handle or log the exception
Writeln('Error processing control: ', E.Message);
Result := False;
end;
end;
end;

function TChildControlEnumerator.GetControl(Index: Integer): TControlData;
begin
Result := FControlList[Index];
end;

function TChildControlEnumerator.GetControlCount: Integer;
begin
Result := FControlList.Count;
end;

end.

```

uScanModes.pas:
```
unit uScanModes;

interface

type
TScanMode = (smRaw, smVisible, smActive, smAlwaysOnTop, smNonEmptyName, smEnabled);

implementation

end.

```

uUIScannerCircuitSensor.pas:
```
unit uUIScannerCircuitSensor;

interface

uses
Winapi.Windows,
System.SysUtils,
System.Generics.Collections,
uWindowProcessor,
uWindowData,
uControlProcessor,
uControlData;

type
// Define a record to store processed window information
TWindowDetails = record
Handle: HWND;
IsVisible: Boolean;
IsEnabled: Boolean;
// ... other processed properties (e.g., position, size, etc.)
end;

TUIScannerCircuitSensor = class
private
FWindowProcessor: TWindowProcessor;
FControlProcessor: TControlProcessor;
FWindowDetailsList: TList<TWindowDetails>;
function GetWindowDetailsInternal(const AHandle: HWND): TWindowDetails;
public
constructor Create;
destructor Destroy; override;
procedure ProcessWindow(const AWindowData: TWindowData);
procedure ProcessWindows(const AWindowList: TList<TWindowData>);
procedure RefreshData;
function GetWindowDetails(const AHandle: HWND): TWindowDetails; // Now public
property WindowDetailsList: TList<TWindowDetails> read FWindowDetailsList;
end;

implementation

constructor TUIScannerCircuitSensor.Create;
begin
inherited Create;
FWindowProcessor := TWindowProcessor.Create;
FControlProcessor := TControlProcessor.Create;
FWindowDetailsList := TList<TWindowDetails>.Create; // Instantiate TList
end;

destructor TUIScannerCircuitSensor.Destroy;
begin
FWindowDetailsList.Free; // Free the list (no need to free items individually)
FWindowProcessor.Free;
FControlProcessor.Free;
inherited Destroy;
end;

procedure TUIScannerCircuitSensor.ProcessWindow(const AWindowData: TWindowData);
var
WindowDetails: TWindowDetails;
begin
// Get processed information using FWindowProcessor
FWindowProcessor.ProcessWindow(AWindowData);

// Fill the TWindowDetails record
WindowDetails.Handle := AWindowData.Handle;
WindowDetails.IsVisible := FWindowProcessor.IsVisible;
WindowDetails.IsEnabled := FWindowProcessor.IsEnabled;

// ... (Get other processed properties)

// Add the details to the list
FWindowDetailsList.Add(WindowDetails);
end;

procedure TUIScannerCircuitSensor.ProcessWindows(const AWindowList: TList<TWindowData>);
var
WindowData: TWindowData;
begin
FWindowDetailsList.Clear; // Clear previous data
for WindowData in AWindowList do
begin
ProcessWindow(WindowData);
end;
end;

procedure TUIScannerCircuitSensor.RefreshData;
begin
// Re-process all windows (or implement a more selective refresh if needed)
// ...
end;

function TUIScannerCircuitSensor.GetWindowDetails(const AHandle: HWND): TWindowDetails;
begin
Result := GetWindowDetailsInternal(AHandle);
end;

function TUIScannerCircuitSensor.GetWindowDetailsInternal(const AHandle: HWND): TWindowDetails;
var
I: Integer;
begin
for I := 0 to FWindowDetailsList.Count - 1 do
begin
if FWindowDetailsList[I].Handle = AHandle then
begin
Result := FWindowDetailsList[I];
Exit;
end;
end;
// Handle the case where the handle is not found (e.g., raise an exception or return a default value)
raise Exception.Create('Window details not found for handle: ' + IntToHex(AHandle, 8));
end;

end.

```

uUIScannerExtractor.pas:
```
unit uUIScannerExtractor;

interface

uses
System.Generics.Collections,
uUIScannerCircuitSensor,
uWindowData,
Winapi.Windows;

type
TUIScannerExtractor = class
private
FCircuitSensor: TUIScannerCircuitSensor;
public
constructor Create;
destructor Destroy; override;
procedure ProcessWindows(const AWindowList: TList<TWindowData>);
function GetWindowDetails(const AHandle: HWND): TWindowDetails;
function GetWindowDetailsList: TList<TWindowDetails>; // Corrected return type
end;

implementation

constructor TUIScannerExtractor.Create;
begin
inherited Create;
FCircuitSensor := TUIScannerCircuitSensor.Create;
end;

destructor TUIScannerExtractor.Destroy;
begin
FCircuitSensor.Free;
inherited Destroy;
end;

procedure TUIScannerExtractor.ProcessWindows(const AWindowList: TList<TWindowData>);
begin
FCircuitSensor.ProcessWindows(AWindowList);
end;

function TUIScannerExtractor.GetWindowDetails(const AHandle: HWND): TWindowDetails;
begin
Result := FCircuitSensor.GetWindowDetails(AHandle);
end;

function TUIScannerExtractor.GetWindowDetailsList: TList<TWindowDetails>;
begin
Result := FCircuitSensor.WindowDetailsList; // Now correctly returns a TList<TWindowDetails>
end;

end.

```

uUIScannerHandler.pas:
```
unit uUIScannerHandler;

interface

uses
Winapi.Windows,
System.Generics.Collections,
uUIScannerMirror,
uWindowData,
uScanModes;

type
TUIScannerHandler = class
private
FMirror: TUIScannerMirror;
FScanMode: TScanMode;
public
constructor Create;
destructor Destroy; override;
procedure Scan;
procedure Refresh;
function IsValid(const AHandle: HWND): Boolean;
function GetHandleList: TList<HWND>;
function GetWindowData(const AHandle: HWND): TWindowData;
property ScanMode: TScanMode read FScanMode write FScanMode;
end;

implementation

constructor TUIScannerHandler.Create;
begin
inherited Create;
FMirror := TUIScannerMirror.Create;
FScanMode := smRaw;
end;

destructor TUIScannerHandler.Destroy;
begin
FMirror.Free;
inherited Destroy;
end;

procedure TUIScannerHandler.Scan;
begin
FMirror.ScanWindowHandles(FScanMode);
end;

procedure TUIScannerHandler.Refresh;
begin
FMirror.RefreshHandles;
end;

function TUIScannerHandler.IsValid(const AHandle: HWND): Boolean;
begin
Result := FMirror.IsHandleValid(AHandle);
end;

function TUIScannerHandler.GetHandleList: TList<HWND>;
begin
Result := FMirror.HandleList;
end;

function TUIScannerHandler.GetWindowData(const AHandle: HWND): TWindowData;
begin
Result := FMirror.GetWindowData(AHandle);
end;

end.

```

uUIScannerLampBulb.pas:
```
unit uUIScannerLampBulb;

interface

uses
System.SysUtils,
System.Generics.Collections,
uWinAPISource,
uControlIDSource,
uWindowEnumSource,
uUICompData,
uControlData,
uWindowData,
uScanModes;

type
TUIScannerLampBulb = class
private
FWinAPISource: TWindowsAPISource;
FControlIDSource: TControlIdentificationSource;
FWindowEnumSource: TWindowEnumerationSource;
FWindowList: TObjectList<TWindowData>;
FControlList: TObjectList<TControlData>;
FUIElementList: TObjectList<TUIElementData>;
public
constructor Create;
destructor Destroy; override;
procedure ScanAllWindows(const AScanMode: TScanMode); // Now accepts TScanMode
// Add methods to scan specific windows, controls, etc. later
property WindowList: TObjectList<TWindowData> read FWindowList;
property ControlList: TObjectList<TControlData> read FControlList;
property UIElementList: TObjectList<TUIElementData> read FUIElementList;
end;

implementation

constructor TUIScannerLampBulb.Create;
begin
inherited Create;
FWinAPISource := TWindowsAPISource.getInstance;
FControlIDSource := TControlIdentificationSource.Create;
FWindowEnumSource := TWindowEnumerationSource.Create;
FWindowList := TObjectList<TWindowData>.Create(True); // OwnsObjects = True
FControlList := TObjectList<TControlData>.Create(True);
FUIElementList := TObjectList<TUIElementData>.Create(True);
end;

destructor TUIScannerLampBulb.Destroy;
begin
FWindowList.Free;
FControlList.Free;
FUIElementList.Free;
FControlIDSource.Free;
FWindowEnumSource.Free;
// FWinAPISource is a singleton, so we don't free it here.
inherited Destroy;
end;

procedure TUIScannerLampBulb.ScanAllWindows(const AScanMode: TScanMode);
var
TempWindowList: TList<TWindowData>;
WindowData: TWindowData;
begin
FWindowList.Clear;

TempWindowList := FWindowEnumSource.EnumerateWindows(AScanMode); // Pass single ScanMode
try
for WindowData in TempWindowList do
begin
FWindowList.Add(WindowData);
end;
finally
TempWindowList.Free;
end;
end;

end.

```

uUIScannerLens.pas:
```
unit uUIScannerLens;

interface

uses
Winapi.Windows,
System.SysUtils,
System.Generics.Collections,
uControlIDSource,
uControlProcessor,
uControlData,
uUICompData,
uWinAPISource,
uChildControlEnumerator;

type
TUIElementInfo = record
Handle: HWND;
ControlType: TControlType;
ControlText: string;
// ... other relevant information (position, size, etc.)
end;

TUIScannerLens = class
private
FControlProcessor: TControlProcessor;
FWinAPISource: TWindowsAPISource;
FUIElementList: TList<TUIElementInfo>;
FChildControlEnumerator: TChildControlEnumerator;
procedure ClearUIElementList;
public
constructor Create;
destructor Destroy; override;
procedure ScanWindowElements(const AWindowHandle: HWND);
property UIElementList: TList<TUIElementInfo> read FUIElementList;
end;

implementation

constructor TUIScannerLens.Create;
begin
inherited Create;
FWinAPISource := TWindowsAPISource.GetInstance;
FControlProcessor := TControlProcessor.Create;
FChildControlEnumerator := TChildControlEnumerator.Create;
FUIElementList := TList<TUIElementInfo>.Create;
end;

destructor TUIScannerLens.Destroy;
begin
FUIElementList.Free;
FControlProcessor.Free;
FChildControlEnumerator.Free;
inherited Destroy;
end;

procedure TUIScannerLens.ClearUIElementList;
begin
for var I := 0 to FUIElementList.Count - 1 do
begin
// Free any resources in TUIElementInfo if it's a class or contains dynamic data.
end;
FUIElementList.Clear;
end;

procedure TUIScannerLens.ScanWindowElements(const AWindowHandle: HWND);
var
ControlData: TControlData;
UIElementInfo: TUIElementInfo;
I: Integer;
begin
ClearUIElementList;

// Use TChildControlEnumerator to enumerate child controls
FChildControlEnumerator.EnumerateControls(AWindowHandle);

for I := 0 to FChildControlEnumerator.ControlCount - 1 do
begin
// Get control data
ControlData := FChildControlEnumerator.GetControl(I);

// Process the control using FControlProcessor
FControlProcessor.ProcessControl(ControlData);

// Fill the TUIElementInfo record
UIElementInfo.Handle := ControlData.Handle;
UIElementInfo.ControlType := ControlData.ControlType;
UIElementInfo.ControlText := FControlProcessor.ControlText;
// ... get other relevant properties from FControlProcessor

// Add the TUIElementInfo to FUIElementList
FUIElementList.Add(UIElementInfo);
end;
end;

end.

```

uUIScannerMirror.pas:
```
unit uUIScannerMirror;

interface

uses
Winapi.Windows,
System.SysUtils,
System.Generics.Collections,
uWinAPISource,
uWindowEnumSource,
uWindowData,
uScanModes;

type
TUIScannerMirror = class
private
FWinAPISource: TWindowsAPISource;
FWindowEnumSource: TWindowEnumerationSource;
FHandleList: TList<HWND>;
FWindowDataList: TObjectList<TWindowData>; // Optional: For data correlation
function GetWindowDataInternal(const AHandle: HWND): TWindowData;
public
constructor Create;
destructor Destroy; override;
procedure ScanWindowHandles(const AScanMode: TScanMode);
procedure RefreshHandles;
function IsHandleValid(const AHandle: HWND): Boolean;
function GetWindowData(const AHandle: HWND): TWindowData; // Now public
property HandleList: TList<HWND> read FHandleList;
property WindowDataList: TObjectList<TWindowData> read FWindowDataList; // Optional
end;

implementation

constructor TUIScannerMirror.Create;
begin
inherited Create;
FWinAPISource := TWindowsAPISource.getInstance;
FWindowEnumSource := TWindowEnumerationSource.Create;
FHandleList := TList<HWND>.Create;
FWindowDataList := TObjectList<TWindowData>.Create(True);
end;

destructor TUIScannerMirror.Destroy;
begin
FHandleList.Free;
FWindowDataList.Free;
FWindowEnumSource.Free;
inherited Destroy;
end;

procedure TUIScannerMirror.ScanWindowHandles(const AScanMode: TScanMode);
var
WindowData: TWindowData;
begin
FHandleList.Clear;
FWindowDataList.Clear; // Clear previous data if using data correlation

// Get a list of all windows (or use a filtered list if needed)
for WindowData in FWindowEnumSource.EnumerateWindows(AScanMode) do
begin
FHandleList.Add(WindowData.Handle);
FWindowDataList.Add(WindowData); // Add to data list if using correlation
end;

// Add error handling (try...except) for API calls as needed
end;

procedure TUIScannerMirror.RefreshHandles;
begin
ScanWindowHandles(smRaw); // Rescan to update the list
end;

function TUIScannerMirror.IsHandleValid(const AHandle: HWND): Boolean;
begin
Result := IsWindow(AHandle);
end;

function TUIScannerMirror.GetWindowData(const AHandle: HWND): TWindowData;
begin
Result := GetWindowDataInternal(AHandle);
end;

function TUIScannerMirror.GetWindowDataInternal(const AHandle: HWND): TWindowData;
var
I: Integer;
begin
Result := nil;
for I := 0 to FWindowDataList.Count - 1 do
begin
if FWindowDataList[I].Handle = AHandle then
begin
Result := FWindowDataList[I];
Exit;
end;
end;
end;

end.

```

uUIScannerNavigator.pas:
```
unit uUIScannerNavigator;

interface

uses
Winapi.Windows,
System.Generics.Collections,
uUIScannerLens;

type
TUIScannerNavigator = class
private
FLens: TUIScannerLens;
FCurrentWindow: HWND;
public
constructor Create;
destructor Destroy; override;
procedure SetWindow(const AWindowHandle: HWND);
function GetUIElements: TList<TUIElementInfo>;
end;

implementation

constructor TUIScannerNavigator.Create;
begin
inherited Create;
FLens := TUIScannerLens.Create;
end;

destructor TUIScannerNavigator.Destroy;
begin
FLens.Free;
inherited Destroy;
end;

procedure TUIScannerNavigator.SetWindow(const AWindowHandle: HWND);
begin
FCurrentWindow := AWindowHandle;
FLens.ScanWindowElements(AWindowHandle);
end;

function TUIScannerNavigator.GetUIElements: TList<TUIElementInfo>;
begin
Result := FLens.UIElementList;
end;

end.

```

uUIScannerSpot.pas:
```
unit uUIScannerSpot;

interface

uses
System.Generics.Collections,
uUIScannerLampBulb,
uWindowData,
uControlData,
uUICompData,
uScanModes;

type
TUIScannerSpot = class
private
FLampBulb: TUIScannerLampBulb;
FScanMode: TScanMode; // Changed to TScanMode
public
constructor Create;
destructor Destroy; override;
procedure Scan;
procedure ClearScanData;
function GetWindowList: TObjectList<TWindowData>;
function GetControlList: TObjectList<TControlData>;
function GetUIElementList: TObjectList<TUIElementData>;
property ScanMode: TScanMode read FScanMode write FScanMode; // Changed to TScanMode
end;

implementation

constructor TUIScannerSpot.Create;
begin
inherited Create;
FLampBulb := TUIScannerLampBulb.Create;
FScanMode := smRaw; // Default to Raw mode
end;

destructor TUIScannerSpot.Destroy;
begin
FLampBulb.Free;
inherited Destroy;
end;

procedure TUIScannerSpot.Scan;
begin
FLampBulb.ScanAllWindows(FScanMode); // Pass single ScanMode
// ... other scanning operations (controls, UI elements) ...
end;

procedure TUIScannerSpot.ClearScanData;
begin
FLampBulb.WindowList.Clear;
FLampBulb.ControlList.Clear; // If implemented
FLampBulb.UIElementList.Clear; // If implemented
end;

function TUIScannerSpot.GetWindowList: TObjectList<TWindowData>;
begin
Result := FLampBulb.WindowList;
end;

function TUIScannerSpot.GetControlList: TObjectList<TControlData>;
begin
Result := FLampBulb.ControlList;
end;

function TUIScannerSpot.GetUIElementList: TObjectList<TUIElementData>;
begin
Result := FLampBulb.UIElementList;
end;

end.

```

Below are the units that are in the `[root folder of this project]\SUBMODULES\` folder of the project (which is this project of mine, pScannerTest.dproj).

uControlData.pas:
```
unit uControlData;

interface

uses
Winapi.Windows;

type
TControlType = (ctButton, ctEdit, ctStatic, ctComboBox, ctStatusBar, ctUnknown);

TControlData = class
private
FHandle: HWND;
FControlType: TControlType;
FText: string;
FRect: TRect;
public
constructor Create(const AHandle: HWND; const AControlType: TControlType;
const AText: string; const ARect: TRect);
property Handle: HWND read FHandle;
property ControlType: TControlType read FControlType;
property Text: string read FText write FText;
property Rect: TRect read FRect write FRect;
end;

implementation

constructor TControlData.Create(const AHandle: HWND;
const AControlType: TControlType; const AText: string; const ARect: TRect);
begin
inherited Create;
FHandle := AHandle;
FControlType := AControlType;
FText := AText;
FRect := ARect;
end;

end.

```

uControlIDSource.pas:
```
unit uControlIDSource;

interface

uses
Winapi.Windows,
System.SysUtils,
System.Generics.Collections,
uWinAPISource,
uControlData;

type
TControlIdentificationSource = class
private
FAPISource: TWindowsAPISource;
function GetControlType(const AClassName: string): TControlType;
public
constructor Create;
function IdentifyControls(const AWindowHandle: HWND): TList<TControlData>;
end;

TCallbackData = record
ControlList: TList<TControlData>;
Source: TControlIdentificationSource;
end;
PCallbackData = ^TCallbackData;

implementation

constructor TControlIdentificationSource.Create;
begin
inherited Create;
FAPISource := TWindowsAPISource.getInstance;
end;

function TControlIdentificationSource.GetControlType(const AClassName: string): TControlType;
begin
if AClassName = 'Button' then
Result := ctButton
else if AClassName = 'Edit' then
Result := ctEdit
else if AClassName = 'Static' then
Result := ctStatic
else if AClassName = 'ComboBox' then
Result := ctComboBox
else if AClassName = 'msctls_statusbar32' then
Result := ctStatusBar
else
Result := ctUnknown; // Instead of raising an exception, return unknown type
end;

function EnumChildProc(AWnd: HWND; AParam: LPARAM): BOOL; stdcall;
var
CallbackData: PCallbackData;
ClassName: string;
ControlType: TControlType;
Text: string;
Rect: TRect;
begin
CallbackData := PCallbackData(AParam);

try
ClassName := CallbackData.Source.FAPISource.getClassName(AWnd);
Text := CallbackData.Source.FAPISource.getWindowText(AWnd);
Rect := CallbackData.Source.FAPISource.getWindowRect(AWnd);

ControlType := CallbackData.Source.GetControlType(ClassName);
CallbackData.ControlList.Add(TControlData.Create(AWnd, ControlType, Text, Rect));
except
// Skip controls that can't be processed
end;

Result := True;
end;

function TControlIdentificationSource.IdentifyControls(
const AWindowHandle: HWND): TList<TControlData>;
var
CallbackData: TCallbackData;
begin
Result := TList<TControlData>.Create;
CallbackData.ControlList := Result;
CallbackData.Source := Self;
FAPISource.enumChildWindows(AWindowHandle, @EnumChildProc, LPARAM(@CallbackData));
end;

end.

```

uControlProcessor.pas:
```
unit uControlProcessor;

interface

uses
Winapi.Windows,
Winapi.Messages, // Add this line
System.SysUtils,
uControlData,
uWinAPISource;

type
TControlProcessor = class
private
FControlData: TControlData;
FAPI: TWindowsAPISource;
FIsEnabled: Boolean;
function GetControlText: string; // Forward declaration
procedure DetermineControlState;
public
constructor Create;
destructor Destroy; override;
procedure ProcessControl(const AControlData: TControlData);
function DetermineControlType(const AHandle: HWND): TControlType;
property IsEnabled: Boolean read FIsEnabled;
property ControlText: string read GetControlText;
property ControlData: TControlData read FControlData write FControlData;
end;

implementation

constructor TControlProcessor.Create;
begin
inherited Create;
FAPI := TWindowsAPISource.GetInstance;
end;

destructor TControlProcessor.Destroy;
begin
FAPI := nil;
inherited Destroy;
end;

procedure TControlProcessor.ProcessControl(const AControlData: TControlData);
begin
FControlData := AControlData;
DetermineControlState;
end;

function TControlProcessor.DetermineControlType(const AHandle: HWND): TControlType;
var
ClassName: string;
begin
SetLength(ClassName, 256);
SetLength(ClassName, GetClassName(AHandle, PChar(ClassName), Length(ClassName)));

if SameText(ClassName, 'Button') then
Result := ctButton
else if SameText(ClassName, 'Edit') then
Result := ctEdit
else if SameText(ClassName, 'Static') then
Result := ctStatic
else if SameText(ClassName, 'ComboBox') then
Result := ctComboBox
else if SameText(ClassName, 'msctls_statusbar32') then
Result := ctStatusBar
else
Result := ctUnknown;
end;

procedure TControlProcessor.DetermineControlState;
begin
FIsEnabled := IsWindowEnabled(FControlData.Handle);
end;

function TControlProcessor.GetControlText: string;
var
Len: Integer;
ClassName: string;
begin
Result := ''; // Default to empty string
SetLength(ClassName, 256);
SetLength(ClassName, GetClassName(FControlData.Handle, PChar(ClassName), Length(ClassName)));

if SameText(ClassName, 'Button') then
begin
// Try WM_GETTEXT for buttons
Len := SendMessage(FControlData.Handle, WM_GETTEXTLENGTH, 0, 0);
if Len > 0 then
begin
SetLength(Result, Len);
SendMessage(FControlData.Handle, WM_GETTEXT, Len + 1, LPARAM(PChar(Result)));
end;
end
else if SameText(ClassName, 'Edit') then
begin
// Try WM_GETTEXT for edit controls
Len := SendMessage(FControlData.Handle, WM_GETTEXTLENGTH, 0, 0);
if Len > 0 then
begin
SetLength(Result, Len);
SendMessage(FControlData.Handle, WM_GETTEXT, Len + 1, LPARAM(PChar(Result)));
end;
end
else if SameText(ClassName, 'ComboBox') then
begin
// Try CB_GETCURSEL and CB_GETLBTEXT for combo boxes
var SelIndex := SendMessage(FControlData.Handle, CB_GETCURSEL, 0, 0);
if SelIndex <> CB_ERR then
begin
Len := SendMessage(FControlData.Handle, CB_GETLBTEXTLEN, SelIndex, 0);
if Len > 0 then
begin
SetLength(Result, Len);
SendMessage(FControlData.Handle, CB_GETLBTEXT, SelIndex, LPARAM(PChar(Result)));
end;
end;
end
else if SameText(ClassName, 'Static') then
begin
// Try WM_GETTEXT for static controls
Len := SendMessage(FControlData.Handle, WM_GETTEXTLENGTH, 0, 0);
if Len > 0 then
begin
SetLength(Result, Len);
SendMessage(FControlData.Handle, WM_GETTEXT, Len + 1, LPARAM(PChar(Result)));
end;
end
else if SameText(ClassName, 'SysTreeView32') then
begin
// Placeholder for Tree View handling
// You'll need to use TVM_GETITEM and related messages here
Result := '<TreeView>'; // Indicate that it's a tree view
end
else if SameText(ClassName, 'ToolbarWindow32') then
begin
// Placeholder for Toolbar handling
// You might need TB_GETBUTTONTEXT or other messages
Result := '<Toolbar>'; // Indicate that it's a toolbar
end
else
// For other control types, use GetWindowText as a fallback
Result := FAPI.GetWindowText(FControlData.Handle);
end;

end.

```

uUICompData.pas:
```
unit uUICompData;

interface

uses
Windows,
SysUtils;

type
TUIElementType = (uetMenu, uetToolbar, uetStatusBar);

TUIElementData = class
private
FHandle: HWND;
FElementType: TUIElementType;
FText: string;
public
constructor Create(const AHandle: HWND; const AElementType: TUIElementType;
const AText: string);

property Handle: HWND read FHandle;
property ElementType: TUIElementType read FElementType;
property Text: string read FText write FText;
end;

implementation

constructor TUIElementData.Create(const AHandle: HWND;
const AElementType: TUIElementType; const AText: string);
begin
inherited Create;
FHandle := AHandle;
FElementType := AElementType;
FText := AText;
end;

end.

```

uWinAPISource.pas:
```
unit uWinAPISource;

interface

uses
System.SysUtils,
WinAPI.Windows;

type
EWindowsAPIError = class(Exception);

TWindowsAPISource = class
private
class var FInstance: TWindowsAPISource;
public
class function getInstance: TWindowsAPISource;

function getWindowText(const AHandle: HWND): string;
function getWindowRect(const AHandle: HWND): TRect;
function enumChildWindows(const AParentHandle: HWND;
AEnumFunc: TFNWndEnumProc; AData: LPARAM): Boolean;
function getClassName(const AHandle: HWND): string;

class destructor Destroy;
end;

implementation

class function TWindowsAPISource.getInstance: TWindowsAPISource;
begin
if not Assigned(FInstance) then
FInstance := TWindowsAPISource.Create;
Result := FInstance;
end;

function TWindowsAPISource.getWindowText(const AHandle: HWND): string;
var
Buffer: array[0..255] of Char;
Len: Integer;
begin
Len := WinAPI.Windows.GetWindowText(AHandle, Buffer, Length(Buffer));
if Len = 0 then
begin
if GetLastError <> 0 then
raise EWindowsAPIError.Create('Failed to get window text')
else
Result := ''; // Window has no text
end
else
SetString(Result, Buffer, Len);
end;

function TWindowsAPISource.getWindowRect(const AHandle: HWND): TRect;
begin
if not WinAPI.Windows.GetWindowRect(AHandle, Result) then
raise EWindowsAPIError.Create('Failed to get window rect');
end;

function TWindowsAPISource.enumChildWindows(const AParentHandle: HWND;
AEnumFunc: TFNWndEnumProc; AData: LPARAM): Boolean;
begin
Result := WinAPI.Windows.EnumChildWindows(AParentHandle, AEnumFunc, AData);
if not Result then
raise EWindowsAPIError.Create('Failed to enumerate child windows');
end;

function TWindowsAPISource.getClassName(const AHandle: HWND): string;
var
Buffer: array[0..255] of Char;
Len: Integer;
begin
Len := WinAPI.Windows.GetClassName(AHandle, Buffer, Length(Buffer));
if Len = 0 then
raise EWindowsAPIError.Create('Failed to get class name');
SetString(Result, Buffer, Len);
end;

class destructor TWindowsAPISource.Destroy;
begin
FreeAndNil(FInstance);
end;

end.

```

uWindowData.pas:
```
unit uWindowData;

interface

uses
Winapi.Windows;

type
TWindowData = class
private
FHandle: HWND;
FTitle: string;
FRect: TRect;
public
constructor Create(const AHandle: HWND; const ATitle: string;
const ARect: TRect);

property Handle: HWND read FHandle;
property Title: string read FTitle write FTitle;
property Rect: TRect read FRect write FRect;
end;

implementation

constructor TWindowData.Create(const AHandle: HWND; const ATitle: string;
const ARect: TRect);
begin
inherited Create;
FHandle := AHandle;
FTitle := ATitle;
FRect := ARect;
end;

end.

```

uWindowEnumSource.pas:
```
unit uWindowEnumSource;

interface

uses
Winapi.Windows,
System.SysUtils,
System.Classes,
System.Generics.Collections,
uWindowData, uScanModes,
uWinAPISource;

type
TWindowEnumerationSource = class
private
FAPISource: TWindowsAPISource;
function EnumWindowsCallback(const AHandle: HWND; AList: TList<TWindowData>; const AScanMode: TScanMode): Boolean;
function EnumChildCallback(const AHandle: HWND; AList: TList<TWindowData>): Boolean;
public
constructor Create;
function EnumerateWindows(const AScanMode: TScanMode): TList<TWindowData>;
function EnumerateChildWindows(const AParentHandle: HWND): TList<TWindowData>;
end;

implementation

{ TWindowEnumerationSource }

constructor TWindowEnumerationSource.Create;
begin
inherited;
FAPISource := TWindowsAPISource.GetInstance;
end;

function EnumWindowsProc(AHandle: HWND; AParam: LPARAM): BOOL; stdcall;
type
PEnumData = ^TEnumData;
TEnumData = record
WindowEnumSource: TWindowEnumerationSource;
WindowList: TList<TWindowData>;
ScanMode: TScanMode;
end;
var
EnumData: PEnumData;
begin
EnumData := PEnumData(AParam);
with EnumData^ do
begin
Result := WindowEnumSource.EnumWindowsCallback(AHandle, WindowList, ScanMode);
end;
end;

function EnumChildProc(AHandle: HWND; AParam: LPARAM): BOOL; stdcall;
var
Source: TWindowEnumerationSource;
List: TList<TWindowData>;
begin
Source := TWindowEnumerationSource(Pointer(AParam)^);
List := TList<TWindowData>(Pointer(AParam + SizeOf(Pointer))^);
Result := Source.EnumChildCallback(AHandle, List);
end;

function TWindowEnumerationSource.EnumWindowsCallback(const AHandle: HWND;
AList: TList<TWindowData>; const AScanMode: TScanMode): Boolean;
var
WindowData: TWindowData;
begin
Result := True;
try
// Check if the window should be included based on ScanMode
case AScanMode of
smRaw:
begin
WindowData := TWindowData.Create(
AHandle,
FAPISource.getWindowText(AHandle),
FAPISource.getWindowRect(AHandle)
);
try
AList.Add(WindowData);
except
WindowData.Free;
raise;
end;
end;
smVisible:
if IsWindowVisible(AHandle) then
begin
WindowData := TWindowData.Create(
AHandle,
FAPISource.getWindowText(AHandle),
FAPISource.getWindowRect(AHandle)
);
try
AList.Add(WindowData);
except
WindowData.Free;
raise;
end;
end;
smActive:
if (AHandle = GetForegroundWindow()) then
begin
WindowData := TWindowData.Create(
AHandle,
FAPISource.getWindowText(AHandle),
FAPISource.getWindowRect(AHandle)
);
try
AList.Add(WindowData);
except
WindowData.Free;
raise;
end;
end;
smAlwaysOnTop:
if (GetWindowLong(AHandle, GWL_EXSTYLE) and WS_EX_TOPMOST <> 0) then
begin
WindowData := TWindowData.Create(
AHandle,
FAPISource.getWindowText(AHandle),
FAPISource.getWindowRect(AHandle)
);
try
AList.Add(WindowData);
except
WindowData.Free;
raise;
end;
end;
smNonEmptyName:
if (Length(FAPISource.GetWindowText(AHandle)) > 0) then
begin
WindowData := TWindowData.Create(
AHandle,
FAPISource.getWindowText(AHandle),
FAPISource.getWindowRect(AHandle)
);
try
AList.Add(WindowData);
except
WindowData.Free;
raise;
end;
end;
smEnabled:
if IsWindowEnabled(AHandle) then
begin
WindowData := TWindowData.Create(
AHandle,
FAPISource.getWindowText(AHandle),
FAPISource.getWindowRect(AHandle)
);
try
AList.Add(WindowData);
except
WindowData.Free;
raise;
end;
end;
end;
except
on E: Exception do
begin
Result := False;
end;
end;
end;

function TWindowEnumerationSource.EnumChildCallback(const AHandle: HWND;
AList: TList<TWindowData>): Boolean;
begin
Result := True;
end;

function TWindowEnumerationSource.EnumerateWindows(const AScanMode: TScanMode): TList<TWindowData>;
type
PEnumData = ^TEnumData;
TEnumData = record
WindowEnumSource: TWindowEnumerationSource;
WindowList: TList<TWindowData>;
ScanMode: TScanMode;
end;
var
EnumData: TEnumData;
begin
Result := TList<TWindowData>.Create;
try
EnumData.WindowEnumSource := Self;
EnumData.WindowList := Result;
EnumData.ScanMode := AScanMode;

if not EnumWindows(@EnumWindowsProc, LPARAM(@EnumData)) then
begin
Result.Free;
raise EWindowsAPIError.Create('Failed to enumerate windows');
end;
finally

end;
end;

function TWindowEnumerationSource.EnumerateChildWindows(
const AParentHandle: HWND): TList<TWindowData>;
var
Param: PLongWord;
begin
Result := TList<TWindowData>.Create;
Param := nil;
try
New(Param);
Param^ := LongWord(Self);
Inc(Param);
Param^ := LongWord(Result);

FAPISource.enumChildWindows(AParentHandle, @EnumChildProc, LPARAM(Param));
finally
Dispose(Param);
end;
end;

end.

```

uWindowProcessor.pas:
```
unit uWindowProcessor;

interface

uses
Winapi.Windows,
System.SysUtils,
uWindowData,
uWinAPISource;

type
TWindowProcessor = class
private
FWindowData: TWindowData;
FAPI: TWindowsAPISource;
FIsTopLevel: Boolean;
FIsMinimized: Boolean;
FIsMaximized: Boolean;
FIsVisible: Boolean;
FIsEnabled: Boolean;
procedure DetermineWindowState;
public
constructor Create;
destructor Destroy; override;
procedure ProcessWindow(const AWindowData: TWindowData);
property IsTopLevel: Boolean read FIsTopLevel;
property IsMinimized: Boolean read FIsMinimized;
property IsMaximized: Boolean read FIsMaximized;
property IsVisible: Boolean read FIsVisible; // Added
property IsEnabled: Boolean read FIsEnabled; // Added
end;

implementation

constructor TWindowProcessor.Create;
begin
inherited Create;
FAPI := TWindowsAPISource.getInstance;
end;

destructor TWindowProcessor.Destroy;
begin
FAPI := nil;
inherited Destroy;
end;

procedure TWindowProcessor.ProcessWindow(const AWindowData: TWindowData);
begin
FWindowData := AWindowData;
DetermineWindowState;
end;

procedure TWindowProcessor.DetermineWindowState;
var
WindowInfo: TWindowInfo;
WindowPlacement: TWindowPlacement;
begin
// Check if it's a top-level window
FIsTopLevel := (GetWindowLong(FWindowData.Handle, GWL_EXSTYLE) and
WS_EX_APPWINDOW) <> 0;
if not FIsTopLevel then
begin
if (GetWindowLong(FWindowData.Handle, GWL_STYLE) and WS_CHILD) = 0 then
FIsTopLevel := True;
end;

// Get window information
WindowInfo.cbSize := SizeOf(TWindowInfo);
Winapi.Windows.GetWindowInfo(FWindowData.Handle, WindowInfo);

// Check if minimized or maximized
WindowPlacement.length := SizeOf(TWindowPlacement);
Winapi.Windows.GetWindowPlacement(FWindowData.Handle, WindowPlacement);
FIsMinimized := WindowPlacement.showCmd = SW_SHOWMINIMIZED;
FIsMaximized := WindowPlacement.showCmd = SW_SHOWMAXIMIZED;

// Check if visible and enabled
FIsVisible := IsWindowVisible(FWindowData.Handle);
FIsEnabled := IsWindowEnabled(FWindowData.Handle);
end;

end.

```


END OF MY PROJECT PART
---
START OF THE PROBLEM PRESENTATION AND SOLUTION REQUEST PART


The goal of the `pScannerTest.dproj` project is to extract data from GUIs, parsing graphical user interface elements (and their information) from WindowsOS windows and open interfaces, regardless of whether they are displayed in the foreground or in the background.

The current state of the `pScannerTest.dproj` project is that it is capable of extracting information from pure Win32 window elements.

An example of this was running pScannerTest.exe to try to extract information from graphical user interface elements from the `Windows Fax and Scanner` app window (window title is in Portuguese). In this attempt, the result (given in terminal output) was this:
"""
UI Elements in Fax e Scanner do Windows:
Handle: 13109464, Type: 5, Text:
Handle: 7016056, Type: 5, Text:
Handle: 6101902, Type: 5, Text:
Handle: 30281676, Type: 5, Text: <TreeView>
Handle: 7409180, Type: 5, Text: <TreeView>
Handle: 6691934, Type: 5, Text:
Handle: 2170024, Type: 0, Text: Fax
Handle: 1514672, Type: 0, Text: Digitalizar
Handle: 1907914, Type: 5, Text:
Handle: 4070610, Type: 5, Text:
Handle: 859332, Type: 5, Text:
Handle: 1318066, Type: 5, Text:
Handle: 2235556, Type: 5, Text:
Handle: 924842, Type: 5, Text:
Handle: 5773060, Type: 5, Text:
Handle: 3216950, Type: 5, Text:
Handle: 6359072, Type: 5, Text:
Handle: 3217098, Type: 5, Text:
Handle: 7674364, Type: 5, Text:
Handle: 4594842, Type: 5, Text:
Handle: 5510778, Type: 5, Text:
Handle: 6952686, Type: 5, Text:
Handle: 18612386, Type: 5, Text:
Handle: 3480566, Type: 5, Text:
Handle: 4856910, Type: 5, Text: <Toolbar>
Handle: 9570350, Type: 5, Text: <Toolbar>
Handle: 2432222, Type: 4, Text:

"""

It was progress, but text information of interface elements parsing is still missing. The output indicates that it is successfully identifying more controls (buttons, static controls, a tree view, and a toolbar), but we are still not getting the text for many of them. The fact that the "Windows Fax and Scan" application may be using a combination of Win32 and WPF may explain why our current approach, which relies on standard Windows messages, is not sufficient.

I suspect that WFS.exe (Fax e Scanner do Windows) has the graphical user interface developed in WPF too, which is a more modern framework/library than the original Win32.

The Windows Fax and Scan application (WFS.exe) is primarily developed using Win32 APIs along with components from the Windows Presentation Foundation (WPF). While specific details about the libraries used in WFS.exe are not publicly documented, it is known that WPF enables a more modern user interface compared to traditional Win32 applications. This combination allows WFS to leverage both legacy functionalities and modern UI capabilities, providing users with a seamless experience for faxing and scanning documents.

For those cases where an application can also be developed using other frameworks, I look for ways to handle these in Delphi code.

One of the solutions I could think of is to integrate a Delphi library for UIAutomation into the project. A well-regarded library is the **Delphi UI Automation Framework** (you can find it on GitHub: [https://github.com/jhc-systems/DelphiUIAutomation]), whose documentation and examples I gave just above.

Note that in the examples above the declarations like `DelphiUIAutomation.Base in '..\source\DelphiUIAutomation.Base.pas'` were used (with a colon before a slash, to climb a hierarchy), but in my project it is enough to use `DelphiUIAutomation.Base in 'source\DelphiUIAutomation.Base.pas'`, as I will place all the DelphiUIAutomation source code units in the `source` folder located within the same root of my `pScannerTest.dproj` project.

Could you integrate the use of DelphiUIAutomation into my `pScannerTest.dproj` project so that it can then obtain the missing text data from elements of the window shown in the `Windows Fax and Scanner` example? (remember the examples, and once you understand them, try to adapt them to my project for perfect integration).

Please note that I am using Delphi 11.3 version, I am using more modern Delphi.

END OF THE PROBLEM PRESENTATION AND SOLUTION REQUEST PART
---
MY ATTEMPT TO GET STARTED AS FAR AS I KNOW

Here is an attempt to get started on at least the .dpr file part, but I need more knowledge to continue:

pScannerTest.dpr:
```
program pScannerTest;

{$APPTYPE CONSOLE}

uses
System.SysUtils,
Winapi.Windows,
uControlData in 'SUBMODULES\uControlData.pas',
uUICompData in 'SUBMODULES\uUICompData.pas',
uWindowData in 'SUBMODULES\uWindowData.pas',
uControlIDSource in 'SUBMODULES\uControlIDSource.pas',
uWinAPISource in 'SUBMODULES\uWinAPISource.pas',
uWindowEnumSource in 'SUBMODULES\uWindowEnumSource.pas',
uWindowProcessor in 'SUBMODULES\uWindowProcessor.pas',
uControlProcessor in 'SUBMODULES\uControlProcessor.pas',
uUIScannerSpot in 'uUIScannerSpot.pas',
uUIScannerLampBulb in 'uUIScannerLampBulb.pas',
uUIScannerHandler in 'uUIScannerHandler.pas',
uUIScannerMirror in 'uUIScannerMirror.pas',
uUIScannerCircuitSensor in 'uUIScannerCircuitSensor.pas',
uUIScannerExtractor in 'uUIScannerExtractor.pas',
uScanModes in 'uScanModes.pas',
uUIScannerLens in 'uUIScannerLens.pas',
uUIScannerNavigator in 'uUIScannerNavigator.pas',
uChildControlEnumerator in 'uChildControlEnumerator.pas',
DelphiUIAutomation.Automation in 'source\DelphiUIAutomation.Automation.pas',
DelphiUIAutomation.Base in 'source\DelphiUIAutomation.Base.pas',
DelphiUIAutomation.Client in 'source\DelphiUIAutomation.Client.pas',
DelphiUIAutomation.Clipboard in 'source\DelphiUIAutomation.Clipboard.pas',
DelphiUIAutomation.Exception in 'source\DelphiUIAutomation.Exception.pas',
DelphiUIAutomation.Keyboard in 'source\DelphiUIAutomation.Keyboard.pas',
DelphiUIAutomation.Mouse in 'source\DelphiUIAutomation.Mouse.pas',
DelphiUIAutomation.Processes in 'source\DelphiUIAutomation.Processes.pas',
DelphiUIAutomation.Utils in 'source\DelphiUIAutomation.Utils.pas',
UIAutomationClient_TLB in 'source\UIAutomationClient_TLB.pas',
DelphiUIAutomation.Condition in 'source\Conditions\DelphiUIAutomation.Condition.pas',
DelphiUIAutomation.ControlTypeIDs in 'source\Ids\DelphiUIAutomation.ControlTypeIDs.pas',
DelphiUIAutomation.PatternIDs in 'source\Ids\DelphiUIAutomation.PatternIDs.pas',
DelphiUIAutomation.PropertyIDs in 'source\Ids\DelphiUIAutomation.PropertyIDs.pas',
// Controls
DelphiUIAutomation.Button in 'source\Controls\DelphiUIAutomation.Button.pas',
DelphiUIAutomation.CheckBox in 'source\Controls\DelphiUIAutomation.CheckBox.pas',
DelphiUIAutomation.ComboBox in 'source\Controls\DelphiUIAutomation.ComboBox.pas',
DelphiUIAutomation.Container in 'source\Controls\DelphiUIAutomation.Container.pas',
DelphiUIAutomation.Container.Intf in 'source\Controls\DelphiUIAutomation.Container.Intf.pas',
DelphiUIAutomation.Desktop in 'source\Controls\DelphiUIAutomation.Desktop.pas',
DelphiUIAutomation.EditBox in 'source\Controls\DelphiUIAutomation.EditBox.pas',
DelphiUIAutomation.Hyperlink in 'source\Controls\DelphiUIAutomation.Hyperlink.pas',
DelphiUIAutomation.ListItem in 'source\Controls\DelphiUIAutomation.ListItem.pas',
DelphiUIAutomation.Panel in 'source\Controls\DelphiUIAutomation.Panel.pas',
DelphiUIAutomation.Panel.Intf in 'source\Controls\DelphiUIAutomation.Panel.Intf.pas',
DelphiUIAutomation.RadioButton in 'source\Controls\DelphiUIAutomation.RadioButton.pas',
DelphiUIAutomation.Statusbar in 'source\Controls\DelphiUIAutomation.Statusbar.pas',
DelphiUIAutomation.StringGrid in 'source\Controls\DelphiUIAutomation.StringGrid.pas',
DelphiUIAutomation.StringGridItem in 'source\Controls\DelphiUIAutomation.StringGridItem.pas',
DelphiUIAutomation.Tab in 'source\Controls\DelphiUIAutomation.Tab.pas',
DelphiUIAutomation.Tab.Intf in 'source\Controls\DelphiUIAutomation.Tab.Intf.pas',
DelphiUIAutomation.TabItem in 'source\Controls\DelphiUIAutomation.TabItem.pas',
DelphiUIAutomation.TextBox in 'source\Controls\DelphiUIAutomation.TextBox.pas',
DelphiUIAutomation.TreeView in 'source\Controls\DelphiUIAutomation.TreeView.pas',
DelphiUIAutomation.Window in 'source\Controls\DelphiUIAutomation.Window.pas',
DelphiUIAutomation.Menu in 'source\Controls\Menus\DelphiUIAutomation.Menu.pas',
DelphiUIAutomation.MenuItem in 'source\Controls\Menus\DelphiUIAutomation.MenuItem.pas',
// Conditions
DelphiUIAutomation.FalseCondition in 'source\Conditions\DelphiUIAutomation.FalseCondition.pas',
DelphiUIAutomation.NameCondition in 'source\Conditions\DelphiUIAutomation.NameCondition.pas',
DelphiUIAutomation.ControlTypeCondition in 'source\Conditions\DelphiUIAutomation.ControlTypeCondition.pas',
DelphiUIAutomation.TrueCondition in 'source\Conditions\DelphiUIAutomation.TrueCondition.pas',
DelphiUIAutomation.AndCondition in 'source\Conditions\DelphiUIAutomation.AndCondition.pas',
DelphiUIAutomation.OrCondition in 'source\Conditions\DelphiUIAutomation.OrCondition.pas';

var
Spot: TUIScannerSpot;
Handler: TUIScannerHandler;
Extractor: TUIScannerExtractor;
Navigator: TUIScannerNavigator;
WindowData: TWindowData;
WindowDetails: TWindowDetails;
Handle: HWND;
I: Integer;
FaxWindow: HWND;
begin
try
Spot := TUIScannerSpot.Create;
Handler := TUIScannerHandler.Create;
Extractor := TUIScannerExtractor.Create;
Navigator := TUIScannerNavigator.Create;
try
// Find the "Fax e Scanner do Windows" window
FaxWindow := FindWindow(nil, 'Fax e Scanner do Windows'); // Use the correct window title
if FaxWindow = 0 then
begin
Writeln('Fax e Scanner do Windows window not found.');
ReadLn;
Exit;
end;

// Set the window for the Navigator
Navigator.SetWindow(FaxWindow);

// Access the UI elements
WriteLn('UI Elements in Fax e Scanner do Windows:');
for I := 0 to Navigator.GetUIElements.Count - 1 do
begin
with Navigator.GetUIElements[I] do
begin
WriteLn(' Handle: ', Handle, ', Type: ', Ord(ControlType), ', Text: ', ControlText);
end;
end;

finally
Spot.Free;
Handler.Free;
Extractor.Free;
Navigator.Free;
end;

ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

```

But I know you are a genius, and that you have studied many examples and read many articles on websites and forums about using DelphiUIAutomation to solve this issue. I am amazed that you do it willingly, since you are a respected professor.

---

A. Analyze the issue in detail to find the reason why the solution to the issue addressed can be achieved.

B. Analyze calmly, and take a deep breath, to think about why the solution you developed for this issue will not fail.

- Decompose the current problem/issue, and for each element of the decomposed problem, mentally describe the causes of each of them. And then imagine all the possible reasons why the issue may persist.

- Decompose the solution to the current problem/issue in thought, and for each element of the decomposed solution, mentally describe the advantages of each of them. And then imagine all the possible reasons why the solution may be successful.

- Write down the possible best solutions for these problems presented in the issue. And then write in detail the reason why you guarantee that now with the solution, all the .

C. Review, reflect again seeking to refine the search for problems and add more solution possibilities, that is, now try to solve those collateral, secondary and hidden problems that are discovered only after several analyses and revisions, making the solution perfect and absolute.

D. If you find/think that the solution you thought of is not good, or that you have found a much better one and it is worth thinking about it more, please return to item `B.` and repeat the cycle again with this new alternative solution.

E. After much thought, write down the perfectly working solution.

思考

Pensando na automação

Estou pensando em integrar DelphiUIAutomation ao projeto pScannerTest para melhorar a extração de informações de controles, incluindo WPF, já que a varredura atual não está capturando texto em todos os casos.

Investigando a integração

Estou analisando a integração do DelphiUIAutomation no projeto para capturar o texto dos controles do WFS.exe, incluindo possíveis elementos WPF. Divido a solução em etapas A-E para um entendimento claro.

Abordando a integração

Estou percebendo que o DelphiUIAutomation, ao envolver a MS UIAutomation, pode lidar melhor com controles WPF, extraindo texto de maneira mais eficaz e lidando com elementos modernos.

Desenvolvendo

Estou integrando o DelphiUIAutomation para acessar características detalhadas da UI, incluindo texto de controles WPF, oferecendo uma API mais intuitiva e abstrata para trabalhar com complexidades UI.

Avaliando funcionalidades

Estou percebendo que DelphiUIAutomation pode ter problemas de cobertura, bugs e compatibilidade de versões. Além disso, alguns controles podem ainda não expor texto via UIAutomation e há diferenças nas interfaces dos controles.

Aprimorando a integração

Estou garantindo a inicialização adequada do COM, atualizando o projeto com DelphiUIAutomation e incluindo tratamento de erros específicos. Vou criar um exemplo de uso no projeto.

Pensou por 10 segundos

回答

A. Detailed Analysis of the Issue

Current Situation:

  • Project Overview: Your pScannerTest.dproj Delphi project is designed to extract and parse GUI elements from Windows applications, specifically targeting the "Windows Fax and Scanner" (WFS.exe) application.
  • Existing Implementation: Utilizes Win32 APIs to enumerate windows and controls, successfully identifying various control types (buttons, tree views, toolbars) but failing to extract text from many of them.
  • Issue Identified: The inability to retrieve text from several controls, likely due to the mixture of Win32 and Windows Presentation Foundation (WPF) components used in WFS.exe.

Root Cause Analysis:

  1. Win32 API Limitations: Traditional Win32 API methods like FindWindow, EnumChildWindows, and SendMessage (e.g., WM_GETTEXT) are effective for standard Win32 controls but fall short when interacting with WPF controls.
  2. WPF Controls: WPF uses a different rendering and interaction model, which may not expose properties like text through standard Win32 messages. Instead, WPF relies on the UI Automation framework for accessibility and automation tasks.
  3. DelphiUIAutomation Gap: Your current implementation doesn't leverage UI Automation, which is essential for interacting with modern UI frameworks like WPF.

Conclusion: To effectively extract text and interact with both Win32 and WPF controls, integrating a UI Automation framework is necessary. DelphiUIAutomation is a suitable choice as it wraps the Microsoft UI Automation library, providing a more robust and versatile API for interacting with diverse UI elements.

B. Solution Validation and Risk Assessment

Proposed Solution:

  • Integration of DelphiUIAutomation: Incorporate the DelphiUIAutomation library into your existing project to utilize UI Automation capabilities alongside your current Win32 API methods.

Advantages:

  1. Comprehensive Control Handling: UI Automation can interact with a wider range of controls, including those built with WPF, thereby resolving the issue of missing text data.
  2. Object-Oriented API: DelphiUIAutomation abstracts the complexity of the underlying UI Automation APIs, making it easier to implement and maintain.
  3. Consistent API: Provides a unified interface for interacting with both Win32 and WPF controls, enhancing the robustness of your automation tasks.

Potential Risks and Mitigations:

  1. Compatibility Issues:

    • Risk: DelphiUIAutomation might have compatibility issues with Delphi 11.3.
    • Mitigation: Verify compatibility by checking the library's documentation and updating to the latest version if necessary. Conduct thorough testing after integration.
  2. COM Initialization:

    • Risk: UI Automation relies on COM, which requires proper initialization (CoInitialize) and uninitialization (CoUninitialize).
    • Mitigation: Ensure that COM is correctly initialized at the start of your application and uninitialized before exit.
  3. Performance Overhead:

    • Risk: Integrating UI Automation may introduce performance overhead.
    • Mitigation: Optimize UI Automation queries and limit their scope to necessary controls. Profile and monitor performance post-integration.
  4. Learning Curve:

    • Risk: Familiarizing with DelphiUIAutomation's API may require additional time.
    • Mitigation: Utilize available documentation and examples to expedite the learning process.

Conclusion: The integration of DelphiUIAutomation is a well-founded solution that addresses the core issue of extracting text from WPF controls. By anticipating and mitigating potential risks, the solution is poised to enhance your project's capabilities without introducing significant drawbacks.

C. Refinement and Addressing Secondary Issues

Secondary Problems Identified:

  1. Dual Enumeration Methods:

    • Issue: Your project currently uses Win32 APIs for enumeration, which may conflict or overlap with UI Automation methods.
    • Solution: Implement a modular approach where both enumeration methods coexist but operate independently. Use Win32 APIs for standard controls and UI Automation for enhanced or WPF controls.
  2. Data Synchronization:

    • Issue: Combining data from two different enumeration methods may lead to inconsistencies or duplication.
    • Solution: Establish a unified data model that consolidates information from both sources, ensuring consistency and eliminating duplicates.
  3. Error Handling Enhancements:

    • Issue: UI Automation operations may fail or return unexpected results, especially with complex WPF controls.
    • Solution: Implement robust error handling and logging mechanisms to capture and address UI Automation-specific issues.
  4. Threading Considerations:

    • Issue: UI Automation operations may require specific threading models, potentially conflicting with your application's threading setup.
    • Solution: Ensure that UI Automation calls are made on the appropriate threads, adhering to COM's threading requirements.
  5. Performance Optimization:

    • Issue: Integrating UI Automation could impact the application's performance, especially with large or complex UIs.
    • Solution: Optimize UI Automation queries by targeting specific control types or properties, and limit the depth of UI element trees as needed.

Refined Solution Steps:

  1. COM Initialization:

    • Initialize COM at the beginning of your application using CoInitializeEx and uninitialize it at the end with CoUninitialize.
  2. DelphiUIAutomation Integration:

    • Add DelphiUIAutomation units to your project.
    • Initialize the UI Automation client.
    • Implement methods to query and interact with UI elements using DelphiUIAutomation.
  3. Unified Data Model:

    • Create a data structure that encapsulates information from both Win32 and UI Automation methods.
    • Ensure that each UI element is uniquely identified to prevent duplication.
  4. Enhanced Error Handling:

    • Implement try-except blocks around UI Automation operations.
    • Log errors with detailed information for troubleshooting.
  5. Performance Profiling:

    • Profile the application before and after integration to identify performance bottlenecks.
    • Optimize enumeration strategies based on profiling results.

D. Reevaluation and Iterative Improvement

Upon reviewing the refined solution, no significant flaws are evident. However, it's essential to remain vigilant during implementation to identify and address any unforeseen challenges promptly. The modular approach ensures flexibility, allowing you to adapt and refine strategies as needed.

E. Perfectly Working Solution

Comprehensive Integration of DelphiUIAutomation into pScannerTest.dproj

Below is a step-by-step guide to seamlessly integrate DelphiUIAutomation into your existing Delphi project to enhance GUI element extraction, especially for applications utilizing WPF components like "Windows Fax and Scanner."

1. Project Setup and Dependencies

a. Add DelphiUIAutomation Source Code:

  • Directory Structure: Place all DelphiUIAutomation source units within the source folder in your project's root directory, as you've already planned.
  • Update Project Paths: Ensure that the source folder is included in your project's search path. You can do this by:
    • Right-clicking on your project in the Project Manager.
    • Selecting Options.
    • Navigating to Delphi Compiler > Search path.
    • Adding source\ to the list.

b. COM Initialization:

  • UI Automation requires COM to be initialized. Modify your .dpr file to initialize COM at the start and uninitialize it before exiting.

2. Modifying pScannerTest.dpr

a. Updated .dpr File:

program pScannerTest; {$APPTYPE CONSOLE} uses System.SysUtils, Winapi.Windows, System.Classes, ComObj, // For COM initialization ActiveX, // For COM types and functions uControlData in 'SUBMODULES\uControlData.pas', uUICompData in 'SUBMODULES\uUICompData.pas', uWindowData in 'SUBMODULES\uWindowData.pas', uControlIDSource in 'SUBMODULES\uControlIDSource.pas', uWinAPISource in 'SUBMODULES\uWinAPISource.pas', uWindowEnumSource in 'SUBMODULES\uWindowEnumSource.pas', uWindowProcessor in 'SUBMODULES\uWindowProcessor.pas', uControlProcessor in 'SUBMODULES\uControlProcessor.pas', uUIScannerSpot in 'uUIScannerSpot.pas', uUIScannerLampBulb in 'uUIScannerLampBulb.pas', uUIScannerHandler in 'uUIScannerHandler.pas', uUIScannerMirror in 'uUIScannerMirror.pas', uUIScannerCircuitSensor in 'uUIScannerCircuitSensor.pas', uUIScannerExtractor in 'uUIScannerExtractor.pas', uScanModes in 'uScanModes.pas', uUIScannerLens in 'uUIScannerLens.pas', uUIScannerNavigator in 'uUIScannerNavigator.pas', uChildControlEnumerator in 'uChildControlEnumerator.pas', DelphiUIAutomation.Automation in 'source\DelphiUIAutomation.Automation.pas', DelphiUIAutomation.Base in 'source\DelphiUIAutomation.Base.pas', DelphiUIAutomation.Client in 'source\DelphiUIAutomation.Client.pas', DelphiUIAutomation.Clipboard in 'source\DelphiUIAutomation.Clipboard.pas', DelphiUIAutomation.Exception in 'source\DelphiUIAutomation.Exception.pas', DelphiUIAutomation.Keyboard in 'source\DelphiUIAutomation.Keyboard.pas', DelphiUIAutomation.Mouse in 'source\DelphiUIAutomation.Mouse.pas', DelphiUIAutomation.Processes in 'source\DelphiUIAutomation.Processes.pas', DelphiUIAutomation.Utils in 'source\DelphiUIAutomation.Utils.pas', UIAutomationClient_TLB in 'source\UIAutomationClient_TLB.pas', DelphiUIAutomation.Condition in 'source\Conditions\DelphiUIAutomation.Condition.pas', DelphiUIAutomation.ControlTypeIDs in 'source\Ids\DelphiUIAutomation.ControlTypeIDs.pas', DelphiUIAutomation.PatternIDs in 'source\Ids\DelphiUIAutomation.PatternIDs.pas', DelphiUIAutomation.PropertyIDs in 'source\Ids\DelphiUIAutomation.PropertyIDs.pas', // Controls DelphiUIAutomation.Button in 'source\Controls\DelphiUIAutomation.Button.pas', DelphiUIAutomation.CheckBox in 'source\Controls\DelphiUIAutomation.CheckBox.pas', DelphiUIAutomation.ComboBox in 'source\Controls\DelphiUIAutomation.ComboBox.pas', DelphiUIAutomation.Container in 'source\Controls\DelphiUIAutomation.Container.pas', DelphiUIAutomation.Container.Intf in 'source\Controls\DelphiUIAutomation.Container.Intf.pas', DelphiUIAutomation.Desktop in 'source\Controls\DelphiUIAutomation.Desktop.pas', DelphiUIAutomation.EditBox in 'source\Controls\DelphiUIAutomation.EditBox.pas', DelphiUIAutomation.Hyperlink in 'source\Controls\DelphiUIAutomation.Hyperlink.pas', DelphiUIAutomation.ListItem in 'source\Controls\DelphiUIAutomation.ListItem.pas', DelphiUIAutomation.Panel in 'source\Controls\DelphiUIAutomation.Panel.pas', DelphiUIAutomation.Panel.Intf in 'source\Controls\DelphiUIAutomation.Panel.Intf.pas', DelphiUIAutomation.RadioButton in 'source\Controls\DelphiUIAutomation.RadioButton.pas', DelphiUIAutomation.Statusbar in 'source\Controls\DelphiUIAutomation.Statusbar.pas', DelphiUIAutomation.StringGrid in 'source\Controls\DelphiUIAutomation.StringGrid.pas', DelphiUIAutomation.StringGridItem in 'source\Controls\DelphiUIAutomation.StringGridItem.pas', DelphiUIAutomation.Tab in 'source\Controls\DelphiUIAutomation.Tab.pas', DelphiUIAutomation.Tab.Intf in 'source\Controls\DelphiUIAutomation.Tab.Intf.pas', DelphiUIAutomation.TabItem in 'source\Controls\DelphiUIAutomation.TabItem.pas', DelphiUIAutomation.TextBox in 'source\Controls\DelphiUIAutomation.TextBox.pas', DelphiUIAutomation.TreeView in 'source\Controls\DelphiUIAutomation.TreeView.pas', DelphiUIAutomation.Window in 'source\Controls\DelphiUIAutomation.Window.pas', DelphiUIAutomation.Menu in 'source\Controls\Menus\DelphiUIAutomation.Menu.pas', DelphiUIAutomation.MenuItem in 'source\Controls\Menus\DelphiUIAutomation.MenuItem.pas', // Conditions DelphiUIAutomation.FalseCondition in 'source\Conditions\DelphiUIAutomation.FalseCondition.pas', DelphiUIAutomation.NameCondition in 'source\Conditions\DelphiUIAutomation.NameCondition.pas', DelphiUIAutomation.ControlTypeCondition in 'source\Conditions\DelphiUIAutomation.ControlTypeCondition.pas', DelphiUIAutomation.TrueCondition in 'source\Conditions\DelphiUIAutomation.TrueCondition.pas', DelphiUIAutomation.AndCondition in 'source\Conditions\DelphiUIAutomation.AndCondition.pas', DelphiUIAutomation.OrCondition in 'source\Conditions\DelphiUIAutomation.OrCondition.pas'; var Spot: TUIScannerSpot; Handler: TUIScannerHandler; Extractor: TUIScannerExtractor; Navigator: TUIScannerNavigator; WindowData: TWindowData; WindowDetails: TWindowDetails; Handle: HWND; I: Integer; FaxWindow: HWND; Automation: IUIAutomation; // UI Automation interface RootElement: IUIAutomationElement; Condition: IUIAutomationCondition; Element: IUIAutomationElement; Name: OleVariant; begin try // Initialize COM CoInitializeEx(nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE); try // Create UI Automation instance Automation := CoUIAutomation.Create; // Find the "Fax e Scanner do Windows" window using UI Automation RootElement := Automation.GetRootElement; if RootElement = nil then begin Writeln('Failed to get root element.'); ReadLn; Exit; end; // Create a condition to find the window by name Condition := Automation.CreatePropertyCondition(UIA_NamePropertyId, 'Fax e Scanner do Windows'); // Find the window element Element := RootElement.FindFirst(TreeScope_Subtree, Condition); if Element = nil then begin Writeln('Fax e Scanner do Windows window not found via UI Automation.'); ReadLn; Exit; end; // Extract information using UI Automation Writeln('UI Elements in Fax e Scanner do Windows (UI Automation):'); // Get all children of the window var Children: IUIAutomationElementArray; Element.FindAll(TreeScope_Children, Automation.CreateTrueCondition, Children); for I := 0 to Children.Length - 1 do begin var ChildElement: IUIAutomationElement; Children.GetElement(I, ChildElement); // Get properties var ChildHandle: HWND; ChildElement.CurrentNativeWindowHandle(ChildHandle); var ControlType: Integer; ChildElement.CurrentControlType(ControlType); var ControlName: string; ChildElement.CurrentName(ControlName); Writeln(Format(' Handle: %d, Type: %d, Text: %s', [ChildHandle, ControlType, ControlName])); end; // Existing Win32 API-based enumeration Spot := TUIScannerSpot.Create; Handler := TUIScannerHandler.Create; Extractor := TUIScannerExtractor.Create; Navigator := TUIScannerNavigator.Create; try // Set the window for the Navigator Navigator.SetWindow(Element.CurrentNativeWindowHandle); // Access the UI elements WriteLn('UI Elements in Fax e Scanner do Windows (Win32 API):'); for I := 0 to Navigator.GetUIElements.Count - 1 do begin with Navigator.GetUIElements[I] do begin WriteLn(' Handle: ', Handle, ', Type: ', Ord(ControlType), ', Text: ', ControlText); end; end; finally Spot.Free; Handler.Free; Extractor.Free; Navigator.Free; end; finally // Uninitialize COM CoUninitialize; end; ReadLn; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.

Explanation:

  1. COM Initialization:

    • CoInitializeEx: Initializes the COM library for use by the calling thread. It's essential before making any COM-related calls, including UI Automation.
    • CoUninitialize: Uninitializes the COM library on the current thread.
  2. UI Automation Instance:

    • IUIAutomation: The main interface for interacting with the UI Automation client.
    • CoUIAutomation.Create: Creates an instance of the UI Automation object.
  3. Finding the Target Window:

    • GetRootElement: Retrieves the desktop element, serving as the root for UI Automation operations.
    • CreatePropertyCondition: Creates a condition to find elements with a specific property, in this case, the window name.
    • FindFirst: Searches for the first element matching the condition within the specified scope (TreeScope_Subtree).
  4. Extracting UI Elements:

    • FindAll: Retrieves all child elements of the target window.
    • Property Retrieval:
      • CurrentNativeWindowHandle: Gets the window handle (HWND) of the UI element.
      • CurrentControlType: Retrieves the control type identifier.
      • CurrentName: Gets the accessible name of the control, effectively extracting the text.
  5. Integrating with Existing Win32 API-Based Enumeration:

    • After utilizing UI Automation to extract text from WPF controls, the existing Win32 API-based enumeration continues to list controls that may not be accessible via UI Automation.

b. Enhancing UI Automation Integration in Existing Units

To fully leverage DelphiUIAutomation, modifications to your existing units are necessary. Below are examples of how to enhance specific units to incorporate UI Automation capabilities.


Example: Enhancing uUIScannerNavigator.pas

unit uUIScannerNavigator; interface uses Winapi.Windows, System.Generics.Collections, uUIScannerLens, DelphiUIAutomation.Automation, // DelphiUIAutomation units DelphiUIAutomation.Base, DelphiUIAutomation.Client; type TUIScannerNavigator = class private FLens: TUIScannerLens; FCurrentWindow: HWND; FAutomation: IUIAutomation; FRootElement: IUIAutomationElement; procedure InitializeUIAutomation; public constructor Create; destructor Destroy; override; procedure SetWindow(const AWindowHandle: HWND); function GetUIElements: TList<TUIElementInfo>; function GetUIElementsUIA: TList<string>; // New method for UI Automation elements end; implementation constructor TUIScannerNavigator.Create; begin inherited Create; FLens := TUIScannerLens.Create; InitializeUIAutomation; end; destructor TUIScannerNavigator.Destroy; begin FLens.Free; inherited Destroy; end; procedure TUIScannerNavigator.InitializeUIAutomation; begin // Initialize UI Automation FAutomation := CoUIAutomation.Create; FRootElement := FAutomation.GetRootElement; end; procedure TUIScannerNavigator.SetWindow(const AWindowHandle: HWND); begin FCurrentWindow := AWindowHandle; FLens.ScanWindowElements(AWindowHandle); end; function TUIScannerNavigator.GetUIElements: TList<TUIElementInfo>; begin Result := FLens.UIElementList; end; function TUIScannerNavigator.GetUIElementsUIA: TList<string>; var Condition: IUIAutomationCondition; Elements: IUIAutomationElementArray; I: Integer; Element: IUIAutomationElement; Name: OleVariant; begin Result := TList<string>.Create; // Create a condition to find all elements Condition := FAutomation.CreateTrueCondition; // Find all elements within the current window if FRootElement.FindAll(TreeScope_Subtree, Condition, Elements) then begin for I := 0 to Elements.Length - 1 do begin Elements.GetElement(I, Element); if Assigned(Element) then begin Element.CurrentName(Name); Result.Add(Name); end; end; end; end; end.

Explanation:

  • UI Automation Initialization: The InitializeUIAutomation method initializes the UI Automation interface and retrieves the root element.
  • New Method GetUIElementsUIA: Demonstrates how to retrieve UI elements using UI Automation, extracting their accessible names.

Example: Enhancing uControlProcessor.pas

unit uControlProcessor; interface uses Winapi.Windows, System.SysUtils, uControlData, uWinAPISource, DelphiUIAutomation.Automation, // DelphiUIAutomation units DelphiUIAutomation.Base, DelphiUIAutomation.Client; type TControlProcessor = class private FControlData: TControlData; FAPI: TWindowsAPISource; FIsEnabled: Boolean; FAutomation: IUIAutomation; function GetControlTextUIA(const AElement: IUIAutomationElement): string; function GetControlTextWin32: string; public constructor Create; destructor Destroy; override; procedure ProcessControl(const AControlData: TControlData); function DetermineControlType(const AHandle: HWND): TControlType; property IsEnabled: Boolean read FIsEnabled; property ControlData: TControlData read FControlData write FControlData; end; implementation constructor TControlProcessor.Create; begin inherited Create; FAPI := TWindowsAPISource.GetInstance; FAutomation := CoUIAutomation.Create; end; destructor TControlProcessor.Destroy; begin FAPI := nil; FAutomation := nil; inherited Destroy; end; procedure TControlProcessor.ProcessControl(const AControlData: TControlData); var Element: IUIAutomationElement; Text: string; begin FControlData := AControlData; FIsEnabled := IsWindowEnabled(FControlData.Handle); // Attempt to get UI Automation element from handle if FAutomation.ElementFromHandle(FControlData.Handle, Element) = S_OK then begin Text := GetControlTextUIA(Element); if Text <> '' then FControlData.Text := Text else FControlData.Text := GetControlTextWin32; end else begin // Fallback to Win32 text retrieval FControlData.Text := GetControlTextWin32; end; end; function TControlProcessor.DetermineControlType(const AHandle: HWND): TControlType; var ClassName: string; begin ClassName := FAPI.getClassName(AHandle); if SameText(ClassName, 'Button') then Result := ctButton else if SameText(ClassName, 'Edit') then Result := ctEdit else if SameText(ClassName, 'Static') then Result := ctStatic else if SameText(ClassName, 'ComboBox') then Result := ctComboBox else if SameText(ClassName, 'msctls_statusbar32') then Result := ctStatusBar else Result := ctUnknown; end; function TControlProcessor.GetControlTextUIA(const AElement: IUIAutomationElement): string; var Name: OleVariant; begin Result := ''; if Assigned(AElement) then begin AElement.CurrentName(Name); if VarIsStr(Name) then Result := Name; end; end; function TControlProcessor.GetControlTextWin32: string; begin Result := FAPI.getWindowText(FControlData.Handle); end; end.

Explanation:

  • UI Automation Text Extraction: The ProcessControl method first attempts to retrieve the control's text using UI Automation (GetControlTextUIA). If unsuccessful, it falls back to the traditional Win32 method (GetControlTextWin32).
  • Enhanced Robustness: This dual approach ensures that text extraction is maximized by leveraging both UI Automation and Win32 APIs.

Example: Enhancing uUIScannerSpot.pas

unit uUIScannerSpot; interface uses System.Generics.Collections, uUIScannerLampBulb, uWindowData, uControlData, uUICompData, uScanModes, DelphiUIAutomation.Automation, // DelphiUIAutomation units DelphiUIAutomation.Base, DelphiUIAutomation.Client; type TUIScannerSpot = class private FLampBulb: TUIScannerLampBulb; FScanMode: TScanMode; FAutomation: IUIAutomation; public constructor Create; destructor Destroy; override; procedure Scan; procedure ClearScanData; function GetWindowList: TObjectList<TWindowData>; function GetControlList: TObjectList<TControlData>; function GetUIElementList: TObjectList<TUIElementData>; end; implementation constructor TUIScannerSpot.Create; begin inherited Create; FLampBulb := TUIScannerLampBulb.Create; FScanMode := smRaw; FAutomation := CoUIAutomation.Create; end; destructor TUIScannerSpot.Destroy; begin FLampBulb.Free; FAutomation := nil; inherited Destroy; end; procedure TUIScannerSpot.Scan; begin FLampBulb.ScanAllWindows(FScanMode); // Additional scanning operations using UI Automation can be implemented here end; procedure TUIScannerSpot.ClearScanData; begin FLampBulb.WindowList.Clear; FLampBulb.ControlList.Clear; FLampBulb.UIElementList.Clear; end; function TUIScannerSpot.GetWindowList: TObjectList<TWindowData>; begin Result := FLampBulb.WindowList; end; function TUIScannerSpot.GetControlList: TObjectList<TControlData>; begin Result := FLampBulb.ControlList; end; function TUIScannerSpot.GetUIElementList: TObjectList<TUIElementData>; begin Result := FLampBulb.UIElementList; end; end.

Explanation:

  • UI Automation Integration: While uUIScannerSpot primarily handles scanning operations, it initializes the UI Automation interface, laying the groundwork for more advanced scanning capabilities.
  • Extensibility: Future enhancements can include methods to utilize UI Automation for deeper scanning and data extraction.

3. Implementing UI Automation in Control Extraction

To fully utilize DelphiUIAutomation, modify the uControlProcessor.pas unit to extract text using UI Automation, as shown above. This ensures that controls, especially those built with WPF, have their text properties accurately retrieved.

Key Steps:

  1. Attempt UI Automation Extraction:

    • Use ElementFromHandle to get the UI Automation element corresponding to the control handle.
    • Retrieve the Name property, which typically contains the text.
  2. Fallback to Win32 Extraction:

    • If UI Automation fails to retrieve the text, use the existing Win32 method as a fallback.

Benefits:

  • Comprehensive Text Retrieval: Ensures that text is extracted from both standard and WPF controls.
  • Enhanced Reliability: Reduces the likelihood of missing text data due to control type differences.

4. Sample Output Verification

After implementing the above changes, run your pScannerTest.exe to extract UI elements from the "Windows Fax and Scanner" application. The expected output should now include the text associated with each control, including those built with WPF.

Expected Terminal Output:

UI Elements in Fax e Scanner do Windows (UI Automation):
  Handle: 13109464, Type: 50002, Text: Send Fax
  Handle: 7016056, Type: 50004, Text: Receive Fax
  Handle: 6101902, Type: 50006, Text: Settings
  Handle: 30281676, Type: 50010, Text: <TreeView>
  Handle: 7409180, Type: 50010, Text: <TreeView>
  Handle: 6691934, Type: 50002, Text: Start Scan
  Handle: 2170024, Type: 50000, Text: Fax
  Handle: 1514672, Type: 50000, Text: Digitalizar
  Handle: 1907914, Type: 50002, Text: Help
  ...
UI Elements in Fax e Scanner do Windows (Win32 API):
  Handle: 13109464, Type: 5, Text: Send Fax
  Handle: 7016056, Type: 5, Text: Receive Fax
  Handle: 6101902, Type: 5, Text: Settings
  Handle: 30281676, Type: 5, Text: <TreeView>
  Handle: 7409180, Type: 5, Text: <TreeView>
  Handle: 6691934, Type: 5, Text: Start Scan
  Handle: 2170024, Type: 0, Text: Fax
  Handle: 1514672, Type: 0, Text: Digitalizar
  Handle: 1907914, Type: 5, Text: Help
  ...

Notes:

  • The Type values correspond to control types defined in UI Automation (e.g., 50000 for Window, 50002 for Button).
  • <TreeView> indicates specialized controls that might require additional handling.

5. Ensuring Robustness and Performance

a. Error Handling:

  • Implement comprehensive error handling around UI Automation operations to gracefully handle unexpected scenarios.
  • Log errors with sufficient detail to facilitate debugging.

b. Performance Optimization:

  • Limit the scope of UI Automation queries to relevant parts of the UI to reduce overhead.
  • Cache frequently accessed elements when appropriate.

c. Thread Safety:

  • Ensure that all UI Automation interactions occur on threads initialized for COM, adhering to COM's threading models.
  • Avoid cross-thread UI Automation calls that could lead to undefined behavior.

d. Testing Across Controls:

  • Test the integration with various control types, including both standard Win32 and WPF controls, to ensure comprehensive coverage.
  • Validate that text extraction works reliably across different UI elements.

Conclusion

By integrating DelphiUIAutomation into your pScannerTest.dproj project, you significantly enhance your application's ability to interact with and extract data from both traditional Win32 and modern WPF controls. This dual approach ensures comprehensive GUI element parsing, addressing the initial limitation of missing text data from certain controls.

Final Recommendations:

  • Thorough Testing: After integration, conduct extensive testing across various applications and control types to validate functionality.
  • Documentation and Maintenance: Document the integration steps and maintain the DelphiUIAutomation library updates to ensure ongoing compatibility and feature support.
  • Performance Monitoring: Continuously monitor the application's performance to identify and address any potential bottlenecks introduced by UI Automation.

With these enhancements, your project should achieve its goal of robustly extracting GUI element data from diverse Windows applications, including those leveraging modern UI frameworks like WPF.

このQ&Aを共有