来源说明:本文根据 AutoHotkey 论坛 jeeswg 的 jeeswg's Acc tutorial (Microsoft Active Accessibility) (MSAA) 整理翻译,并结合 AHK66 站内已有 Acc、UIA、控件自动化内容重新组织。原帖本身更像 Acc 资料索引和问答集合,本文不会机械搬运一长串跳转链接,而是把最适合上手的概念、写法和排查路线整理成一篇实用入门。

Acc 是 Microsoft Active Accessibility,也常被称为 MSAA。它原本是 Windows 辅助功能体系的一部分,用来让读屏软件、自动化工具读取窗口、控件和界面元素的信息。放到 AHK 里看,Acc 的价值很直接:当 Window SpyControlGetTextControlClick 这些普通控件方法拿不到内容时,Acc 往往还能读到元素名称、值、状态、位置,甚至触发默认动作。

但 Acc 不是万能钥匙。我的建议是:标准控件先用 Control 系列;非标准但还能暴露 MSAA 信息的界面,再用 Acc;更现代的 Chromium、WPF、UWP、Electron 或复杂软件界面,再考虑 UIA、浏览器控制、图色识别等路线。站内已有 AHK 自动化到底该用哪种方法控件自动化入门UIA 窗口控制辅助增强库,这篇主要补上 Acc/MSAA 的思路。

一、什么时候值得用 Acc

下面这些情况比较适合尝试 Acc:

  • 窗口里看得到文字,但 WinGetTextControlGetText 拿不到。
  • 按钮、选项卡、列表项没有稳定的 ClassNN,普通控件命令不好操作。
  • 想判断某个元素是否聚焦、选中、隐藏、可点击。
  • 想获取鼠标下元素的名称、值、位置,然后再决定点击或右键。
  • 想读取某些浏览器、资源管理器、播放器、聊天软件界面中的可访问性信息。

不建议一开始就把 Acc 当成所有窗口控制的首选。普通 Edit、Button、ComboBox、SysListView32 这类标准控件,Control 系列更简单、更稳定。Acc 更像是标准控件路线失效后的补位工具。

二、准备工具:Acc.ahk 和 AccViewer

原帖最重要的两个工具入口是:

站内也已经有整理好的 Acc 资源,可以优先看 Acc窗口控制辅助增强库Acc库获取窗口自动化高级用法示例Acc获取鼠标下的文字

使用方式通常是把 Acc.ahk 放进脚本同目录,或者放到 AHK 的 Lib 目录,然后在脚本里引用。

#Requires AutoHotkey v1.1
#Include <Acc>

F1::
try
{
    oAcc := Acc_ObjectFromPoint(childId)
    name := oAcc.accName(childId)
    value := oAcc.accValue(childId)
    role := oAcc.accRole(childId)
    state := oAcc.accState(childId)

    MsgBox, % "Name: " name
        . "`nValue: " value
        . "`nRole: " role
        . "`nState: " state
        . "`nChildID: " childId
}
catch e
{
    MsgBox, % "Acc 读取失败:" e.Message
}
return

这个示例适合先验证 Acc 能不能读到鼠标下的元素。能读到名称和值,后面再考虑按路径稳定定位;读不到,说明目标软件可能没有暴露 MSAA 信息,或者需要换 UIA、图色、浏览器接口等方案。

三、AccViewer 应该怎么看

AccViewer 可以理解成 Acc 版的 Window Spy。你把十字准星拖到目标元素上,它会显示元素的名称、值、角色、状态、坐标、父子关系等信息。原帖里反复强调:AccViewer 能看到的信息,理论上就可以通过 Acc 函数读取。

最容易让新手卡住的是 Acc 路径。类似 1.2.3 的路径,意思是:从窗口或控件对应的起点对象开始,进入第 1 个子元素,再进入它的第 2 个子元素,再进入它的第 3 个子元素。也就是说,Acc 路径不是屏幕坐标,而是界面元素树里的“孩子编号路线”。

用路径读取对象的基本写法如下:

F2::
WinGet, hWnd, ID, A
vAccPath := "1.2.3" ; 这里换成 AccViewer 里看到的路径

try
{
    oAcc := Acc_Get("Object", vAccPath, 0, "ahk_id " hWnd)
    MsgBox, % "名称:" oAcc.accName(0) "`n值:" oAcc.accValue(0)
}
catch e
{
    MsgBox, % "读取失败:" e.Message
}
return

有些 AccViewer 版本会显示逗号路径,例如 4,1,1,而 Acc_Get() 一般使用点号路径,例如 4.1.1。如果复制后不能用,先把逗号换成点号。

四、Acc_Get 常用命令怎么理解

