WPF Control Detection Issues
Creado el: 12 de enero de 2025
Creado el: 12 de enero de 2025
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 {
textenum 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)
text```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?
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.
Traditional Win32 controls (e.g., Button
, Edit
, Static
, etc.) can be enumerated with:
EnumChildWindows()
GetWindowText()
GetClassName()
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:
Unknown
child windows that do not have meaningful text or class name.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.
GetClassName(hwnd, ...)
returns a class name that is not recognized in your DetermineControlType()
, it defaults to ControlType::Unknown
.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:
HWND
you have, use UIA to get the root IUIAutomationElement
.Name
, ClassName
, IsEnabled
, bounding rectangle, etc.ControlInfo
or similar) the same way you do for Win32.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.)
cpp#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:
cppif (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 newUIAutomationProcessor
.
In Delphi, the same principle applies:
Win32:
EnumChildWindows()
or Delphi’s Winapi.Windows
calls to retrieve child handles, etc.WPF:
UIAutomationClient
/UIAutomationCore
) and manipulate IUIAutomation
, IUIAutomationElement
, etc.IUIAutomationElement
for your target window handle.Name
, ClassName
, bounding rectangle, etc.A pseudo-Delphi example might look like:
pascaluses 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.
By combining both the Win32 child enumeration and the UI Automation enumeration, you ensure you capture:
EnumChildWindows
).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
.
EnumChildWindows()
for standard Win32.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.