WPF Control Detection Issues

Criado em: 12 de janeiro de 2025

Respondido usando OpenAI 01 por OpenAI01

Pergunta

See this C++ console application project for Visual Studio (I'm using the `ISO C++17 Standard (/std:c++17)`).


Note:

The files
"""
AnalysisResult.h
CommonTypes.h
ControlAnalyzer.h
ProcessorResult.h
UIElementAnalyzer.h
WindowAnalyzer.cpp
WindowAnalyzer.h
WindowData.h
WindowProcessor.cpp
WindowProcessor.h
WindowSource.cpp
WindowSource.h
"""
are located in the SOURCE_UNITS_uiparsercore folder.

The files
"""
WPFElementAttributes.h
WPFElementHandle.h
WPFElementParser.h
"""
are located in the SOURCE_UNITS_extractwpf folder.

The files
"""
WindowAttributes.h
WindowHandle.h
WindowParser.h
"""
are located in the SOURCE_UNITS_extractwin32 folder.


// UIScannerNavigator.h

// ... (other includes)

namespace UIScanner {

enum class ControlType {
Button,
Edit,
Static,
ComboBox,
StatusBar,
TreeView,
Toolbar,
Unknown
};

struct ControlInfo {
std::wstring type; // e.g., "Button", "Label", "TextBox"
ControlType controlType = ControlType::Unknown; // More specific type
std::wstring text; // Text content of the control
RECT rect; // Position and size of the control
bool isEnabled; // Whether the control is enabled
bool isVisible; // Whether the control is visible
};

// ... (rest of the file)
```

```cpp
// UIScannerNavigatorSource.h
// ...
void AddControl(const ControlInfo& control) {
// Check if a control with the same handle already exists
auto it = std::find_if(m_controls.begin(), m_controls.end(),
[&control](const ControlInfo& existingControl) {
return existingControl.type == control.type &&
existingControl.text == control.text &&
existingControl.rect.left == control.rect.left &&
existingControl.rect.top == control.rect.top;
});

// If the control does not exist, add it to the list
if (it == m_controls.end()) {
m_controls.push_back(control);
}
}
// ...
```

* **`ControlType` Enum:** We introduce an enum to represent control types more accurately, similar to the Delphi `TControlType`.
* **`controlType` in `ControlInfo`:** We add a `controlType` member to `ControlInfo` to store the identified type.

**2. `ControlProcessor` Class (New - Inspired by Delphi):**

We'll create a `ControlProcessor` class to handle control type identification and text retrieval. This is similar in purpose to the Delphi `TControlProcessor`.

```cpp
// ControlProcessor.h
#pragma once

#include <string>
#include <Windows.h>
#include "UIScannerNavigator.h"

namespace WindowParser {
class ControlProcessor {
public:
ControlProcessor() = default;
~ControlProcessor() = default;

UIScanner::ControlType DetermineControlType(HWND hwnd);
std::wstring GetControlText(HWND hwnd, UIScanner::ControlType type);
};
}
```

```cpp
// ControlProcessor.cpp
#include "ControlProcessor.h"

namespace WindowParser {

UIScanner::ControlType ControlProcessor::DetermineControlType(HWND hwnd) {
wchar_t className[256];
GetClassName(hwnd, className, 256);
std::wstring classNameStr(className);

if (classNameStr == L"Button") {
return UIScanner::ControlType::Button;
}
else if (classNameStr == L"Edit") {
return UIScanner::ControlType::Edit;
}
else if (classNameStr == L"Static") {
return UIScanner::ControlType::Static;
}
else if (classNameStr == L"ComboBox") {
return UIScanner::ControlType::ComboBox;
}
else if (classNameStr == L"msctls_statusbar32") {
return UIScanner::ControlType::StatusBar;
}
else if (classNameStr == L"SysTreeView32") {
return UIScanner::ControlType::TreeView;
}
else if (classNameStr == L"ToolbarWindow32") {
return UIScanner::ControlType::Toolbar;
}
else {
return UIScanner::ControlType::Unknown;
}
}

std::wstring ControlProcessor::GetControlText(HWND hwnd, UIScanner::ControlType type) {
switch (type) {
case UIScanner::ControlType::Button:
case UIScanner::ControlType::Static:
case UIScanner::ControlType::Edit: {
int len = GetWindowTextLength(hwnd);
if (len > 0) {
std::wstring text;
text.resize(len + 1);
GetWindowText(hwnd, &text[0], len + 1);
text.resize(len);
return text;
}
break;
}
case UIScanner::ControlType::ComboBox: {
// Get the currently selected item text
LRESULT selIndex = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
if (selIndex != CB_ERR) {
LRESULT textLen = SendMessage(hwnd, CB_GETLBTEXTLEN, selIndex, 0);
if (textLen > 0) {
std::wstring text;
text.resize(textLen + 1);
SendMessage(hwnd, CB_GETLBTEXT, selIndex, (LPARAM)&text[0]);
text.resize(textLen);
return text;
}
}
break;
}
case UIScanner::ControlType::TreeView:
return L"<TreeView>";
case UIScanner::ControlType::Toolbar:
return L"<Toolbar>";
default:
break;
}

return L"";
}

}
```

**3. Modify `UIScannerNavigator`:**

```cpp
// UIScannerNavigator.cpp

#include "UIScannerNavigator.h"
#include "SOURCE_UNITS_uiparsercore/ControlProcessor.h"
#include <Windows.h>
#include <string>
#include <iostream>

namespace UIScanner {

BOOL CALLBACK UIScannerNavigator::EnumChildProc(HWND hwnd, LPARAM lParam) {
UIScannerNavigator* navigator = reinterpret_cast<UIScannerNavigator*>(lParam);
if (navigator) {
// Recursive call to handle child windows
EnumChildWindows(hwnd, EnumChildProc, lParam);

ControlInfo control;

// Get the class name of the control
wchar_t className[256];
GetClassName(hwnd, className, 256);
control.type = className;

// Use ControlProcessor to determine control type
WindowParser::ControlProcessor controlProcessor;
control.controlType = controlProcessor.DetermineControlType(hwnd);

// Get the text of the control using ControlProcessor
control.text = controlProcessor.GetControlText(hwnd, control.controlType);

// Get the position and size of the control (relative to its parent)
RECT localRect;
GetWindowRect(hwnd, &localRect);

// Get parent window handle
HWND hParent = GetParent(hwnd);

// If parent is the target window, map to screen coordinates
if (hParent == navigator->m_windowHandle) {
POINT topLeft = { localRect.left, localRect.top };
POINT bottomRight = { localRect.right, localRect.bottom };
MapWindowPoints(hParent, HWND_DESKTOP, &topLeft, 1);
MapWindowPoints(hParent, HWND_DESKTOP, &bottomRight, 1);
control.rect = { topLeft.x, topLeft.y, bottomRight.x, bottomRight.y };
}
else {
control.rect = localRect;
}

// Check if the control is enabled
control.isEnabled = IsWindowEnabled(hwnd);

// Check if the control is visible, considering parent visibility
control.isVisible = IsWindowVisible(hwnd);
HWND parent = GetParent(hwnd);
while (parent != NULL && control.isVisible) {
control.isVisible = control.isVisible && IsWindowVisible(parent);
parent = GetParent(parent);
}

// Add the control to the list
navigator->m_source.AddControl(control);
}
return TRUE; // Continue enumeration
}

void UIScannerNavigator::ScanWindow() {
m_source.ClearControls();

// Enumerate child windows and extract control information
EnumChildWindows(m_windowHandle, EnumChildProc, reinterpret_cast<LPARAM>(this));
}

} // namespace UIScanner
```

**4. Update `win_APIUIPCORE.cpp` (Test 3):**

```cpp
// win_APIUIPCORE.cpp

// ... (other includes)

int main() {
// ... (other tests)

// --- Test 3: Get details of all elements/controls in a specific window (UIScannerNavigator) ---
HWND faxScannerWindowHandle = FindWindow(NULL, L"Fax e Scanner do Windows");
if (faxScannerWindowHandle) {
UIScanner::UIScannerNavigator navigator(faxScannerWindowHandle);
navigator.ScanWindow();

const auto& controls = navigator.GetSource().GetControls();
std::wcout << L"\n--- Controls in 'Fax e Scanner do Windows' Window ---\n" << std::endl;
for (const auto& control : controls) {
std::wcout << L"Handle: " << std::hex << control.rect.left << std::dec << L"\n";
std::wcout << L"Type: ";
switch (control.controlType) {
case UIScanner::ControlType::Button:
std::wcout << L"Button";
break;
case UIScanner::ControlType::Edit:
std::wcout << L"Edit";
break;
case UIScanner::ControlType::Static:
std::wcout << L"Static";
break;
case UIScanner::ControlType::ComboBox:
std::wcout << L"ComboBox";
break;
case UIScanner::ControlType::StatusBar:
std::wcout << L"StatusBar";
break;
case UIScanner::ControlType::TreeView:
std::wcout << L"TreeView";
break;
case UIScanner::ControlType::Toolbar:
std::wcout << L"Toolbar";
break;
default:
std::wcout << L"Unknown";
break;
}
std::wcout << L"\n";

std::wcout << L"Text: " << control.text << L"\n";
std::wcout << L"Position: (" << control.rect.left << L", " << control.rect.top << L") - ("
<< control.rect.right << L", " << control.rect.bottom << L")\n";
std::wcout << L"Enabled: " << (control.isEnabled ? L"Yes" : L"No") << L"\n";
std::wcout << L"Visible: " << (control.isVisible ? L"Yes" : L"No") << L"\n";
std::wcout << L"----------------------------\n";
}
}
else {
std::wcout << L"Could not find 'Fax e Scanner do Windows' window.\n";
}

return 0;
}
```

**Full Updated C++ Code:**

Here's the complete updated C++ code, including the new `ControlProcessor` and all modifications:

```cpp
// --- START OF FILE win_APIUIPCORE.cpp ---
#include <iostream>
#include <vector>
#include <iomanip>
#include "UIScannerSpot.h"
#include "UIScannerExtractor.h"
#include "UIScannerNavigator.h"

void displayReport(const std::vector<UIScanner::WindowInfo>& report, bool detailedView = false, const UIScanner::WindowInfo* detailedWindowInfo = nullptr) {
if (!detailedView) {
// Display the full report
std::wcout << std::left << std::setw(10) << L"Handle"
<< std::setw(30) << L"Title"
<< std::setw(20) << L"Position"
<< std::setw(10) << L"Visible"
<< std::setw(10) << L"PID"
<< std::setw(30) << L"Process Name"
<< std::setw(15) << L"Accessible"
<< std::setw(15) << L"Interactive"
<< L"Issues" << std::endl;
std::wcout << std::setfill(L'-') << std::setw(130) << L"-" << std::setfill(L' ') << std::endl;

for (const auto& info : report) {
std::wcout << std::left << std::setw(10) << info.handle
<< std::setw(30) << (info.title.empty() ? L"N/A" : info.title.substr(0, 27) + (info.title.length() > 27 ? L"..." : L""))
<< std::setw(20) << (L"(" + std::to_wstring(info.rect.left) + L", " + std::to_wstring(info.rect.top) + L") - (" +
std::to_wstring(info.rect.right) + L", " + std::to_wstring(info.rect.bottom) + L")")
<< std::setw(10) << (info.isVisible ? L"Yes" : L"No")
<< std::setw(10) << info.processId
<< std::setw(30) << (info.processName.empty() ? L"N/A" : info.processName.substr(0, 27) + (info.processName.length() > 27 ? L"..." : L""))
<< std::setw(15) << (info.isAccessible ? L"Yes" : L"No")
<< std::setw(15) << (info.isInteractive ? L"Yes" : L"No");

if (!info.potentialIssues.empty()) {
for (size_t i = 0; i < info.potentialIssues.size(); ++i) {
if (i > 0) {
std::wcout << std::setw(100) << L" ";
}
std::wcout << info.potentialIssues[i] << std::endl;
}
}
else {
std::wcout << std::endl;
}
}
}
else if (detailedWindowInfo) {
// Display detailed information for a single window
std::wcout << L"Detailed Window Information:\n";
std::wcout << L" Handle: " << detailedWindowInfo->handle << L"\n";
std::wcout << L" Title: " << detailedWindowInfo->title << L"\n";
std::wcout << L" Position: (" << detailedWindowInfo->rect.left << L", " << detailedWindowInfo->rect.top << L") - ("
<< detailedWindowInfo->rect.right << L", " << detailedWindowInfo->rect.bottom << L")\n";
std::wcout << L" Visible: " << (detailedWindowInfo->isVisible ? L"Yes" : L"No") << L"\n";
std::wcout << L" PID: " << detailedWindowInfo->processId << L"\n";
std::wcout << L" Process Name: " << detailedWindowInfo->processName << L"\n";
std::wcout << L" Accessible: " << (detailedWindowInfo->isAccessible ? L"Yes" : L"No") << L"\n";
std::wcout << L" Interactive: " << (detailedWindowInfo->isInteractive ? L"Yes" : L"No") << L"\n";

if (!detailedWindowInfo->potentialIssues.empty()) {
std::wcout << L" Issues:\n";
for (const auto& issue : detailedWindowInfo->potentialIssues) {
std::wcout << L" " << issue << L"\n";
}
}
}
}

int main() {
// --- Test 1: List all windows (UIScannerSpot) ---
UIScanner::UIScannerSpot spot;
spot.SetScanMode(UIScanner::ScanMode::Visible);
spot.SetRequireTitle(false); // Set as needed
spot.Scan();
std::vector<UIScanner::WindowInfo> windowInfoList = spot.GetWindowInfoList();

std::wcout << L"\n--- All Windows (UIScannerSpot) ---\n" << std::endl;
displayReport(windowInfoList); // Corrected call for all windows

// --- Test 2: Detailed report for a specific window (UIScannerExtractor) ---
// Find the WindowInfo for "Fax e Scanner do Windows"
auto it = std::find_if(windowInfoList.begin(), windowInfoList.end(), [](const UIScanner::WindowInfo& info) {
return info.title == L"Fax e Scanner do Windows";
});

if (it != windowInfoList.end()) {
// Found the window, create UIScannerExtractor with the correct WindowInfo
UIScanner::UIScannerExtractor extractor(*it);

std::wcout << L"\n--- Detailed Information for 'Fax e Scanner do Windows' (UIScannerExtractor) ---\n" << std::endl;

// Call displayReport with detailed view and a pointer to the detailedWindowInfo
displayReport({}, true, &extractor.GetWindowInfo());
}
else {
std::wcout << L"Could not find 'Fax e Scanner do Windows' window in the list.\n";
}

// --- Test 3: Get details of all elements/controls in a specific window (UIScannerNavigator) ---
HWND faxScannerWindowHandle = FindWindow(NULL, L"Fax e Scanner do Windows");
if (faxScannerWindowHandle) {
UIScanner::UIScannerNavigator navigator(faxScannerWindowHandle);
navigator.ScanWindow();

const auto& controls = navigator.GetSource().GetControls();
std::wcout << L"\n--- Controls in 'Fax e Scanner do Windows' Window ---\n" << std::endl;
for (const auto& control : controls) {
std::wcout << L"Handle: " << std::hex << control.rect.left << std::dec << L"\n";
std::wcout << L"Type: ";
switch (control.controlType) {
case UIScanner::ControlType::Button:
std::wcout << L"Button";
break;
case UIScanner::ControlType::Edit:
std::wcout << L"Edit";
break;
case UIScanner::ControlType::Static:
std::wcout << L"Static";
break;
case UIScanner::ControlType::ComboBox:
std::wcout << L"ComboBox";
break;
case UIScanner::ControlType::StatusBar:
std::wcout << L"StatusBar";
break;
case UIScanner::ControlType::TreeView:
std::wcout << L"TreeView";
break;
case UIScanner::ControlType::Toolbar:
std::wcout << L"Toolbar";
break;
default:
std::wcout << L"Unknown";
break;
}
std::wcout << L"\n";

std::wcout << L"Text: " << control.text << L"\n";
std::wcout << L"Position: (" << control.rect.left << L", " << control.rect.top << L") - ("
<< control.rect.right << L", " << control.rect.bottom << L")\n";
std::wcout << L"Enabled: " << (control.isEnabled ? L"Yes" : L"No") << L"\n";
std::wcout << L"Visible: " << (control.isVisible ? L"Yes" : L"No") << L"\n";
std::wcout << L"----------------------------\n";
}
}
else {
std::wcout << L"Could not find 'Fax e Scanner do Windows' window.\n";
}

return 0;
}
// --- START OF FILE UIScannerExtractor.h ---

// UIScannerExtractor.h
#pragma once

#include "UIScannerExtractorSource.h"
#include "UIScannerSpot.h"
#include <Windows.h>

namespace UIScanner {

class UIScannerExtractor {
public:
UIScannerExtractor(const WindowInfo& windowInfo);
~UIScannerExtractor() = default;

void ExtractWindowDetails();
const WindowInfo& GetWindowInfo() const; // Getter for m_windowInfo

private:
UIScannerExtractorSource m_extractorSource;
WindowInfo m_windowInfo; // Store the WindowInfo for this window
};

} // namespace UIScanner
// --- START OF FILE UIScannerHandler.h ---

#pragma once

#include "UIScannerHandlerSource.h"
#include "UIScannerSpot.h"
#include <Windows.h>

namespace UIScanner {

struct EnumCallbackData {
void* objPtr; // To store either UIScannerHandler* or UIScannerExtractor*
bool requireTitle;
};

class UIScannerHandler {
public:
UIScannerHandler() = default;
~UIScannerHandler() = default;

// Scan for windows and store their handles
void ScanWindows(ScanMode mode = ScanMode::Raw);

// Get the stored window handles
const std::vector<HWND>& GetWindowHandles() const {
return m_handlerSource.GetWindowHandles();
}

// Set the scan mode
void SetScanMode(ScanMode mode) { m_currentScanMode = mode; }

// Set require title
void SetRequireTitle(bool requireTitle) { m_requireTitle = requireTitle; }
bool GetRequireTitle() const { return m_requireTitle; }

private:
UIScannerHandlerSource m_handlerSource;
ScanMode m_currentScanMode = ScanMode::Raw;
bool m_requireTitle = false;

// Callback function for EnumWindows
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
};

}
// --- START OF FILE UIScannerHandler.cpp ---

#include "UIScannerHandler.h"

namespace UIScanner {

// Scan for windows and store their handles
void UIScannerHandler::ScanWindows(ScanMode mode) {
m_handlerSource.ClearWindowHandles(); // Clear previous handles
m_currentScanMode = mode; // Store the current mode

// Prepare data for the callback
EnumCallbackData data = { this, m_requireTitle };

EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&data));
}

// Callback function for EnumWindows
BOOL CALLBACK UIScannerHandler::EnumWindowsProc(HWND hwnd, LPARAM lParam) {
EnumCallbackData* data = reinterpret_cast<EnumCallbackData*>(lParam);
UIScannerHandler* handler = static_cast<UIScannerHandler*>(data->objPtr);

if (handler) {
// Check if the window has a title if required
if (data->requireTitle) {
int titleLength = GetWindowTextLengthW(hwnd);
if (titleLength == 0) {
return TRUE; // Skip this window
}
}

// Filter based on scan mode
bool shouldProcess = false;
switch (handler->m_currentScanMode) {
case ScanMode::Raw:
shouldProcess = true;
break;
case ScanMode::Visible:
shouldProcess = IsWindowVisible(hwnd);
break;
case ScanMode::Active:
shouldProcess = (GetForegroundWindow() == hwnd);
break;
case ScanMode::OnTop:
shouldProcess = (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
break;
case ScanMode::Enabled:
shouldProcess = IsWindowEnabled(hwnd);
break;
}

if (shouldProcess) {
handler->m_handlerSource.AddWindowHandle(hwnd);
}
}
return TRUE; // Continue enumeration
}

} // namespace UIScanner
// --- START OF FILE UIScannerExtractor.cpp ---

// UIScannerExtractor.cpp
#include "UIScannerExtractor.h"

namespace UIScanner {

UIScannerExtractor::UIScannerExtractor(const WindowInfo& windowInfo)
: m_windowInfo(windowInfo) // Initialize m_windowInfo with the provided WindowInfo
{
// Now we have the initial window data, let's use it
ExtractWindowDetails();
}

void UIScannerExtractor::ExtractWindowDetails() {
// Here, we can set all details in m_extractorSource using m_windowInfo
m_extractorSource.SetWindowInfo(m_windowInfo);
}

const WindowInfo& UIScannerExtractor::GetWindowInfo() const {
return m_windowInfo;
}

} // namespace UIScanner
// --- START OF FILE UIScannerExtractorSource.h ---

// UIScannerExtractorSource.h
#pragma once

#include <vector>
#include <Windows.h>
#include <string>
#include "UIScannerSpot.h"

namespace UIScanner {

class UIScannerExtractorSource {
public:
UIScannerExtractorSource() = default;
~UIScannerExtractorSource() = default;

void SetWindowInfo(const WindowInfo& info) {
m_windowInfo = info;
}

const WindowInfo& GetWindowInfo() const {
return m_windowInfo;
}

private:
WindowInfo m_windowInfo;
};

} // namespace UIScanner
// --- START OF FILE UIScannerHandlerSource.h ---

#pragma once

#include <vector>
#include <Windows.h>

namespace UIScanner {

class UIScannerHandlerSource {
public:
UIScannerHandlerSource() = default;
~UIScannerHandlerSource() = default;

// Add a window handle to the storage
void AddWindowHandle(HWND handle) {
m_windowHandles.push_back(handle);
}

// Get all stored window handles
const std::vector<HWND>& GetWindowHandles() const {
return m_windowHandles;
}

// Clear all stored window handles
void ClearWindowHandles() {
m_windowHandles.clear();
}

private:
std::vector<HWND> m_windowHandles; // Stores window handles
};

} // namespace UIScanner
// --- START OF FILE UIScannerNavigator.h ---

#pragma once

#include <Windows.h>
#include <vector>
#include <string>

namespace UIScanner {

enum class ControlType {
Button,
Edit,
Static,
ComboBox,
StatusBar,
TreeView,
Toolbar,
Unknown
};

struct ControlInfo {
std::wstring type; // e.g., "Button", "Label", "TextBox"
ControlType controlType = ControlType::Unknown; // More specific type
std::wstring text; // Text content of the control
RECT rect; // Position and size of the control
bool isEnabled; // Whether the control is enabled
bool isVisible; // Whether the control is visible
};

class UIScannerNavigatorSource {
public:
UIScannerNavigatorSource() = default;
~UIScannerNavigatorSource() = default;

void AddControl(const ControlInfo& control) {
// Check if a control with the same handle already exists
auto it = std::find_if(m_controls.begin(), m_controls.end(),
[&control](const ControlInfo& existingControl) {
return existingControl.type == control.type &&
existingControl.text == control.text &&
existingControl.rect.left == control.rect.left &&
existingControl.rect.top == control.rect.top;
});

// If the control does not exist, add it to the list
if (it == m_controls.end()) {
m_controls.push_back(control);
}
}

const std::vector<ControlInfo>& GetControls() const {
return m_controls;
}

void ClearControls() {
m_controls.clear();
}

private:
std::vector<ControlInfo> m_controls;
};

class UIScannerNavigator {
public:
UIScannerNavigator(HWND windowHandle) : m_windowHandle(windowHandle) {}

void ScanWindow();

const UIScannerNavigatorSource& GetSource() const {
return m_source;
}

private:
HWND m_windowHandle;
UIScannerNavigatorSource m_source;

static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam);
};

} // namespace UIScanner
// --- START OF FILE UIScannerNavigator.cpp ---