Acc_Get() 是 AHK Acc 库里的封装函数,它帮你从窗口标题、句柄、路径和 ChildID 定位到可访问性对象,再读取对应属性或执行动作。常见命令可以这样理解:

Cmd 含义 常见用途
Object 返回 Acc 对象本身 后续手动调用 accNameaccValueaccLocation
Name 读取 accName 按钮文字、菜单项名称、列表项名称
Value 读取 accValue 编辑框内容、地址栏内容、部分状态值
Action 读取默认动作说明 查看元素能否点击、打开、切换
DoAction 执行默认动作 类似点击按钮或触发菜单项
Keyboard 读取快捷键提示 少数菜单或按钮会提供快捷键信息

如果只是读文本,可以先用 NameValue。如果要做复杂判断,建议先取 Object,再自己调用属性和方法。

五、ChildID:为什么有时不是 0

Acc 里经常会遇到 ChildID。简单理解:0 通常表示对象本身,正整数可能表示对象内部的某个子项。比如某些 ListView、TreeView、菜单项或浏览器元素,焦点和选择项可能不是一个完整对象,而是通过 ChildID 表示。

所以同样是 accName(),有时要写 accName(0),有时要写 accName(childId)。用 Acc_ObjectFromPoint(childId) 时,函数会把鼠标下子项编号写到 childId 变量里,这就是为什么前面的示例要把它传给 accName

六、焦点和选中项:accFocus / accSelection

原帖后面的问答里有一个很实用的点:accFocus 可以判断某些元素是否获得焦点,但返回值不一定直观。对于浏览器地址栏这类元素,可能返回 0 表示对象本身有焦点;对于列表控件,它可能返回当前焦点项的索引。

下面是原帖里提到的浏览器地址栏聚焦判断思路,路径会随着浏览器版本和界面布局变化,仅适合当作写法参考:

FirefoxUrlBarFocused(hWnd)
{
    vAccPath := "application.tool_bar3.combo_box1.editable_text"
    ; vAccPath := "4.27.4.2"
    oAcc := Acc_Get("Object", vAccPath, 0, "ahk_id " hWnd)
    return oAcc.accFocus = "0" ? 1 : 0
}

ChromeUrlBarFocused(hWnd)
{
    vAccPath := "pane.client.client2.client2.client.grouping.editable_text"
    ; vAccPath := "4.1.2.2.3.5.2"
    oAcc := Acc_Get("Object", vAccPath, 0, "ahk_id " hWnd)
    return oAcc.accFocus = "0" ? 1 : 0
}

如果你处理的是列表控件,accFocusaccSelection 的返回值可能是索引,也可能是一个可枚举对象。原帖给了一个读取焦点项和选中项的基础写法:

q:: ; 获取当前控件的焦点项 / 选中项
WinGet, hWnd, ID, A
ControlGetFocus, vCtlClassNN, % "ahk_id " hWnd
ControlGet, hCtl, Hwnd,, % vCtlClassNN, % "ahk_id " hWnd

oAcc := Acc_ObjectFromWindow(hCtl)
vFoc := oAcc.accFocus
vSel := oAcc.accSelection

if IsObject(vSel)
{
    oSel := vSel, vSel := ""
    while oSel.Next(vValue, vType)
        vSel .= (A_Index=1 ? "" : ",") vValue
    oSel := ""
}

MsgBox, % "焦点项:" vFoc "`r`n选中项:" vSel
oAcc := ""
return

这里也能看出一个实战原则:Acc 经常需要结合普通窗口命令一起用。先用 WinGetControlGetFocus 拿到窗口或控件句柄,再把句柄交给 Acc。

七、获取位置后再点击或右键

Acc 本身可以执行默认动作,例如 accDoDefaultActionAcc_Get("DoAction", ...)。但它不等于完整鼠标模拟,尤其是右键菜单、拖拽、复杂点击时,往往还是要先用 Acc 取元素位置,再用普通鼠标命令或 ControlClick 处理。

Acc_Location(Acc, ChildId=0, ByRef Position="")
{
    try Acc.accLocation(ComObj(0x4003, &x:=0)
        , ComObj(0x4003, &y:=0)
        , ComObj(0x4003, &w:=0)
        , ComObj(0x4003, &h:=0)
        , ChildId)
    catch
        return

    x := NumGet(x, 0, "int")
    y := NumGet(y, 0, "int")
    w := NumGet(w, 0, "int")
    h := NumGet(h, 0, "int")
    Position := "x" x " y" y " w" w " h" h
    return {x:x, y:y, w:w, h:h}
}

拿到坐标以后,可以点击元素中心:

F3::
oAcc := Acc_ObjectFromPoint(childId)
pos := Acc_Location(oAcc, childId)

if IsObject(pos)
{
    Click, % pos.x + pos.w//2, % pos.y + pos.h//2
}
return

