---
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.