#include "UIScannerNavigator.h"
#include "SOURCE_UNITS_uiparsercore/ControlProcessor.h"
#include <Windows.h>
#include <string>
#include <iostream>

namespace UIScanner {

BOOL CALLBACK UIScannerNavigator::EnumChildProc(HWND hwnd, LPARAM lParam) {
UIScannerNavigator* navigator = reinterpret_cast<UIScannerNavigator*>(lParam);
if (navigator) {
// Recursive call to handle child windows
EnumChildWindows(hwnd, EnumChildProc, lParam);

ControlInfo control;

// Get the class name of the control
wchar_t className[256];
GetClassName(hwnd, className, 256);
control.type = className;

// Use ControlProcessor to determine control type
WindowParser::ControlProcessor controlProcessor;
control.controlType = controlProcessor.DetermineControlType(hwnd);

// Get the text of the control using ControlProcessor
control.text = controlProcessor.GetControlText(hwnd, control.controlType);

// Get the position and size of the control (relative to its parent)
RECT localRect;
GetWindowRect(hwnd, &localRect);

// Get parent window handle
HWND hParent = GetParent(hwnd);

// If parent is the target window, map to screen coordinates
if (hParent == navigator->m_windowHandle) {
POINT topLeft = { localRect.left, localRect.top };
POINT bottomRight = { localRect.right, localRect.bottom };
MapWindowPoints(hParent, HWND_DESKTOP, &topLeft, 1);
MapWindowPoints(hParent, HWND_DESKTOP, &bottomRight, 1);
control.rect = { topLeft.x, topLeft.y, bottomRight.x, bottomRight.y };
}
else {
control.rect = localRect;
}

// Check if the control is enabled
control.isEnabled = IsWindowEnabled(hwnd);

// Check if the control is visible, considering parent visibility
control.isVisible = IsWindowVisible(hwnd);
HWND parent = GetParent(hwnd);
while (parent != NULL && control.isVisible) {
control.isVisible = control.isVisible && IsWindowVisible(parent);
parent = GetParent(parent);
}

// Add the control to the list
navigator->m_source.AddControl(control);
}
return TRUE; // Continue enumeration
}

void UIScannerNavigator::ScanWindow() {
m_source.ClearControls();

// Enumerate child windows and extract control information
EnumChildWindows(m_windowHandle, EnumChildProc, reinterpret_cast<LPARAM>(this));
}

} // namespace UIScanner
// --- START OF FILE UIScannerNavigatorSource.h ---

