控件获取 - chatautox 开发文档

控件获取

扩展开发时,第一步通常是拿到目标控件的定位信息NameAutomationIdClassNameControlType),再编写点击、输入等自动化逻辑。本章介绍 chatautox 提供的控件获取方式。

合规提示:下文示例中的窗口标题、控件 Name 等字符串,均来自 UIA/Win32 枚举结果,仅作技术演示。详见 开发文档 — 商标与合规说明

推荐流程

1. 导出控件树(dump / walk)

2. 在树文本或 control_browser 中找到目标行

3. 提取 Name / Aid / Class / ctrl_type

4. 用 WinAuto.find() 验证能定位到控件

5. 编写 click / input / get_list_items 等业务逻辑

方式一:导出控件树(调试首选)

WinAuto — 通用任意窗口

from chatautox import WinAuto

TARGET_CLASS = 'YourMainWndClass'  # 改为你的窗口 ClassName
TARGET_TITLES = ('目标应用', 'MyApp')
hwnd = WinAuto.find_main_hwnd(TARGET_CLASS, TARGET_TITLES, find_window_name=TARGET_TITLES[0])
auto = WinAuto(hwnd)

windows = auto.dump_windows_controls(
    max_windows=30,       # 最多导出几个顶级窗
    max_lines=800,        # 每个窗最多几行控件
    same_process_only=True,  # 仅同进程窗口
)
WinAuto.print_windows_dump(windows)

输出示例(窗口标题 title= / 控件 Name= 为系统返回值,非 chatautox 品牌用语):

[window] hwnd=12345 pid=6789 class="YourMainWndClass" title="…" rect=[0, 0, 800, 600]
  WindowControl Name="…" Class="YourMainWndClass" Aid="" Rect=(0,0,800,600)
    PaneControl Name="" Class="" Aid="main_window" Rect=(0,0,800,600)
      ListControl Name="" Class="" Aid="session_list" Rect=(0,60,250,600)
        ListItemControl Name="示例会话" Class="ChatSessionCell" Aid="" Rect=(0,60,250,100)
      EditControl Name="" Class="ChatInputBox" Aid="chat_input" Rect=(250,550,780,590)
      ButtonControl Name="发送" Class="" Aid="" Rect=(780,550,800,590)

树文本每行格式:

{缩进}{ControlType} Name="..." Class="..." Aid="..." Rect=(left,top,right,bottom)

内置 IM 客户端 — 含子进程窗(高级)

部分 IM 客户端的笔记、合并消息等弹窗可能在独立进程。通过 auto.im_client() 获得客户端后,可调用 DumpWindowsControls(参数与 WinAuto.dump_windows_controls 类似):

im = auto.im_client(debug=False)

# 常用:主进程窗口
windows = im.DumpWindowsControls(
    max_windows=30,
    max_lines=800,
    same_process_only=True,
)

若需 restrict_pid / extra_hwnds(笔记子进程、不可见 HWND),走内部 API:

# 指定 PID + 强制包含额外 HWND
windows = im._api.DumpWindowsControls(
    max_windows=30,
    max_lines=1000,
    same_process_only=True,
    restrict_pid=note_pid,
    extra_hwnds=[note_hwnd],
)
for w in windows:
    print(f'hwnd={w["hwnd"]} class={w["class_name"]} title={w["title"]}')
    print(w.get('tree', ''))

GenericWnd — 结构化遍历(保留 ctrl 对象)

control_browser 控件浏览器底层使用此接口,返回带 UIA 控件对象 的列表:

from chatautox.ui.window import GenericWnd

wnd = GenericWnd(hwnd=12345)
controls = wnd.dump_controls(max_windows=50, max_lines=800)

for item in controls:
    print(item['label'])       # 格式化标签行
    print(item['ctrl'])        # UIA 控件对象,可直接传给 WinAuto.click
    print(item['cx'], item['cy'])  # 中心坐标
    print(item['rect'], item['depth'])

walk_controls()dump_controls() 类似,可单独遍历当前绑定窗口:

items = wnd.walk_controls(max_depth=30, max_lines=800, time_budget=5.0)

