如何使用UIA提升遍历窗口所有控件的速度?
遍历UIA控件提升搜索速度的方案
一、先解决你的条件无效问题
你当前的非空Name条件只排除了Name明确为空字符串的控件,但很多无名称的控件其实是未设置Name属性(即属性不存在),这类控件不会被你的条件过滤。正确的条件应该同时检查「Name属性存在」且「Name不为空」:
// 条件1:Name属性存在 CComPtr<IUIAutomationCondition> pNameExistsCondition; pAutomation->CreatePropertyCondition(UIA_NamePropertyId, CComVariant(), PropertyConditionFlags_PropertyExists, &pNameExistsCondition); // 条件2:Name不等于空字符串 CComPtr<IUIAutomationCondition> pNameNotEmptyCondition; pAutomation->CreatePropertyCondition(UIA_NamePropertyId, CComVariant(L""), &pNameNotEmptyCondition); CComPtr<IUIAutomationCondition> pNameNotEmptyNotCondition; pAutomation->CreateNotCondition(pNameNotEmptyCondition, &pNameNotEmptyNotCondition); // 组合条件:属性存在 且 非空 CComPtr<IUIAutomationCondition> pFinalCondition; pAutomation->CreateAndCondition(pNameExistsCondition, pNameNotEmptyNotCondition, &pFinalCondition);
用这个组合条件替代原来的Not条件,才能真正过滤掉无名称的控件,减少后续需要处理的元素数量。
二、除缓存外的其他提速方案
1. 一次性获取所有符合条件的后代控件
你当前用递归遍历TreeScope_Children,每次调用FindAll都会触发跨进程通信(UIA是跨进程架构),多次调用会产生额外开销。改为直接用TreeScope_Descendants一次性获取所有符合条件的后代,减少跨进程调用次数:
// 直接从根元素获取所有符合条件的后代控件 CComPtr<IUIAutomationElementArray> pAllElements; pRootElement->FindAll(TreeScope_Descendants, pFinalCondition, &pAllElements); if (pAllElements) { int count; pAllElements->get_Length(&count); for (int i = 0; i < count; ++i) { CComPtr<IUIAutomationElement> pElement; pAllElements->GetElement(i, &pElement); if (pElement) { BSTR bstrName; if (SUCCEEDED(pElement->get_CurrentName(&bstrName)) && bstrName) { elementsList.insert(bstrName); SysFreeString(bstrName); } } } }
2. 优化数据存储结构
你用std::set<std::wstring>存储名称,每次插入都是O(log n)的开销。如果只是需要去重的名称列表,可以先用std::vector<std::wstring>存储所有名称,最后再排序去重,整体效率更高:
// 先用vector存储 std::vector<std::wstring> tempList; // ... 遍历元素时: tempList.push_back(bstrName); // 最后去重 std::sort(tempList.begin(), tempList.end()); auto last = std::unique(tempList.begin(), tempList.end()); tempList.erase(last, tempList.end()); // 再转成set或者直接使用vector elementsList.insert(tempList.begin(), tempList.end());
3. 过滤不必要的控件类型
根据你的业务场景,某些控件类型(如Pane、Group等)通常不会有需要关注的名称,可以提前过滤:
// 创建控件类型过滤条件,排除不需要的类型 std::vector<int> excludedTypes = {UIA_PaneControlTypeId, UIA_GroupControlTypeId}; CComPtr<IUIAutomationCondition> pTypeCondition; for (int typeId : excludedTypes) { CComPtr<IUIAutomationCondition> pSingleTypeCond; pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, CComVariant(typeId), &pSingleTypeCond); if (!pTypeCondition) { pTypeCondition = pSingleTypeCond; } else { CComPtr<IUIAutomationCondition> pNewCond; pAutomation->CreateOrCondition(pTypeCondition, pSingleTypeCond, &pNewCond); pTypeCondition = pNewCond; } } CComPtr<IUIAutomationCondition> pExcludeTypeCond; pAutomation->CreateNotCondition(pTypeCondition, &pExcludeTypeCond); // 和之前的Name条件组合 CComPtr<IUIAutomationCondition> pFinalCondition; pAutomation->CreateAndCondition(pNameAndNotEmptyCond, pExcludeTypeCond, &pFinalCondition);
4. 减少属性获取的开销
因为已经通过条件过滤了有非空Name的控件,所以在获取Name时可以省略空值判断,直接处理即可(仍要检查HRESULT确保调用成功):
BSTR bstrName; if (SUCCEEDED(pElement->get_CurrentName(&bstrName))) { tempList.push_back(bstrName); SysFreeString(bstrName); }
5. 简化递归终止逻辑
你的代码里有parsedCount > 2000就返回的逻辑,如果这是测试代码,正式场景下移除它避免提前终止遍历;如果需要限制数量,可放在一次性获取后处理,减少递归中的判断开销。
内容的提问来源于stack exchange,提问作者Jabu
相关产品推荐
相关产品推荐