#pragma once
#include <vector>
#include <string>
#include <Windows.h>

namespace UIScanner {

struct ControlInfo {
std::wstring type; // e.g., "Button", "Label", "TextBox"
std::wstring text; // Text content of the control
RECT rect; // Position and size of the control
bool isEnabled; // Whether the control is enabled
bool isVisible; // Whether the control is visible
};

class UIScannerNavigatorSource {
public:
UIScannerNavigatorSource() = default;
~UIScannerNavigatorSource() = default;

void AddControl(const ControlInfo& control) {
m_controls.push_back(control);
}

const std::vector<ControlInfo>& GetControls() const {
return m_controls;
}

void ClearControls() {
m_controls.clear();
}

private:
std::vector<ControlInfo> m_controls;
};

} // namespace UIScanner
// --- START OF FILE UIScannerSpot.cpp ---

// UIScannerSpot.cpp
#include "UIScannerSpot.h"
#include <Psapi.h>

namespace UIScanner {

UIScannerSpot::UIScannerSpot() {}

void UIScannerSpot::Scan() {
m_windowInfoList.clear();
EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(this));
}

std::vector<WindowInfo> UIScannerSpot::GetWindowInfoList() const {
return m_windowInfoList;
}

BOOL CALLBACK UIScannerSpot::EnumWindowsProc(HWND hwnd, LPARAM lParam) {
UIScannerSpot* scanner = reinterpret_cast<UIScannerSpot*>(lParam);
if (!scanner) {
return FALSE;
}

// Check ScanMode
switch (scanner->m_currentScanMode) {
case ScanMode::Raw:
// Process all windows in Raw mode
break;
case ScanMode::Visible:
if (!IsWindowVisible(hwnd)) {
return TRUE; // Skip non-visible windows
}
break;
case ScanMode::Active:
if (GetForegroundWindow() != hwnd) {
return TRUE; // Skip non-active windows
}
break;
case ScanMode::OnTop:
if (!(GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST)) {
return TRUE; // Skip if not on top
}
break;
case ScanMode::Enabled:
if (!IsWindowEnabled(hwnd)) {
return TRUE; // Skip if not enabled
}
break;
}

// Check title requirement if needed
if (scanner->m_requireTitle && GetWindowTextLengthW(hwnd) == 0) {
return TRUE; // Skip windows without titles if required
}

WindowInfo info;
info.handle = hwnd;

// Get window title
int titleLength = GetWindowTextLengthW(hwnd);
if (titleLength > 0) {
info.title.resize(titleLength + 1);
GetWindowTextW(hwnd, &info.title[0], titleLength + 1);
info.title.resize(titleLength);
}
else {
info.title = L"N/A";
}

// Get window position and size
GetWindowRect(hwnd, &info.rect);

// Get window visibility
info.isVisible = IsWindowVisible(hwnd) != 0;

// Analyze the window
auto analysisResult = scanner->m_windowAnalyzer.GetWindowAnalysis(hwnd);
info.isAccessible = analysisResult.isAccessible;
info.isInteractive = analysisResult.isInteractive;
info.potentialIssues = analysisResult.potentialIssues;

// Get process information
DWORD processId;
GetWindowThreadProcessId(hwnd, &processId);
info.processId = processId;

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (hProcess != NULL) {
wchar_t processName[MAX_PATH];
if (GetModuleFileNameExW(hProcess, NULL, processName, MAX_PATH)) {
info.processName = processName;
}
else {
info.processName = L"N/A";
}
CloseHandle(hProcess);
}
else {
info.processName = L"N/A";
}

scanner->m_windowInfoList.push_back(info);

return TRUE;
}

} // namespace UIScanner
// --- START OF FILE UIScannerSpot.h ---