解析树文本为结构化列表

若已有 dump_windows_controls 的返回值,可解析为扁平控件列表(不含 ctrl 对象):

from chatautox.ui.window import GenericWnd

parsed = GenericWnd.parse_controls(windows)
for p in parsed:
    print(p['label'], p['cx'], p['cy'], p.get('sender', ''))

方式二:按属性查找单个控件

WinAuto.find — 推荐

在已绑定 HWND 的窗口下,按 UIA 属性查找第一个匹配控件:

from chatautox import WinAuto

auto = WinAuto(hwnd)

ctrl = auto.find(
    ctrl_type='EditControl',    # UIA 类型名,空则泛型 Control
    name='发送',                 # Name 精确匹配
    aid='chat_input',           # AutomationId
    cls='ChatInputBox',         # ClassName
    depth=10,                   # 搜索深度,默认 10
)

if ctrl is None:
    print('未找到控件')
else:
    print(ctrl.Name, ctrl.AutomationId, ctrl.ClassName)
    auto.click(ctrl)

查找参数可任意组合,至少提供一个非空条件。常用 ctrl_type

ctrl_type说明
Control任意控件(最宽泛)
ButtonControl按钮
EditControl输入框
ListControl列表
ListItemControl列表项
TextControl文本
MenuItemControl菜单项
PaneControl面板

WinAuto.wait_ctrl — 等待控件出现

弹窗、加载动画等场景,轮询直到控件出现或超时:

ctrl = auto.wait_ctrl(
    name='另存为',
    ctrl_type='WindowControl',
    timeout=5.0,      # 最长等待秒数
    interval=0.2,     # 轮询间隔
)

macro_helper — 函数式

流程编排器生成代码常用:

from chatautox.auto.macro_helper import get_hwnd, find_ctrl, ctrl_find

WIN_TITLE = '…'  # 替换为目标窗口在任务栏显示的实际标题
hwnd = get_hwnd(win_title=WIN_TITLE, win_class='')
ctrl = find_ctrl(win_title=WIN_TITLE, name='发送', ctrl_type='ButtonControl')
ctrl = ctrl_find(hwnd, aid='chat_input', ctrl_type='EditControl')

方式三:批量查找(多个匹配项)

AppWindow — 返回列表

from chatautox.auto.uibase import AppWindow

win = AppWindow.from_hwnd(hwnd)

# 按 ClassName 查找所有匹配
cells = win.find_by_class('ChatSessionCell', depth=15)

# 按 Name 查找(exact=True 精确,False 模糊包含)
items = win.find_by_name('示例会话', exact=True, depth=15)

# 按 AutomationId
inputs = win.find_by_automationid('chat_input', depth=10)

# 原生 UIA FindAll
root = win.root_control
all_btns = [c for c in root.FindAll(maxDepth=10)
            if c.ControlTypeName == 'ButtonControl']

遍历子控件

list_ctrl = auto.find(ctrl_type='ListControl', aid='session_list')
children = list_ctrl.GetChildren()

for child in children:
    print(child.Name, child.ClassName, child.AutomationId)

方式四:列表项批量获取

适用于会话列表、联系人列表等 ListControl,一次性获取所有项的完整属性:

items = auto.get_list_items(
    list_type='ListControl',
    list_aid='session_list',       # 列表 AutomationId
    item_cls='ChatSessionCell',    # 列表项 ClassName 过滤
    name_field='name',             # 提取名称字段:name / aid / text
    unread_pattern=r'\[(\d+)条\]',  # 未读数正则(可选)
    mute_keyword='消息免打扰',       # 免打扰关键词(可选)
    depth=15,
)

for item in items:
    print(item['name'])            # 显示名称
    print(item['unread'])          # 未读数
    print(item['mute'])            # 是否免打扰
    print(item['ctrl'])            # UIA 控件对象
    print(item['BoundingRectangle'])  # 位置
    print(item['AutomationId'])

get_list_items 返回的每项包含 _dump_ctrl_props 导出的完整属性:

