- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
导读
本文以 Windows-classic-samples 仓库中的 UIAutomationDocumentClient 示例 为骨架,系统讲解如何利用微软 UI Automation(UIA)的 Text / TextRange 控件模式,从一个命令行工具中读取其他应用窗口正在显示的文档内容——包括标题、各级标题、批注(annotation)、当前选区、可见文本以及光标(caret)位置等。读完本文,你将掌握 UIA 文本模式的完整调用链、命令行的构建与运行方式,以及如何基于 UiaDocumentClient.cpp 的源码理解并复用这套跨进程文档内容提取方案。
示例是什么:一个“文档内容客户端”
UIAutomationDocumentClient 是一个纯命令行工具(UiaDocumentClient.exe),其核心能力是:通过 Microsoft UI Automation 控件模式,访问另一个应用窗口中正在展示的文档内容。它不要求目标文档应用做任何特殊集成,只要该窗口支持Text(文本)控件模式即可——例如记事本(Notepad)这类文本控件天然支持该模式。
按仓库 README 的表述,示例展示了对窗口内容的多种检索能力,包括:
- 标题(heading)与文档各段落的样式文本;
- 批注(annotation,如文档评论及其作者、时间、类型);
- 当前选区(selection)与可见文本(visible text);
- 整篇文档范围(document range);
- 光标位置及其相关的插入符状态信息。
从源码结构看,这个示例同时依赖TextPattern2(IUIAutomationTextPattern2)及其扩展特性:批注范围(RangeFromAnnotation)、光标范围(GetCaretRange),这些是普通 Text 模式不具备的能力,因此在 UiaDocumentClient.cpp 中明确使用GetCurrentPatternAs(UIA_TextPattern2Id, ...)获取文本模式对象。
核心技术:Text 与 TextRange 控件模式
UI Automation 的文本体系由两个层面的接口组成,示例分别使用了它们:
| 接口 / 概念 | 在本示例中的用途 |
|---|---|
IUIAutomationTextPattern | 获取文档范围(DocumentRange)、选区(GetSelection)、可见范围(GetVisibleRanges)等基础文本信息 |
IUIAutomationTextPattern2 | 在 Text 模式基础上扩展出批注(RangeFromAnnotation)与光标(GetCaretRange)能力,是示例实际获取的模式对象 |
IUIAutomationTextRange | 表示一段文本范围,支持GetText、FindAttribute、Move、MoveEndpointByRange、CompareEndpoints、ExpandToEnclosingUnit、Clone等操作 |
IUIAutomationTextRangeArray | 返回多段范围时使用,例如多选区或可见文本可能不止一段 |
示例在 TextPatternExplorer::GetRange 中演示了四类范围的获取方式:
- 文档范围:
_textPattern->get_DocumentRange(&uiaRange); - 光标范围:
_textPattern->GetCaretRange(&isActive, &uiaRange),同时返回该光标是否处于活动状态; - 选区:
_textPattern->GetSelection(&uiaRangeArray); - 可见文本:
_textPattern->GetVisibleRanges(&uiaRangeArray)。
其中选区和可见范围以“范围数组”形式返回。源码注释特别指出,对于复杂文本模式这些属性返回的是数组,示例为保持简洁只取第一个元素(源码 L1041-L1061),实战中若要完整处理多选区/多可见区域,应当遍历整个数组。
源码结构与关键类设计
示例的全部逻辑都位于单个源文件 UiaDocumentClient.cpp(约 1240 行),配套工程文件为 UiaDocumentClient.sln 与 UiaDocumentClient.vcxproj。整个程序围绕三个封装类展开:
Comment:批注元素的包装
Comment 类 包装一个支持批注的元素,负责:
- 获取
AnnotationPattern(GetCurrentPatternAs(UIA_AnnotationPatternId, ...)),用于读取批注的类型名、作者、日期时间; - 调用
textPattern->RangeFromAnnotation(...)把批注还原为一段文本范围; - 打印批注概要(仅名称)或完整信息(类型/作者/时间/内容)。
Range:文本范围的包装
Range 类 是示例中最核心的封装,它持有IUIAutomationTextRange并提供多种检索能力:
Print:输出当前范围文本(概要模式下截断为 40 字符并附字符总数,完整模式下全量输出,读取上限为maxLength = 10000字符);FindAndPrintHeaders/FindAndPrintStyle:通过FindAttribute(UIA_StyleIdAttributeId, ...)逐段查找 Title、Heading1、Heading2、Heading3 样式的文本,并用MoveEndpointByRange推进“剩余范围”的起点继续向后搜索;FindAndPrintComments/FindCommentsInRange/WalkForCommentsInRange:以TextUnit_Format为单位逐段移动范围,读取UIA_AnnotationObjectsAttributeId属性收集批注元素,并通过CompareElements去重,限制最多收集maxComments = 25条;PrintCaretInfo:读取UIA_IsActiveAttributeId、UIA_CaretPositionAttributeId、UIA_CaretBidiModeAttributeId、UIA_SelectionActiveEndAttributeId四个属性,并通过GetBoundingRectangles输出光标范围的屏幕坐标矩形。
值得注意的是,示例专门处理了 UI Automation 中两个保留值:ReservedNotSupportedValue(提供方不支持该属性)和ReservedMixedAttributeValue(范围内属性混合),从源码 WalkForCommentsInRange 可看到它对这两类返回值做了区分处理,这也是在实际开发中处理文本属性时容易踩坑的地方。
TextPatternExplorer:交互主循环
TextPatternExplorer 类 负责把上述能力组织成一个交互式命令行界面:读取用户输入、解析命令、维护“当前活动范围”(currentRange)与“当前活动批注”(currentComment)两个状态,并在每次输入前打印当前范围的概要信息。
构建示例
README 给出的构建前提是:
警告:本示例要求 Microsoft Visual Studio 2013 或更高版本(任意 SKU),且无法在 Visual Studio Express 2013 for Windows 中编译。
按 README 的步骤构建:
- 启动 Visual Studio,选择File → Open → Project/Solution;
- 进入示例目录下的 C++ 目录,双击解决方案文件 UiaDocumentClient.sln;
- 按F7(Visual Studio 2013 中是 F6),或使用Build → Build Solution编译。
仓库配套的 ReadMe.txt 还补充了命令行构建方式:
msbuild UiaDocumentClient.sln从 UiaDocumentClient.vcxproj 可以确认工程的关键配置:
- 支持Debug/Release × Win32/x64四种配置;
- 平台工具集为
V110(对应 VS2011/2012 时代工具集,但 README 声明可用 VS2013+ 打开); - 字符集为Unicode,子系统为Console(命令行程序);
- 链接依赖
uiautomationcore.lib,编译警告级别为 Level4 且将警告视为错误(TreatWarningAsError=true),因此编译时若遇到警告会直接报错,这也是本项目代码风格严谨的原因之一。
运行示例与两种目标窗口选择方式
README 描述的典型运行流程:
- 在一个支持 UI Automation Text 控件模式的应用(如记事本)中打开一篇文档;
- 在示例安装目录下运行
UiaDocumentClient.exe(如<install_root>\UI Automation Document Content Client Sample\C++\Debug目录);若在 Visual Studio 中运行,按F5(带调试)或Ctrl+F5(不带调试),也可从Debug菜单选择Start Debugging / Start Without Debugging; - 程序启动后3 秒内,把鼠标光标移动到第 1 步打开的文档窗口上——程序会获取鼠标所在位置的元素;
- 随后程序列出命令清单,用命令即可检索文档内容。
从 wmain 入口 可以看到更完整的运行方式细节,示例支持零参数或一个参数两种模式:
UiaDocumentClient [hwnd] Explore the text pattern for the specific [hwnd] (in hex) If no [hwnd] is supplied, it will get the element under the mouse pointer.- 不带参数:先打印 “Getting element at cursor in 3 seconds...”,
Sleep(3000)等待 3 秒后用GetCursorPos获取鼠标坐标,再调用_automation->ElementFromPoint(pt, &element)得到鼠标下的元素; - 带一个十六进制 HWND 参数:例如
UiaDocumentClient 0x0006093A。程序用swscanf_s(argv[1], L"%x", ...)解析十六进制窗口句柄,先通过IsWindow校验句柄合法性,再调用_automation->ElementFromHandle(hwnd, &element)获取元素。窗口句柄可以通过 Spy++、Inspect 等工具获取。
得到目标元素后,程序调用 FindTextPatternElement:用CreatePropertyCondition(UIA_IsTextPattern2AvailablePropertyId, VARIANT_TRUE, ...)构造“支持 TextPattern2”的属性条件,再对元素及其全部后代执行FindFirst(TreeScope_Subtree, ...)深度搜索,找到第一个支持 TextPattern2 的元素。这正是“记事本窗口顶层元素不直接支持文本模式、但内部编辑控件支持”这类场景能正常工作的原因。
命令行交互命令详解
程序启动后即进入TextPatternExplorer的交互循环,输入任意命令(不区分大小写)即可操作。README 与源码 Welcome 帮助输出 中的完整命令如下:
| 命令 | 功能 | 对应实现 |
|---|---|---|
help | 显示帮助信息 | Welcome() |
print | 打印当前活动范围的完整文本 | Range::Print(false) |
document | 把活动范围设置为整篇文档 | get_DocumentRange |
selection | 把活动范围设置为当前选区 | GetSelection |
visible | 把活动范围设置为当前可见文本 | GetVisibleRanges |
headers | 在活动范围内查找并打印 Title、Heading 1/2/3 样式文本 | Range::FindAndPrintHeaders |
comments | 列出活动范围内所有批注(编号 + 名称概要) | Range::FindAndPrintComments |
comment {n} | 把活动批注设置为活动范围内第 n 条批注 | SelectComment/Range::SelectComment |
commentrange | 把活动范围设置为活动批注对应的文本范围 | RangeFromComment/RangeFromAnnotation |
printcomment | 打印活动批注的完整信息(类型、作者、日期时间、内容) | Comment::Print(false) |
caret | 获取光标范围并打印光标专属信息 | Range::PrintCaretInfo |
exit | 退出程序 | — |
几个值得注意的交互细节:
- 命令解析通过 GetCommand 完成:将输入小写化后与预置命令前缀逐条
wcsncmp比较,因此comment 3这类带参数命令也能正确匹配; caret命令在执行前会Sleep(2000)暂停 2 秒并提示用户“如需聚焦文本控件请在此期间操作”,随后获取的光标范围还会打印其活动状态、位于行首/行尾/行中、双向文本(BiDi)方向、选区活动端以及光标外接矩形坐标;comments输出的批注编号可以直接配合comment {n}使用,进而通过commentrange把活动范围跳到该批注对应的正文位置——这是文本模式“批注 ↔ 文本范围”双向映射的完整演示。
底层运行环境与约束
- 初始化顺序:示例在
wmain中先CoInitialize(NULL)初始化 COM,再CoCreateInstance(__uuidof(CUIAutomation8), ..., CLSCTX_INPROC_SERVER, ...)创建 UIA 自动化对象(源码 L1162-L1175)。注意这里使用的是CUIAutomation8(Windows 8 版本自动化对象),而不是更早的CUIAutomation,这与示例依赖 TextPattern2 等 Windows 8 新增能力一致。 - 系统要求:README 明确标注 Client 为Windows 8.1、Server 为Windows Server 2012 R2;仓库说明同时指出,示例为 Windows 8.1 / Windows Server 2012 R2 + Visual Studio 2013 创建,多数情况下可在后续版本上不加修改地运行。
- 工具链限制:必须使用 VS2013 或更高版本(任意 SKU),Express 版不可编译。
- 可移植性提示:源码中的
CUIAutomation8、UIA_TextPattern2Id、UIA_AnnotationPatternId、UIA_CaretPositionAttributeId等属于较新的 UIA API,若要在旧系统上运行,需要确认目标平台提供对应支持。
总结与延伸
UIAutomationDocumentClient 是理解 UI Automation文本模式家族最直接的样例:它把IUIAutomationTextPattern2的文档范围、选区、可见范围、批注、光标等全部关键入口,浓缩进一个无需任何 UI 依赖的命令行工具中,并给出了完整的错误处理(每个 COM 调用失败都会打印 HRESULT)与资源释放(所有权式封装、SysFreeString、VariantClear、CoTaskMemFree等)范本。
如果希望进一步深入,可以对照同一仓库中的 UIAutomationDocumentProvider(文档提供方)与 UIAutomationFragmentProvider(片段提供方)示例,从“客户端消费”与“服务端提供”两个视角交叉理解文本模式的双向契约;本示例源码 UiaDocumentClient.cpp 与工程文件 UiaDocumentClient.vcxproj 均可直接作为后续二次开发的起点。
- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
相关推荐
Windows-classic-samples 源码精读:基于 UiaDisconnectProvider 实现 DLL 中 UI Automation Provider 的安全卸载
Windows classic samples 源码精读:基于 UiaDisconnectProvider 实现 DLL 中 UI Automation Pro
示例工程Windows-classic-samples 实战:用 Appx Packaging API 实现 App 包(.appx)内容解压
Windows classic samples 实战:用 Appx Packaging API 实现 App 包(.appx)内容解压 本篇技术指南以 Samp
示例工程Windows-classic-samples 中的 AmbientLightAware 示例:基于 Windows 传感器平台读取环境光并自适应 UI 字体的 MFC 实战
Windows classic samples 中的 AmbientLightAware 示例:基于 Windows 传感器平台读取环境光并自适应 UI 字体的
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考