// UIScannerSpot.h
#pragma once

#include "SOURCE_UNITS_uiparsercore/WindowSource.h"
#include "SOURCE_UNITS_uiparsercore/WindowAnalyzer.h"
#include <vector>
#include <Windows.h>

namespace UIScanner {

enum class ScanMode {
Raw,
Visible,
Active,
OnTop,
Enabled
};

struct WindowInfo {
HWND handle = nullptr;
std::wstring title;
RECT rect = {}; // Initialize with an empty RECT
bool isVisible = false;
DWORD processId = 0;
std::wstring processName;
bool isAccessible = false;
bool isInteractive = false;
std::vector<std::wstring> potentialIssues;
};

class UIScannerSpot {
public:
UIScannerSpot();
~UIScannerSpot() = default;

void SetScanMode(ScanMode mode) { m_currentScanMode = mode; }
ScanMode GetScanMode() const { return m_currentScanMode; }

void SetRequireTitle(bool requireTitle) { m_requireTitle = requireTitle; }
bool GetRequireTitle() const { return m_requireTitle; }

void Scan();
std::vector<WindowInfo> GetWindowInfoList() const;

private:
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
std::vector<WindowInfo> m_windowInfoList;
WindowParser::WindowAnalyzer m_windowAnalyzer;
ScanMode m_currentScanMode = ScanMode::Raw;
bool m_requireTitle = false;
};

} // namespace UIScanner
// --- START OF FILE UIScannerSpotSource.h ---

#pragma once

#include <Windows.h>
#include <string>

namespace UIScanner {

struct WindowSpotInfo {
HWND handle;
std::wstring title;
};

} // namespace UIScanner
// --- START OF FILE WindowSource.h ---

#pragma once
#include "CommonTypes.h"
#include <memory>

namespace WindowParser {
class WindowSource {
public:
HWND GetWindowHandle() const;
std::wstring GetWindowTitle() const;
Position GetWindowPosition() const;
Size GetWindowSize() const;
WindowState GetWindowState() const;

static std::unique_ptr<WindowSource> FromHandle(HWND handle);
private:
HWND m_handle;
WindowSource(HWND handle) : m_handle(handle) {}
};
}
// --- START OF FILE ControlAnalyzer.h ---

#pragma once
#include "AnalysisResult.h"
#include <functional>

namespace WindowParser {
class ControlAnalyzer {
public:
using AnalysisCallback = std::function<void(const ControlAnalysisResult&)>;

ControlAnalyzer() = default;
~ControlAnalyzer() = default;

ControlAnalysisResult AnalyzeControlData(const ProcessingResult<ProcessedControlInfo>& processedData);
void SetCallback(AnalysisCallback callback) { m_callback = callback; }

private:
AnalysisCallback m_callback;
std::vector<std::wstring> CheckAccessibility(const ProcessedControlInfo& info);
};
}
// --- START OF FILE AnalysisResult.h ---

#pragma once
#include "ProcessorResult.h"
#include <vector>
#include <chrono>

namespace WindowParser {
struct WindowAnalysisResult {
bool isAccessible = false;
bool isInteractive = false;
std::vector<std::wstring> potentialIssues;
std::chrono::system_clock::time_point analysisTime;
ProcessedWindowInfo processedInfo;
};

struct ControlAnalysisResult {
bool isAccessible;
bool isInteractable;
std::vector<std::wstring> accessibilityIssues;
std::chrono::system_clock::time_point analysisTime;
ProcessedControlInfo processedInfo;
};

struct UIElementAnalysisResult {
bool isAccessible;
bool meetsAccessibilityStandards;
std::vector<std::wstring> recommendations;
std::chrono::system_clock::time_point analysisTime;
ProcessedUIElementInfo processedInfo;
};
}
// --- START OF FILE CommonTypes.h ---