如果目标窗口支持后台点击,也可以把位置换算到窗口客户区后配合 ControlClick。这部分可以结合站内 控件自动化入门窗口控制怎么选 一起看。

八、批量枚举:JEE_AccGetTextAll 的用途

原帖还提到一个很有用的诊断思路:当 AccViewer 给出的路径不准确,或者你不知道窗口里到底有哪些 Acc 元素时,可以用枚举函数把窗口里的文本和路径全部扫出来。jeeswg 的相关帖子是 Acc: get text from all window/control elements

q:: ; 枚举活动窗口里的 Acc 文本和路径
WinGet, hWnd, ID, A
Clipboard := JEE_AccGetTextAll(hWnd, "`r`n")
MsgBox, % "已写入剪贴板"
return

注意:JEE_AccGetTextAll 不是 Acc.ahk 原生函数,需要从对应帖子或整理版代码中引入。它更适合排查和定位路径,不建议在高频循环里一直跑,因为完整枚举复杂窗口可能会比较慢。

九、常用 IAccessible 方法速查

Acc.ahk 封装的是 MSAA 的 IAccessible 接口。下面这些方法名在脚本里经常出现:

方法 / 属性 用途
accName 读取元素名称,常见于按钮文字、菜单项、列表项。
accValue 读取元素值,常见于编辑框、地址栏、部分进度或状态元素。
accRole 读取元素角色,例如按钮、文本、列表、组合框。
accState 读取状态,例如聚焦、选中、不可用、隐藏等。
accLocation 读取元素屏幕坐标和宽高。
accFocus 读取当前焦点对象或焦点子项。
accSelection 读取选中项,多个选中项时可能返回对象。
accDefaultAction 读取默认动作说明。
accDoDefaultAction 执行默认动作,常见效果类似点击或打开。
accParent 获取父对象。
accChild 获取子对象。
accChildCount 获取子元素数量。

更完整的接口说明可以看微软文档里的 IAccessible interface。不过对于 AHK 实战来说,先掌握 accNameaccValueaccStateaccLocationaccDoDefaultAction 已经能解决不少问题。

十、常见坑和判断路线

1. AccViewer 能看到,不代表路径永远稳定。软件更新、布局改变、语言切换、窗口缩放,都可能让路径变化。重要脚本要尽量结合窗口类名、标题、控件句柄、元素名称一起判断。

2. Acc 路径有时要从控件句柄开始,而不是主窗口句柄。如果从主窗口开始路径很深,可以先用 ControlGet 拿到目标控件的 HWND,再对这个控件调用 Acc。

3. accValue 不一定能写入。有些元素能读值,但不允许设置值;有些元素看起来像编辑框,实际只暴露名称。写入文本时仍要考虑 ControlSetText、Send、剪贴板、COM、浏览器接口等方案。

4. accDoDefaultAction 不等于万能点击。它触发的是元素声明的默认动作。有些元素没有默认动作,有些默认动作和你想象的不一样。需要右键、拖动、复杂菜单时,通常还是取坐标后用鼠标命令。

5. Chrome 类浏览器可能需要开启可访问性。如果 Acc 读不到网页内容,可以尝试用 --force-renderer-accessibility 启动 Chrome,或在 chrome://accessibility/ 检查相关开关。但网页自动化长期来看更推荐浏览器控制、JS 注入或 HTTP 请求,站内可看 浏览器自动化怎么选

6. Acc 和 UIA 不要混为一谈。Acc/MSAA 更老,很多传统软件和部分系统界面很好用;UIA 更新,面对现代界面更有优势。站内 利用ACC和UIA后台获取文本UIA库获取窗口自动化高级用法示例 可以配合看。

十一、我的推荐学习顺序

如果你是第一次接触 Acc,我建议按这个顺序走:

  1. 先用 AHKInfo 和 Window Spy 判断有没有标准控件、类名、控件句柄。
  2. 能用 ControlGetTextControlClick 的,优先用普通控件命令。
  3. 普通控件命令拿不到时,用 AccViewer 拖十字准星看目标元素。
  4. 先用 Acc_ObjectFromPoint 读鼠标下元素,确认 Acc 能读到信息。
  5. 再用 Acc_Get("Object", path, childId, "ahk_id " hWnd) 固定路径。
  6. 需要批量查路径时,用 JEE_AccGetTextAll 这类枚举工具辅助定位。
  7. 如果 Acc 路径非常不稳定,或者目标是现代复杂界面,再转 UIA、浏览器控制或图色路线。

这样学 Acc 会轻松很多:不要一开始就记 IAccessible 的全部方法,也不要急着写通用封装。先做到“能识别一个元素、能读一个值、能定位一个按钮”,再慢慢扩展到焦点、选中项、位置、默认动作。

站内延伸

外部参考

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。