字段说明
Name / Text控件 Name 原文
AutomationIdAutomationId
ClassNameClassName
ControlTypeName控件类型
BoundingRectangle{left, top, right, bottom, width, height}
IsEnabled / IsOffscreen状态
ctrlUIA 控件对象(可直接操作)
name / unread / mute列表语义字段

IM 客户端会话列表快捷方法(以 chatautox 默认适配目标为例):

sessions = auto.get_sessions(list_aid='session_list', item_cls='ChatSessionCell')
unread   = auto.get_unread_sessions()
found    = auto.find_session('示例会话')

方式五:control_browser 可视化获取

项目内置 control_browser / control_browser_lite 可视化调试面板,适合不熟悉代码的控件定位:

基本操作

  1. 启动控件浏览器,拾取或绑定目标窗口(类名 + 标题,可重复执行、自动查找 HWND)
  2. F5 刷新控件树
  3. 在左侧树中选中目标控件
  4. 右键 → 控件详情 查看完整属性
  5. 右键 → 生成代码 获取 WinAuto 调用代码

面板顶部可切换点击模式(UIA / PostMessage / 物理),与 WinAutomode 参数对应。

control_browser_lite:流程录制与代码执行

lite 版右侧提供代码编辑框运行输出面板:

  1. 拾取窗口后自动生成绑定头(WIN_CLASS / WIN_TITLES / auto = WinAuto(...)
  2. 点击、发送、键盘操作,或「所有操作 → 消息列表处理」等,会追加步骤到编辑器,不覆盖已有代码
  3. ▶ 执行:在独立子进程中运行编辑器脚本(-u 无缓冲),print / wxlog 调试日志实时显示在「运行输出」
  4. ⏹ 停止:终止子进程(含 GetNextNewMessage 长时间阻塞时也可停止);执行中 ▶ 禁用

「消息列表处理 → GetNextNewMessage 循环 / GetNewMessage 循环」模板含 auto.activate() + while True 轮询。首帧通常不交付历史消息,属正常行为。

消息 API 通过 auto.im_client() 创建(默认内置 IM 适配器;绑定控件浏览器时会自动查找 IM 主窗口),步骤模板会自动补上 im = auto.im_client(...)

所有操作 → 注册别名(给控件起名字)

一句话:先把「怎么找到某个控件」记在一个短名字里,后面写流程只写名字,不用反复抄 aidnamedepth

可以把它理解成:给按钮、输入框、会话列表起外号,脚本里用外号操作。

什么时候用

情况要不要注册别名
只点一次、试一下就完不用,直接「鼠标操作 → 左键单击」即可
同一条流程里多次用到输入框、发送钮、会话列表建议用
脚本很长,想改一处定位、全局生效建议用

和「列表操作」的区别:

  • 列表操作:代码里直接写 list_aid='session_list',适合演示、写一次
  • 注册别名:先 register('sessions', list_aid='session_list', ...),后面统一写 'sessions',适合多步骤流程

在 control_browser_lite 里怎么用

  1. 拾取目标窗口,左侧控件树 F5 刷新
  2. 打开 「所有操作」 → 分类选 「注册别名」
  3. 按下面顺序操作(每一步点 「生成代码」 追加到右侧编辑器):
步骤选什么操作你要做什么
注册控件在树里选中输入框/按钮 → 生成 auto.register('c', ...) → 把 'c' 改成好记的名字,如 'input_box'
注册列表(可选)参数填 list_aid,如 'session_list' → 别名 'lst' 表示左侧会话列表
别名点击 / 别名输入 / 别名发消息不再选控件,代码里只用 'input_box' 等名字
代理链式发消息(可选)一行写完:auto.use('input_box').click().input('你好').enter()

注册列表 时参数示例:list_aid='session_list'(左侧会话列表示例,以你控件树里看到的 AutomationId 为准)。

注册控件 前务必在树里选中目标;步骤预览里会出现 target = auto.find(...) + register(...)

生成代码长什么样

注册一个输入框:

# 查找聊天输入框(Aid 来自控件树)
target = auto.find(aid='chat_input', depth=12)
auto.register('input_box', ctrl_type='EditControl', aid='chat_input', depth=12)

注册左侧会话列表:

auto.register('sessions',
    list_type='ListControl',
    list_aid='session_list',
    item_cls='ChatSessionCell',
    unread_pattern=r'\[(\d+)条\]',
    mute_keyword='消息免打扰')

后面只用别名(短、好读):

auto.input_cfg('input_box', '你好')
auto.send_msg_cfg('input_box', '你好')      # 点击 + 输入 + 回车

auto.use('sessions').click_item('示例会话')
auto.use('sessions').click_unread(0)        # 点第一个未读会话

# 链式写法(和上面等价,一行串起来)
auto.use('input_box').click().input('你好').enter()

「所有操作」里各项是干什么的

操作名作用
注册控件当前选中控件的定位写入别名
注册列表列表的 list_aid 等写入别名
读取配置 cfg查看别名里存了什么:auto.cfg('input_box')
别名查找ctrl = auto.find_cfg('input_box')
别名点击 / 输入 / 发消息用别名点击、打字、发消息
别名取列表 / 点列表项 / 取未读 / 点未读列表专用,不用每次写 list_aid
代理链式 ×auto.use('别名').click().input(...) 链式写法

API 参数表见 WinAuto — 控件别名

完整小流程示例(发送一条消息)

auto = WinAuto(hwnd, mode='uia')
auto.activate()

# ① 注册(流程开头做一次即可)
auto.register('input_box', ctrl_type='EditControl', aid='chat_input', depth=12)

# ② 使用别名
auto.use('input_box').click().input('你好,这是测试').enter()

若还要先点某个会话,可先 注册列表 + 代理点列表项,或直接用「列表操作 → 点击会话」。


读取控件属性

找到控件后,可直接读 UIA 属性或借助封装:

# 原生 UIA 属性
ctrl.Name
ctrl.AutomationId
ctrl.ClassName
ctrl.ControlTypeName
ctrl.BoundingRectangle   # left/top/right/bottom
ctrl.IsEnabled
ctrl.Exists(maxSearchSeconds=0)

# ElementAction 包装(uibase)
from chatautox.auto.uibase import ElementAction

elem = ElementAction(ctrl, hwnd=hwnd)
print(elem.name, elem.automation_id, elem.bbox, elem.center)
texts = elem.get_all_text()   # 子控件所有 Name

完整示例:从导出到操作

from chatautox import WinAuto

TARGET_CLASS = 'YourMainWndClass'
TARGET_TITLES = ('目标应用', 'MyApp')
hwnd = WinAuto.find_main_hwnd(TARGET_CLASS, TARGET_TITLES, find_window_name=TARGET_TITLES[0])
auto = WinAuto(hwnd, mode='uia')

# 1. 导出控件树,找到 chat_input 的 Aid
WinAuto.print_windows_dump(auto.dump_windows_controls(max_lines=500))

# 2. 验证定位
input_ctrl = auto.find(ctrl_type='EditControl', aid='chat_input')
assert input_ctrl is not None, '输入框未找到'

# 3. 注册别名,后续脚本复用
auto.register('input_box', ctrl_type='EditControl', aid='chat_input')
auto.register('send_btn', ctrl_type='ButtonControl', name='发送')

# 4. 操作
auto.use('input_box').click().input('你好').enter()
# 或
auto.send_msg_cfg('input_box', '你好')

常见问题

find 返回 None

  • 检查 depth 是否足够(复杂界面建议 15~20)
  • 确认 Name 是否与控件树中完全一致(含空格、换行)
  • 弹窗可能在子进程,需用 DumpWindowsControls(restrict_pid=..., extra_hwnds=...)
  • 控件可能尚未渲染,改用 wait_ctrl(timeout=5)

控件树过大 / 卡顿

  • 减小 max_lines(如 500~800)
  • 设置 same_process_only=True 过滤无关窗口
  • 复杂界面建议 max_windows=30

Name 含换行

列表项 Name 常含多行文本,get_list_items 会自动取第一行作为 name 字段;手动查找时建议用 aidcls 定位。

上一页WinAuto 总览下一页构造与绑定