#pragma once
#include <Windows.h>
#include <string>

namespace WindowParser {
struct Position {
int x;
int y;
int width;
int height;
};

struct Size {
int width;
int height;
};

enum class WindowState {
Normal,
Minimized,
Maximized
};

enum class ControlState {
Enabled,
Disabled,
Checked,
Unchecked
};

enum class UIElementState {
Visible,
Hidden
};
}
// --- START OF FILE ProcessorResult.h ---

#pragma once
#include <string>
#include <vector>
#include <memory>
#include "CommonTypes.h"

namespace WindowParser {
enum class ProcessingStatus {
Success,
Failed,
NoData,
InvalidHandle
};

template<typename T>
struct ProcessingResult;

struct ProcessedWindowInfo {
bool isResponding = false;
bool isVisible = false;
bool hasFocus = false;
std::wstring processName;
DWORD processId = 0;
};

struct ProcessedControlInfo {
bool isEnabled;
bool isVisible;
bool canInteract;
std::wstring className;
};

struct ProcessedUIElementInfo {
bool isVisible;
bool isEnabled;
std::wstring automationId;
};

template<typename T>
struct ProcessingResult {
ProcessingStatus status = ProcessingStatus::NoData; // Or another appropriate default
std::shared_ptr<T> data;
std::wstring message;
};
}
// --- START OF FILE WindowData.h ---

#pragma once
#include "CommonTypes.h"
#include <string>

namespace WindowParser {
class WindowData {
public:
WindowData(HWND handle, const std::wstring& title,
const Position& pos, const Size& size,
WindowState state)
: m_handle(handle)
, m_title(title)
, m_position(pos)
, m_size(size)
, m_state(state) {
}

HWND handle() const { return m_handle; }
const std::wstring& title() const { return m_title; }
const Position& position() const { return m_position; }
const Size& size() const { return m_size; }
WindowState state() const { return m_state; }

private:
HWND m_handle;
std::wstring m_title;
Position m_position;
Size m_size;
WindowState m_state;
};
}
// --- START OF FILE UIElementAnalyzer.h ---

#pragma once
#include "AnalysisResult.h"
#include <functional>

namespace WindowParser {
class UIElementAnalyzer {
public:
using AnalysisCallback = std::function<void(const UIElementAnalysisResult&)>;

UIElementAnalyzer() = default;
~UIElementAnalyzer() = default;

UIElementAnalysisResult AnalyzeUIElementData(const ProcessingResult<ProcessedUIElementInfo>& processedData);
void SetCallback(AnalysisCallback callback) { m_callback = callback; }

private:
AnalysisCallback m_callback;
std::vector<std::wstring> ValidateAccessibilityStandards(const ProcessedUIElementInfo& info);
};
}
// --- START OF FILE WindowAnalyzer.cpp ---

#include "WindowAnalyzer.h"
#include "WindowProcessor.h"

namespace WindowParser {
WindowAnalysisResult WindowAnalyzer::AnalyzeWindowData(
const ProcessingResult<ProcessedWindowInfo>& processedData) {
WindowAnalysisResult result;
result.analysisTime = std::chrono::system_clock::now();

if (processedData.status != ProcessingStatus::Success || !processedData.data) {
result.isAccessible = false;
result.isInteractive = false;
result.potentialIssues.push_back(L"Failed to process window data");
return result;
}

result.processedInfo = *processedData.data;
result.isInteractive = CheckInteractivity(result.processedInfo);
result.potentialIssues = AnalyzeAccessibility(result.processedInfo);
result.isAccessible = result.potentialIssues.empty();

if (m_callback) {
m_callback(result);
}

return result;
}

WindowAnalysisResult WindowAnalyzer::GetWindowAnalysis(HWND handle) {
WindowProcessor processor;
auto processedData = processor.GetProcessedWindowData(handle);
return AnalyzeWindowData(processedData);
}

std::vector<std::wstring> WindowAnalyzer::AnalyzeAccessibility(const ProcessedWindowInfo& info) {
std::vector<std::wstring> issues;

if (!info.isVisible) {
issues.push_back(L"Window is not visible");
}

if (!info.isResponding) {
issues.push_back(L"Window is not responding");
}

return issues;
}

bool WindowAnalyzer::CheckInteractivity(const ProcessedWindowInfo& info) {
return info.isResponding && info.isVisible;
}
}
// --- START OF FILE WindowProcessor.cpp ---

#include "WindowProcessor.h"
#include <psapi.h>

namespace WindowParser {
ProcessingResult<ProcessedWindowInfo> WindowProcessor::ProcessWindowData(const WindowData& windowData) {
ProcessingResult<ProcessedWindowInfo> result;
result.data = std::make_shared<ProcessedWindowInfo>();

if (!IsWindow(windowData.handle())) {
result.status = ProcessingStatus::InvalidHandle;
result.message = L"Invalid window handle";
return result;
}

// Get process information
std::wstring processName;
DWORD processId;
if (!GetWindowProcessInfo(windowData.handle(), processName, processId)) {
result.status = ProcessingStatus::Failed;
result.message = L"Failed to get process information";
return result;
}

// Fill processed info
result.data->isResponding = IsWindowEnabled(windowData.handle());
result.data->isVisible = IsWindowVisible(windowData.handle());
result.data->hasFocus = (GetForegroundWindow() == windowData.handle());
result.data->processName = processName;
result.data->processId = processId;

result.status = ProcessingStatus::Success;

if (m_callback) {
m_callback(result);
}

return result;
}

ProcessingResult<ProcessedWindowInfo> WindowProcessor::GetProcessedWindowData(HWND handle) {
auto windowSource = WindowSource::FromHandle(handle);
if (!windowSource) {
ProcessingResult<ProcessedWindowInfo> result;
result.status = ProcessingStatus::InvalidHandle;
result.message = L"Could not create WindowSource from handle";
return result;
}

WindowData data(
handle,
windowSource->GetWindowTitle(),
windowSource->GetWindowPosition(),
windowSource->GetWindowSize(),
windowSource->GetWindowState()
);

return ProcessWindowData(data);
}

bool WindowProcessor::GetWindowProcessInfo(HWND handle, std::wstring& processName, DWORD& processId) {
GetWindowThreadProcessId(handle, &processId);
if (processId == 0) return false;

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (!hProcess) return false;

wchar_t buffer[MAX_PATH];
if (GetModuleFileNameEx(hProcess, nullptr, buffer, MAX_PATH)) {
processName = buffer;
}

CloseHandle(hProcess);
return true;
}
}
// --- START OF FILE WindowAnalyzer.h ---

#pragma once
#include "AnalysisResult.h"
#include <functional>

namespace WindowParser {
class WindowAnalyzer {
public:
using AnalysisCallback = std::function<void(const WindowAnalysisResult&)>;

WindowAnalyzer() = default;
~WindowAnalyzer() = default;

WindowAnalysisResult AnalyzeWindowData(const ProcessingResult<ProcessedWindowInfo>& processedData);
WindowAnalysisResult GetWindowAnalysis(HWND handle);
void SetCallback(AnalysisCallback callback) { m_callback = callback; }

private:
AnalysisCallback m_callback;
std::vector<std::wstring> AnalyzeAccessibility(const ProcessedWindowInfo& info);
bool CheckInteractivity(const ProcessedWindowInfo& info);
};
}
// --- START OF FILE WindowProcessor.h ---

#pragma once
#include "ProcessorResult.h"
#include "WindowData.h"
#include "WindowSource.h" // Add this include
#include <functional>

namespace WindowParser {
class WindowProcessor {
public:
using WindowCallback = std::function<void(const ProcessingResult<ProcessedWindowInfo>&)>;

WindowProcessor() = default;
~WindowProcessor() = default;

ProcessingResult<ProcessedWindowInfo> ProcessWindowData(const WindowData& windowData);
ProcessingResult<ProcessedWindowInfo> GetProcessedWindowData(HWND handle);

void SetCallback(WindowCallback callback) { m_callback = callback; }

private:
WindowCallback m_callback;
bool GetWindowProcessInfo(HWND handle, std::wstring& processName, DWORD& processId);
};
}
// --- START OF FILE WindowSource.cpp ---

#include "WindowSource.h"

namespace WindowParser {
HWND WindowSource::GetWindowHandle() const {
return m_handle;
}

std::wstring WindowSource::GetWindowTitle() const {
int length = GetWindowTextLengthW(m_handle);
if (length == 0) return std::wstring();

std::wstring title(length + 1, 0);
GetWindowTextW(m_handle, &title[0], length + 1);
title.resize(length);
return title;
}

Position WindowSource::GetWindowPosition() const {
RECT rect;
GetWindowRect(m_handle, &rect);
return { rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top };
}

Size WindowSource::GetWindowSize() const {
RECT rect;
GetWindowRect(m_handle, &rect);
return { rect.right - rect.left, rect.bottom - rect.top };
}

WindowState WindowSource::GetWindowState() const {
WINDOWPLACEMENT placement = { sizeof(WINDOWPLACEMENT) };
GetWindowPlacement(m_handle, &placement);

switch (placement.showCmd) {
case SW_SHOWMAXIMIZED:
return WindowState::Maximized;
case SW_SHOWMINIMIZED:
return WindowState::Minimized;
default:
return WindowState::Normal;
}
}

std::unique_ptr<WindowSource> WindowSource::FromHandle(HWND handle) {
if (!IsWindow(handle)) return nullptr;
return std::unique_ptr<WindowSource>(new WindowSource(handle));
}
}
// --- START OF FILE WPFElementHandle.h ---

#ifndef WPFELEMENTHANDLE_H
#define WPFELEMENTHANDLE_H

#include <string>
#include <Windows.h>
#include <UIAutomation.h>
#include <atlbase.h>

class WPFElementHandle {
protected:
CComPtr<IUIAutomationElement> element;

public:
WPFElementHandle(CComPtr<IUIAutomationElement> elem) : element(elem) {}
virtual ~WPFElementHandle() = default;

std::wstring GetName() const;
std::wstring GetClassName() const;
};

#endif // WPFELEMENTHANDLE_H
// --- START OF FILE WPFElementParser.h ---

#ifndef WPFELEMENTPARSER_H
#define WPFELEMENTPARSER_H

#include "WPFElementAttributes.h"
#include <vector>

class WPFElementParser {
public:
static std::vector<WPFElementAttributes> ParseChildElements(CComPtr<IUIAutomationElement> parent);
};

#endif // WPFELEMENTPARSER_H
// --- START OF FILE WPFElementAttributes.h ---

#ifndef WPFELEMENTATTRIBUTES_H
#define WPFELEMENTATTRIBUTES_H

#include "WPFElementHandle.h"

class WPFElementAttributes : public WPFElementHandle {
public:
WPFElementAttributes(CComPtr<IUIAutomationElement> elem) : WPFElementHandle(elem) {}

RECT GetBoundingRectangle() const;
bool IsEnabled() const;
bool IsVisible() const;
};

#endif // WPFELEMENTATTRIBUTES_H
// --- START OF FILE WindowParser.h ---

#ifndef WINDOWPARSER_H
#define WINDOWPARSER_H

#include "WindowAttributes.h"
#include <vector>

class WindowParser {
public:
static std::vector<HWND> EnumerateChildWindows(HWND parent);
static std::vector<WindowAttributes> ParseChildWindows(HWND parent);
};

#endif // WINDOWPARSER_H
// --- START OF FILE WindowHandle.h ---

#ifndef WINDOWHANDLE_H
#define WINDOWHANDLE_H

#include <windows.h>
#include <string>

class WindowHandle {
protected:
HWND hWnd;

public:
WindowHandle(HWND handle) : hWnd(handle) {}
virtual ~WindowHandle() = default;

HWND GetHandle() const { return hWnd; }
std::string GetWindowText() const;
std::string GetClassName() const;
};

#endif // WINDOWHANDLE_H
// --- START OF FILE WindowAttributes.h ---

#ifndef WINDOWATTRIBUTES_H
#define WINDOWATTRIBUTES_H

#include "WindowHandle.h"

class WindowAttributes : public WindowHandle {
public:
WindowAttributes(HWND handle) : WindowHandle(handle) {}

RECT GetDimensions() const;
bool IsVisible() const;
bool IsEnabled() const;
};

#endif // WINDOWATTRIBUTES_H
// --- START OF FILE ControlProcessor.h ---
#pragma once

#include <string>
#include <Windows.h>
#include "UIScannerNavigator.h"

namespace WindowParser {
class ControlProcessor {
public:
ControlProcessor() = default;
~ControlProcessor() = default;

UIScanner::ControlType DetermineControlType(HWND hwnd);
std::wstring GetControlText(HWND hwnd, UIScanner::ControlType type);
};
}
// --- START OF FILE ControlProcessor.cpp ---
#include "ControlProcessor.h"

namespace WindowParser {

UIScanner::ControlType ControlProcessor::DetermineControlType(HWND hwnd) {
wchar_t className[256];
GetClassName(hwnd, className, 256);
std::wstring classNameStr(className);

if (classNameStr == L"Button") {
return UIScanner::ControlType::Button;
}
else if (classNameStr == L"Edit") {
return UIScanner::ControlType::Edit;
}
else if (classNameStr == L"Static") {
return UIScanner::ControlType::Static;
}
else if (classNameStr == L"ComboBox") {
return UIScanner::ControlType::ComboBox;
}
else if (classNameStr == L"msctls_statusbar32") {
return UIScanner::ControlType::StatusBar;
}
else if (classNameStr == L"SysTreeView32") {
return UIScanner::ControlType::TreeView;
}
else if (classNameStr == L"ToolbarWindow32") {
return UIScanner::ControlType::Toolbar;
}
else {
return UIScanner::ControlType::Unknown;
}
}

std::wstring ControlProcessor::GetControlText(HWND hwnd, UIScanner::ControlType type) {
switch (type) {
case UIScanner::ControlType::Button:
case UIScanner::ControlType::Static:
case UIScanner::ControlType::Edit: {
int len = GetWindowTextLength(hwnd);
if (len > 0) {
std::wstring text;
text.resize(len + 1);
GetWindowText(hwnd, &text[0], len + 1);
text.resize(len);
return text;
}
break;
}
case UIScanner::ControlType::ComboBox: {
// Get the currently selected item text
LRESULT selIndex = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
if (selIndex != CB_ERR) {
LRESULT textLen = SendMessage(hwnd, CB_GETLBTEXTLEN, selIndex, 0);
if (textLen > 0) {
std::wstring text;
text.resize(textLen + 1);
SendMessage(hwnd, CB_GETLBTEXT, selIndex, (LPARAM)&text[0]);
text.resize(textLen);
return text;
}
}
break;
}
case UIScanner::ControlType::TreeView:
return L"<TreeView>";
case UIScanner::ControlType::Toolbar:
return L"<Toolbar>";
default:
break;
}

return L"";
}

}

There is a test example to obtain in the third test the data of the interface elements of the specific window `Fax e Scanner do Windows` (title in Portuguese).

However, the terminal output presented is this:
"""

--- All Windows (UIScannerSpot) ---

Handle Title Position Visible PID Process Name Accessible Interactive Issues
----------------------------------------------------------------------------------------------------------------------------------
00000000000100B4N/A (0, 824) - (1536, 864)Yes 8948 C:\Windows\explorer.exe Yes Yes
0000000000020780C:\agents\SUBMODULES_uipars...(179, 179) - (1074, 697)Yes 19488 C:\Program Files\Microsoft ...Yes Yes
0000000000010556N/A (0, 824) - (1536, 824)Yes 7496 C:\Program Files\Microsoft ...Yes Yes
0000000000010552N/A (1536, 0) - (1536, 824)Yes 7496 C:\Program Files\Microsoft ...Yes Yes
000000000001054CN/A (0, 0) - (0, 824) Yes 7496 C:\Program Files\Microsoft ...Yes Yes
0000000000010536win_APIUIPCORE (Running) - ...(-7, -7) - (1543, 831)Yes 7496 C:\Program Files\Microsoft ...Yes Yes
0000000000060770OT┴VIO GUEDES da GLOBO NEWS...(-7, -7) - (1543, 831)Yes 11756 C:\Program Files\Google\Chr...Yes Yes
0000000000020712N/A (0, 0) - (1536, 824)Yes 8948 C:\Windows\explorer.exe Yes Yes
000000000001070ECalculadora (0, 1) - (320, 543) Yes 3824 C:\Program Files\WindowsApp...Yes Yes
00000000000302E6Calculadora (724, 255) - (1058, 805)Yes 16640 C:\Windows\System32\Applica...Yes Yes
0000000000010666Fax e Scanner do Windows (313, 132) - (1465, 730)Yes 12632 C:\Windows\System32\WFS.exe Yes Yes
00000000000203C8win_APIUIPCORE___v0_m0_p19 (479, 234) - (1393, 824)Yes 8948 C:\Windows\explorer.exe Yes Yes
00000000000101AAN/A (15, 0) - (1536, 3) Yes 8948 C:\Windows\explorer.exe Yes Yes
00000000000101B6N/A (0, 0) - (0, 0) Yes 8948 C:\Windows\explorer.exe Yes Yes
0000000000010190N/A (0, 0) - (0, 0) Yes 8948 C:\Windows\explorer.exe Yes Yes
000000000001018EN/A (0, 0) - (0, 0) Yes 8948 C:\Windows\explorer.exe Yes Yes
000000000003001AMicrosoft Text Input Applic...(0, 0) - (1536, 864)Yes 9212 C:\Windows\SystemApps\Micro...Yes Yes
000000000001015EProgram Manager (0, 0) - (1536, 864)Yes 8948 C:\Windows\explorer.exe Yes Yes

--- Detailed Information for 'Fax e Scanner do Windows' (UIScannerExtractor) ---

Detailed Window Information:
Handle: 0000000000010666
Title: Fax e Scanner do Windows
Position: (313, 132) - (1465, 730)
Visible: Yes
PID: 12632
Process Name: C:\Windows\System32\WFS.exe
Accessible: Yes
Interactive: Yes

--- Controls in 'Fax e Scanner do Windows' Window ---

Handle: 140
Type: TreeView
Text: <TreeView>
Position: (320, 213) - (320, 213)
Enabled: Yes
Visible: No
----------------------------
Handle: 140
Type: Unknown
Text:
Position: (320, 213) - (533, 691)
Enabled: Yes
Visible: No
----------------------------
Handle: 142
Type: TreeView
Text: <TreeView>
Position: (322, 214) - (535, 649)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 142
Type: Button
Text: Fax
Position: (322, 656) - (533, 678)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 142
Type: Button
Text: Digitalizar
Position: (322, 678) - (533, 700)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 142
Type: Unknown
Text:
Position: (322, 655) - (535, 701)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 140
Type: Unknown
Text:
Position: (320, 213) - (536, 703)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 21a
Type: Unknown
Text:
Position: (538, 213) - (1476, 234)
Enabled: Yes
Visible: No
----------------------------
Handle: 21a
Type: Unknown
Text:
Position: (538, 213) - (1476, 343)
Enabled: Yes
Visible: No
----------------------------
Handle: 21c
Type: Unknown
Text:
Position: (540, 214) - (1456, 235)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 21a
Type: Unknown
Text:
Position: (538, 349) - (1457, 703)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 21a
Type: Unknown
Text:
Position: (538, 213) - (1457, 703)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 280
Type: Unknown
Text:
Position: (640, 395) - (1778, 885)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 142
Type: Toolbar
Text: <Toolbar>
Position: (322, 214) - (1458, 239)
Enabled: Yes
Visible: No
----------------------------
Handle: 140
Type: Toolbar
Text: <Toolbar>
Position: (320, 188) - (1456, 217)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 280
Type: Unknown
Text:
Position: (640, 364) - (1778, 394)
Enabled: Yes
Visible: Yes
----------------------------
Handle: 280
Type: StatusBar
Text:
Position: (640, 885) - (1778, 904)
Enabled: Yes
Visible: Yes
----------------------------

C:\agents\SUBMODULES_uiparsercore\win_APIUIPCORE___v0_m0_p19\x64\Debug\win_APIUIPCORE.exe (process 13992) exited with code 0 (0x0).
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
"""

Note that some interface components of this `Windows Fax and Scanner` window are still missing such as `Text` information in the test 3 report in win_APIUIPCORE.cpp.


What I think it is:

Also note that in some interface elements some effort was made to obtain some text from UI elements without text, such as `<TreeView>`, for elements without text. However, you know that the `Fax e Scanner do Windows` window has more interface elements than the buttons identified by the algorithm so far, and it is very possible that the visible elements identified as `Unknown` are elements that were not identified. It is known that the `Fax e Scanner do Windows` window is an app also built in WPF (Windows Presentation Foundation), that is, there is an integration of Win32 interface elements with WPF. The Win32 elements were those that were able to obtain information such as [Handle: 142, Type: Button, Text: Scan], [Handle: 142, Type: Button, Text: Fax], which are window elements that exist in the lower left corner of the `Windows Fax and Scanner` window, they are buttons of a higher hierarchy, since they function as tabs that change the entire screen of this app, of course also within these modes `Fax` e `Digitalizar` existe no canto esquerdo superior um TreeView como um explorador das pastas dentro do objeto `Fax` ou `Digitalizar`. Você entende?

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 these cases where an app can be developed using other frameworks as well, how would you handle this in the Delphi code as well?

After analyzing all this and reflecting, can you see where the error is? How can you fix it? Could you write/rewrite the files to fix this? How would you guarantee in justification that your fix will be effective in achieving the goal of displaying correct data of all window (Win32 and WPF) elements?

Pensamento

Integrating UI automation

The user is mapping out the integration of UI automation to enumerate WPF elements in a Windows application, as standard Win32 enumeration doesn't support WPF.

Integrating UI Automation

The user needs to replace Win32 enumeration with UI Automation to enumerate all WPF elements, ensuring accurate data display for all windows.

Summarizing the solution

The primary issue is relying on EnumChildWindows for WPF elements, leading to data omission. To resolve this, it's essential to incorporate UI Automation for thorough enumeration.

Integrating UI Automation

It's becoming clear that standard Win32 enumerations miss some WPF controls. Adopting a UI Automation strategy for accurate enumeration, particularly in Delphi with TUIAutomation, is crucial.

Thought about handling mixed UI frameworks in WFS por 9 segundos

Resposta

Below is an extensive explanation of why these elements do not appear, where the actual shortcoming is in the approach, and how to fix it—both in C++ and in Delphi. The key point is that WPF-based UI elements are not always standard Win32 child windows, so relying solely on EnumChildWindows or similar Win32 calls will not detect every control. Instead, one must incorporate Microsoft UI Automation (UIA) to capture WPF elements.


1. Understanding the Problem

1.1 Win32 vs. WPF Elements

  • Traditional Win32 controls (e.g., Button, Edit, Static, etc.) can be enumerated with:

    • EnumChildWindows()
    • GetWindowText()
    • GetClassName()
    • etc.
  • WPF controls do not always have their own native HWNDs. Many WPF controls are rendered via DirectX or composition surfaces; at most, you might get a single child window hosting everything, rather than a separate HWND per control.

  • Because of this, enumerating child windows (via EnumChildWindows) might show:

    • A single “host” window, or a small number of Unknown child windows that do not have meaningful text or class name.
    • Possibly some hybrid Win32-based subwindows (like toolbars or tree controls that are still from the older Win32 library).

Hence, your code does detect some old-fashioned Win32 controls in “Fax and Scanner” (the "Button" tabs at the bottom, some "TreeView", some "Toolbar"), but not the WPF-based controls that do not manifest as distinct windows.

1.2 Why Some Controls Appear as “Unknown”

  • If GetClassName(hwnd, ...) returns a class name that is not recognized in your DetermineControlType(), it defaults to ControlType::Unknown.
  • Even if you recognized the class name, many WPF-based UIs simply do not present a separate HWND for each control. You are effectively “seeing” only the top-level hosting window, or other few sub-host windows, not the actual WPF UI tree.

1.3 Where the “Error” Really Is

Strictly speaking, the code does not have a bug in enumerating Win32 child windows; it is simply incomplete for enumerating WPF child elements. The “error” is the assumption that enumerating Win32 child windows would also yield WPF controls. For a real solution, you must combine:

  1. Win32 enumeration (which you already have).
  2. Microsoft UI Automation (UIA) for enumerating WPF elements and retrieving their properties (like text, bounding rectangles, etc.).

2. How to Fix It (Conceptual Overview)

  1. Keep your existing Win32 enumeration code in place (because it does find the real Win32 child windows).
  2. Add a UI Automation–based pass:
    • For each (top-level) window HWND you have, use UIA to get the root IUIAutomationElement.
    • Recursively traverse the UI Automation tree for all children, sub-children, and so on.
    • For each WPF UI element discovered, retrieve its Name, ClassName, IsEnabled, bounding rectangle, etc.
    • Store these in your data structures (ControlInfo or similar) the same way you do for Win32.

2.1 Example UIA Approach in C++

Below is a minimal outline of enumerating WPF elements with UI Automation in C++. (Much of this is similar to what your WPFElementParser code implies, but you must integrate it in your main scanning routine.)

#include <UIAutomation.h> #include <atlbase.h> #include <iostream> #include <string> #include <vector> struct WpfControlInfo { std::wstring name; std::wstring className; bool isEnabled; bool isVisible; RECT bounds; }; // Helper to get text property from UIA element std::wstring GetElementText(IUIAutomationElement* element) { CComVariant varName; if (SUCCEEDED(element->GetCurrentPropertyValue(UIA_NamePropertyId, &varName))) { if (varName.vt == VT_BSTR && varName.bstrVal != nullptr) { return std::wstring(varName.bstrVal); } } return L""; } bool IsElementEnabled(IUIAutomationElement* element) { BOOL enabled = FALSE; if (SUCCEEDED(element->get_CurrentIsEnabled(&enabled))) { return enabled == TRUE; } return false; } bool IsElementOffscreen(IUIAutomationElement* element) { BOOL offscreen = FALSE; if (SUCCEEDED(element->get_CurrentIsOffscreen(&offscreen))) { return offscreen == TRUE; } return false; } // Recursively enumerate children void EnumerateUIAChildren(IUIAutomation* pAutomation, IUIAutomationElement* root, std::vector<WpfControlInfo>& outList) { if (!root) return; // Get root's bounding rectangle, name, class, etc. WpfControlInfo info; info.name = GetElementText(root); // Class name CComVariant varClassName; if (SUCCEEDED(root->GetCurrentPropertyValue(UIA_ClassNamePropertyId, &varClassName))) { if (varClassName.vt == VT_BSTR && varClassName.bstrVal != nullptr) { info.className = std::wstring(varClassName.bstrVal); } } info.isEnabled = IsElementEnabled(root); info.isVisible = !IsElementOffscreen(root); // Get bounding rectangle UiaRect bounding; if (SUCCEEDED(root->get_CurrentBoundingRectangle(&bounding))) { info.bounds.left = (long)bounding.left; info.bounds.top = (long)bounding.top; info.bounds.right = (long)bounding.right; info.bounds.bottom = (long)bounding.bottom; } outList.push_back(info); // Children CComPtr<IUIAutomationTreeWalker> walker; pAutomation->get_ControlViewWalker(&walker); // The first child CComPtr<IUIAutomationElement> child; walker->GetFirstChildElement(root, &child); while (child) { EnumerateUIAChildren(pAutomation, child, outList); // Next sibling CComPtr<IUIAutomationElement> sibling; walker->GetNextSiblingElement(child, &sibling); child = sibling; } } std::vector<WpfControlInfo> ScanWpfElements(HWND hwnd) { std::vector<WpfControlInfo> results; // Initialize COM CoInitialize(nullptr); CComPtr<IUIAutomation> automation; HRESULT hr = CoCreateInstance( CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&automation ); if (FAILED(hr) || !automation) { std::wcerr << L"Failed to create UIAutomation instance.\n"; return results; } // Get the root UIA element for this HWND CComPtr<IUIAutomationElement> rootElement; hr = automation->ElementFromHandle(hwnd, &rootElement); if (SUCCEEDED(hr) && rootElement) { EnumerateUIAChildren(automation, rootElement, results); } // Uninitialize COM CoUninitialize(); return results; }

Then in your main code where you do:

if (faxScannerWindowHandle) { // 1) Do your existing Win32 enumeration (UIScannerNavigator). UIScanner::UIScannerNavigator navigator(faxScannerWindowHandle); navigator.ScanWindow(); // 2) Then do the UIA-based enumeration for WPF. auto wpfControls = ScanWpfElements(faxScannerWindowHandle); // 3) Display results from both. // ... }

Note: The above code is only a sketch for demonstration of how to incorporate UI Automation. You would integrate it into your classes similarly to how you do with ControlProcessor or a new UIAutomationProcessor.


3. How to Adapt This in Delphi

In Delphi, the same principle applies:

  1. Win32:

    • You might already use the VCL’s EnumChildWindows() or Delphi’s Winapi.Windows calls to retrieve child handles, etc.
  2. WPF:

    • Use UI Automation via COM. Delphi can import the Microsoft UI Automation type library (UIAutomationClient/UIAutomationCore) and manipulate IUIAutomation, IUIAutomationElement, etc.
    • Or rely on a third-party Delphi wrapper library for UI Automation.
    • Recursively get child elements from the root IUIAutomationElement for your target window handle.
    • Extract the same properties: Name, ClassName, bounding rectangle, etc.

A pseudo-Delphi example might look like:

uses Winapi.Windows, Winapi.ActiveX, UIAutomationClient_TLB, ..., SysUtils; function EnumerateWpfElements(hWnd: HWND): TArray<TUIAControlInfo>; var Automation: IUIAutomation; Root, Child, Sibling: IUIAutomationElement; Walker: IUIAutomationTreeWalker; List: TList<TUIAControlInfo>; procedure RecursiveEnum(Element: IUIAutomationElement); var ChildElem, NextSibling: IUIAutomationElement; Info: TUIAControlInfo; pName, pClassName: OleVariant; bounding: tagUiaRect; Enabled, Offscreen: BOOL; begin // Name if Succeeded(Element.GetCurrentPropertyValue(UIA_NamePropertyId, pName)) then Info.Name := pName; // ClassName if Succeeded(Element.GetCurrentPropertyValue(UIA_ClassNamePropertyId, pClassName)) then Info.ClassName := pClassName; // Enabled if Succeeded(Element.get_CurrentIsEnabled(Enabled)) then Info.IsEnabled := Enabled; // Offscreen if Succeeded(Element.get_CurrentIsOffscreen(Offscreen)) then Info.IsVisible := not Offscreen; // Bounds if Succeeded(Element.get_CurrentBoundingRectangle(bounding)) then begin Info.Bounds := Rect(Trunc(bounding.left), Trunc(bounding.top), Trunc(bounding.right), Trunc(bounding.bottom)); end; List.Add(Info); // Enumerate children if Assigned(Walker) then begin Walker.GetFirstChildElement(Element, ChildElem); while ChildElem <> nil do begin RecursiveEnum(ChildElem); Walker.GetNextSiblingElement(ChildElem, NextSibling); ChildElem := NextSibling; end; end; end; begin CoInitialize(nil); List := TList<TUIAControlInfo>.Create; try if Succeeded(CoCreateInstance(CLASS_CUIAutomation, nil, CLSCTX_INPROC_SERVER, IID_IUIAutomation, Automation)) and Assigned(Automation) then begin if Succeeded(Automation.ElementFromHandle(hWnd, Root)) and Assigned(Root) then begin Automation.get_ControlViewWalker(Walker); if Assigned(Walker) then RecursiveEnum(Root); end; end; Result := List.ToArray; finally List.Free; CoUninitialize; end; end;

The above is conceptual/pseudocode. The actual code will differ based on your Delphi version and the exact UI Automation imports.


4. Guaranteeing the Fix Works

  • By combining both the Win32 child enumeration and the UI Automation enumeration, you ensure you capture:

    • Standard Win32 controls (via EnumChildWindows).
    • Non-HWND-based WPF controls (via the UI Automation tree).
  • This approach is widely used in tools like Inspect.exe (Microsoft’s official UI debugging tool). Inspect.exe enumerates the UIA tree to show both Win32 and WPF elements in a single tree.

  • Because you directly query the UI Automation layer, you will get “all” the logical elements the accessibility layer sees—including the text for those WPF controls that do not have a Win32 HWND.

4.1 What if Some Controls Are Still Missing?

  • Some sophisticated or custom-drawn controls might not expose all their properties properly to UIA. That is an application-level or vendor-level limitation.
  • For most Microsoft and standard WPF controls, UI Automation will be enough.

5. Summary

  1. Where the error is: The code is not truly “broken,” but it is incomplete. Win32 enumeration alone does not uncover WPF controls, because WPF can be “windowless.”
  2. How to fix: Integrate UI Automation for enumerating WPF-based elements.
  3. Code changes:
    • Keep EnumChildWindows() for standard Win32.
    • Add a new pass that uses UI Automation (C++ or Delphi).
  4. Why it will work: UI Automation is the recommended method for discovering and automating WPF elements. Tools like Inspect.exe, Narrator, etc. use UIA, and you will see your WPF-based “Fax e Scanner” elements appear there.

By doing this, you will see all of the “Fax e Scanner do Windows” UI details in your output—both the Win32 “tabs” and the WPF-based subcomponents.

Compartilhar esta Q&A