#调试与示例
本章提供可直接复制运行的组合示例。每个示例都包含 print 调试输出,并在注释里标注典型控制台输出,方便对照排查。
合规提示:示例中的窗口标题、控件 Name 等字符串均来自 UIA/Win32 枚举,请按实际环境替换
WIN_CLASS/aid/ 会话名。
#通用脚本头(建议每个脚本开头都用)
from chatautox import WinAuto
import time
WIN_CLASS = 'YourMainWndClass' # 改为你的窗口 ClassName
WIN_TITLES = ('目标应用', 'MyApp') # 改为任务栏标题
def bind_auto(mode='uia'):
hwnd = WinAuto.find_main_hwnd(WIN_CLASS, WIN_TITLES, find_window_name=WIN_TITLES[0])
if not hwnd:
raise RuntimeError(f'未找到窗口: class={WIN_CLASS} titles={WIN_TITLES}')
auto = WinAuto(hwnd, mode=mode, delay=0.15)
auto.activate()
time.sleep(0.3)
print(f'[bind] hwnd={hwnd} mode={mode}')
return auto
def pp_ctrl(label, ctrl):
"""打印控件关键属性,定位失败时最有用。"""
if ctrl is None:
print(f'[{label}] ctrl=None')
return
try:
r = ctrl.BoundingRectangle
print(f'[{label}] type={ctrl.ControlTypeName} name={ctrl.Name!r} '
f'aid={ctrl.AutomationId!r} class={ctrl.ClassName!r} rect={r}')
except Exception as e:
print(f'[{label}] 读取属性失败: {e}')
auto = bind_auto()典型输出:
[bind] hwnd=123456 mode=uia#控件树导出
详见 控件获取 — 导出控件树。
auto = bind_auto()
windows = auto.dump_windows_controls(
max_windows=30,
max_lines=800,
same_process_only=True,
)
print(f'[dump] 共 {len(windows)} 个顶级窗口')
for i, w in enumerate(windows):
print(f" [{i}] hwnd={w['hwnd']} class={w['class_name']!r} title={w['title']!r}")
# 在 tree 文本里搜 aid / Name
for needle in ('session_list', 'chat_input', '发送'):
if needle in (w.get('tree') or ''):
print(f' tree 含关键字: {needle!r}')
WinAuto.print_windows_dump(windows) # 完整树打印到控制台典型输出:
[dump] 共 1 个顶级窗口
[0] hwnd=123456 class='YourMainWndClass' title='目标应用'
tree 含关键字: 'session_list'
tree 含关键字: 'chat_input'
[window] hwnd=123456 pid=6789 class="YourMainWndClass" title="目标应用" rect=[...]
WindowControl Name="目标应用" ...
ListControl Aid="session_list" ...#示例 1:find 定位 + 打印属性
auto = bind_auto()
ctrl = auto.find(ctrl_type='EditControl', aid='chat_input', depth=15)
pp_ctrl('chat_input', ctrl)
btn = auto.find(ctrl_type='ButtonControl', name='发送', depth=15)
pp_ctrl('send_btn', btn)典型输出:
[bind] hwnd=123456 mode=uia
[chat_input] type=EditControl name='' aid='chat_input' class='ChatInputBox' rect=(250,550,780,590)
[send_btn] type=ButtonControl name='发送' aid='' class='' rect=(780,550,800,590)若 ctrl=None,说明 aid / depth 不对,回到控件树导出核对。
#示例 2:wait_ctrl 等待出现 + 超时提示
控件可能晚于窗口出现(加载动画、切换会话后输入框重建):
auto = bind_auto()
auto.click_session('示例会话')
time.sleep(0.5)
ctrl = auto.wait_ctrl(
ctrl_type='EditControl',
aid='chat_input',
timeout=5.0,
interval=0.2,
)
if ctrl is None:
print('[wait_ctrl] 超时:5s 内未找到 chat_input')
else:
pp_ctrl('chat_input after wait', ctrl)典型输出:
[bind] hwnd=123456 mode=uia
[chat_input after wait] type=EditControl name='' aid='chat_input' class='ChatInputBox' rect=(...)#示例 3:注册别名 + 链式发送(带每步输出)
auto = bind_auto()
auto.register('input_box', ctrl_type='EditControl', aid='chat_input', depth=15)
auto.register('send_btn', ctrl_type='ButtonControl', name='发送', depth=15)
auto.register('sessions', list_type='ListControl', list_aid='session_list',
item_cls='ChatSessionCell')
print('[register] 已注册: input_box, send_btn, sessions')
# 切会话
auto.click_list_cfg('sessions', '示例会话', exact=True)
time.sleep(0.4)
print('[click_list_cfg] 已点击会话: 示例会话')
# 链式:点击输入框 → 输入 → 回车
text = f'WinAuto 测试 {time.strftime("%H:%M:%S")}'
auto.use('input_box').click().input(text).enter()
print(f'[send] 已发送: {text!r}')
# 验证输入框是否清空(部分客户端发送后仍留字,仅作调试参考)
left = auto.copy(auto.find_cfg('input_box'), mode='uia')
print(f'[verify] 输入框剩余文字: {left!r}')典型输出:
[bind] hwnd=123456 mode=uia
[register] 已注册: input_box, send_btn, sessions
[click_list_cfg] 已点击会话: 示例会话
[send] 已发送: 'WinAuto 测试 14:32:05'
[verify] 输入框剩余文字: ''#示例 4:会话列表遍历 + 未读统计
auto = bind_auto()
sessions = auto.get_sessions(list_aid='session_list')
print(f'[sessions] 共 {len(sessions)} 项')
for i, s in enumerate(sessions[:8]): # 只打印前 8 条
print(f" [{i}] name={s.get('name')!r} unread={s.get('unread')} mute={s.get('mute')}")
unread = auto.get_unread_sessions()
print(f'[unread] 未读会话 {len(unread)} 个:')
for s in unread:
print(f" - {s.get('name')!r} ({s.get('unread')}条)")典型输出:
[sessions] 共 42 项
[0] name='示例会话' unread=0 mute=False
[1] name='张三' unread=3 mute=False
...
[unread] 未读会话 2 个:
- '张三' (3条)
- '工作群' (12条)#示例 5:切换会话 + 读取当前聊天全部消息
auto = bind_auto()
im = auto.im_client(debug=False, auto_listen=False)
im.ChatWith('示例会话')
time.sleep(0.5)
info = im.ChatInfo()
print(f"[ChatInfo] {info}")
msgs = im.GetAllMessage(fetch_sender=False)
print(f'[GetAllMessage] 共 {len(msgs)} 条')
for msg in msgs[-5:]: # 最后 5 条
print(f" [{msg.attr}] type={msg.type} content={msg.content!r}")典型输出:
[bind] hwnd=123456 mode=uia
[ChatInfo] {'chat_name': '示例会话', 'chat_type': 'friend', ...}
[GetAllMessage] 共 128 条
[self] type=text content='你好'
[friend] type=text content='收到'
[self] type=image content='[图片]'
...#示例 6:轮询一轮新消息 + 打印 batch 结构
auto = bind_auto()
im = auto.im_client(debug=False, auto_listen=False)
def on_message(msg):
print(f' [callback] attr={msg.attr} type={msg.type} sender={msg.sender!r} '
f'content={msg.content!r}')
print('[poll] 第 1 次(通常建立基线,msg 为空)')
batch = im.GetNextNewMessage(filter_mute=False, fetch_sender=False, callback=on_message)
print(f" chat_name={batch.get('chat_name')!r} chat_type={batch.get('chat_type')!r} "
f"msg_count={len(batch.get('msg') or [])}")
time.sleep(2)
print('[poll] 第 2 次(有新消息时 msg 非空)')
batch = im.GetNextNewMessage(filter_mute=False, fetch_sender=False, callback=on_message)
msgs = batch.get('msg') or []
print(f" chat_name={batch.get('chat_name')!r} msg_count={len(msgs)}")
for msg in msgs:
print(f" [batch] attr={msg.attr} type={msg.type} content={msg.content!r}")典型输出(第 1 次):
[poll] 第 1 次(通常建立基线,msg 为空)
chat_name='' chat_type='' msg_count=0典型输出(第 2 次,有人发消息后):
[poll] 第 2 次(有新消息时 msg 非空)
[callback] attr=friend type=text sender='张三' content='在吗'
chat_name='张三' msg_count=1
[batch] attr=friend type=text content='在吗'#示例 6b:消息锚点(MakeMessageAnchor → 滚动后 ResolveMessageByAnchor)
处理 GetNextNewMessage 的 msg 时先生成锚点,中间滚动聊天区后仍可按锚点找回同一条(与内部 callback 队列重绑逻辑一致):
auto = bind_auto()
im = auto.im_client(debug=False, auto_listen=False)
_last_anchor = None
def on_message(msg):
global _last_anchor
_last_anchor = im.MakeMessageAnchor(msg)
print(f'[anchor] {_last_anchor["summary"]}')
print(f' key={_last_anchor["key"]!r}')
batch = im.GetNextNewMessage(filter_mute=False, callback=on_message)
if not _last_anchor:
print('[skip] 本轮无新消息')
else:
# 模拟:滚动后 msg.control 可能已失效
time.sleep(1)
msg = im.ResolveMessageByAnchor(_last_anchor)
if msg is None:
print('[resolve] 视口内未找到(可能已滚出屏幕)')
else:
print(f'[resolve] 找回 attr={msg.attr} type={msg.type} content={msg.content!r}')
msg.roll_into_view()典型输出:
[anchor] [friend] text chat='示例会话' content='测试' rid=42.1234567.789...
key=('示例会话', 'text', 'friend', '', '测试', 0, '42.1234567.789...')
[resolve] 找回 attr=friend type=text content='测试'#示例 7:输入框 copy + inspect_clipboard 对比
auto = bind_auto()
auto.click_session('示例会话')
time.sleep(0.4)
ctrl = auto.wait_ctrl(ctrl_type='EditControl', aid='chat_input', timeout=5.0)
if not ctrl:
print('[copy] 未找到输入框,退出')
else:
plain = auto.copy(ctrl, mode='uia') # 纯文字 str
rich = auto.copy(ctrl, mode='uia', items=True) # 含 items 的 dict
clip = auto.inspect_clipboard()
print(f'[copy plain] {plain!r}')
print(f'[copy items] success={rich.get("success")} item_count={rich.get("item_count")}')
for item in rich.get('items') or []:
print(f' item type={item["type"]} content={item["content"]!r}')
print('--- report ---')
print(clip.get('report', ''))典型输出:
[copy plain] '你好,这是测试文字'
[copy items] success=True item_count=1
item type=text content='你好,这是测试文字'
--- report ---
✓ 剪贴板解析成功
文字 x1: 你好,这是测试文字#示例 8:监听里读笔记(推荐)
聊天区笔记不要 find(name='[笔记]'),应通过消息 API 拿到 Message 后调用 get_content():
from chatautox.param import WxResponse
auto = bind_auto()
im = auto.im_client(debug=False, auto_listen=False)
def on_message(msg):
if msg.attr != 'friend' or msg.type != 'note':
return
print(f'[note] 收到笔记: {msg.content!r}')
result = msg.get_content(wait=3)
if isinstance(result, WxResponse):
print('[note] 失败:', result.get('message'))
return
for i, item in enumerate(result):
print(f" [{i}] {item if isinstance(item, dict) else item}")
# 单次:先打开有笔记的会话
im.ChatWith('示例会话')
time.sleep(0.5)
batch = im.GetNextNewMessage(filter_mute=False, callback=on_message)
print(f'[poll] batch msg_count={len(batch.get("msg") or [])}')典型输出:
[note] 收到笔记: '[笔记]'
[0] {'type': 'text', 'content': '标题一行'}
[1] {'type': 'text', 'content': '正文...'}
[2] {'type': 'image', 'content': 'C:\\...\\note_img.png'}
[poll] batch msg_count=1#示例 9:msg.control + 弹窗差集(进阶)
需要手写「点击气泡 → 等新弹窗 → 读内容」时,ctrl 仍来自消息列表:
auto = bind_auto()
im = auto.im_client(debug=False, auto_listen=False)
im.ChatWith('示例会话')
time.sleep(0.5)
msg = next((m for m in im.GetAllMessage() if m.type == 'note'), None)
if msg is None:
print('[popup] 当前聊天无笔记消息')
else:
POPUP_CLASSES = ['Chrome_WidgetWin_0', 'NoteWnd', 'ChatRecordWnd']
before = auto.snapshot_popups(POPUP_CLASSES)
print(f'[popup] 点击前弹窗数: {len(before)}')
auto.click_bubble(msg.control, mode='uia')
popup_hwnd = auto.wait_popup(POPUP_CLASSES, before, timeout=5.0)
if not popup_hwnd:
print('[popup] 5s 内未出现弹窗')
else:
print(f'[popup] 新弹窗 hwnd={popup_hwnd}')
popup = WinAuto(popup_hwnd, mode='uia')
popup.activate()
body = popup.find(ctrl_type='DocumentControl', depth=25)
pp_ctrl('popup body', body)
result = popup.copy_content(body, mode='uia') if body else {'success': False}
print(result.get('report', '无 report'))
popup.close_window()
print('[popup] 已关闭')典型输出:
[popup] 点击前弹窗数: 0
[popup] 新弹窗 hwnd=789012
[popup body] type=EditControl name='' aid='' class='' rect=(...)
✓ 内容复制成功
文字 x3 | 图片 x2
[popup] 已关闭#示例 10:键盘组合 + 剪贴板验证
auto = bind_auto()
ctrl = auto.wait_ctrl(ctrl_type='EditControl', aid='chat_input', timeout=5.0)
auto.click(ctrl)
auto.input('ABC测试123', ctrl=ctrl, paste=True)
print(f'[input] 已输入')
auto.hotkey(0x11, 0x41) # Ctrl+A
auto.hotkey(0x11, 0x43) # Ctrl+C
selected = auto.get_clip()
print(f'[hotkey copy] 剪贴板={selected!r}')
auto.hotkey(0x11, 0x41)
auto.keys('{Delete}', ctrl)
after_clear = auto.copy(ctrl, mode='uia')
print(f'[after clear] 输入框={after_clear!r}')典型输出:
[input] 已输入
[hotkey copy] 剪贴板='ABC测试123'
[after clear] 输入框=''#示例 11:type_and_send + 滚动翻页组合
auto = bind_auto()
auto.click_session('示例会话')
time.sleep(0.4)
ctrl = auto.find(ctrl_type='EditControl', aid='chat_input')
auto.type_and_send('第一条', ctrl, mode='uia')
print('[type_and_send] 第一条已发')
time.sleep(0.3)
auto.type_and_send('第二条', ctrl, mode='uia')
print('[type_and_send] 第二条已发')
# 聊天区翻页(坐标按窗口大小调整)
auto.page_up(times=2)
print('[scroll] 上翻 2 页')
auto.page_down(times=2)
print('[scroll] 下翻 2 页')#示例 12:记事本完整流程(通用窗口)
不依赖 IM 客户端,适合验证 WinAuto 环境是否正常:
from chatautox import WinAuto, MODE_UIA
auto = WinAuto.from_main_hwnd('Notepad', '无标题 - Notepad', mode=MODE_UIA)
if not auto.hwnd:
# 已有文件时标题可能带文件名
auto = WinAuto.from_main_hwnd('Notepad', '无标题 - 记事本', mode=MODE_UIA)
auto.activate()
time.sleep(0.3)
print(f'[notepad] hwnd={auto.hwnd}')
auto.register('editor', ctrl_type='EditControl', depth=5)
auto.use('editor').click().input('Hello from WinAuto').sleep(0.3)
print('[notepad] 已输入文字')
saved = auto.keys('{Ctrl}s', auto.find_cfg('editor'), mode='uia')
clip_check = auto.copy(auto.find_cfg('editor'), mode='uia')
print(f'[notepad] 当前内容={clip_check!r}')典型输出:
[notepad] hwnd=456789
[notepad] 已输入文字
[notepad] 当前内容='Hello from WinAuto'#示例 13:一键调试函数(复制到项目里反复用)
def debug_winauto_snapshot(auto, aids=('session_list', 'chat_input')):
"""绑定后快速检查:窗口信息 + 关键 aid 能否 find。"""
print('=' * 50)
print(f'hwnd={auto.hwnd} mode={auto.mode}')
for aid in aids:
c = auto.find(aid=aid, depth=20)
pp_ctrl(aid, c)
sessions = auto.get_sessions(list_aid='session_list') if 'session_list' in aids else []
if sessions:
print(f'sessions: total={len(sessions)} first={sessions[0].get("name")!r}')
unread = auto.get_unread_sessions() if 'session_list' in aids else []
if unread is not None:
print(f'unread sessions: {len(unread)}')
print('=' * 50)
auto = bind_auto()
debug_winauto_snapshot(auto)典型输出:
==================================================
hwnd=123456 mode=uia
session_list type=ListControl name='' aid='session_list' ...
chat_input type=EditControl name='' aid='chat_input' ...
sessions: total=42 first='示例会话'
unread sessions: 2
==================================================#与 macro_helper 的关系
macro_helper 中的函数式接口(ctrl_click、ctrl_input、get_hwnd 等)底层均委托给 WinAuto。Macro 与 OO 风格可混用,调试时建议统一加 print:
# 函数式(macro_helper)
from chatautox.auto.macro_helper import get_hwnd, ctrl_click, ctrl_input
WIN_TITLE = '…' # 目标窗口实际标题
hwnd = get_hwnd(win_title=WIN_TITLE)
print(f'[macro] hwnd={hwnd}')
ctrl_click(hwnd, name='发送')
print('[macro] ctrl_click 完成')
# 面向对象(推荐)
from chatautox import WinAuto
auto = WinAuto(hwnd)
auto.click(name='发送')
print('[winauto] click 完成')#调试排查速查
| 现象 | 建议 |
|---|---|
find 返回 None | 先 dump_windows_controls,核对 aid/Name/depth |
| 点击无反应 | 试 mode='uia' 或 mode='mouse';activate() 后再点 |
| 中文输入乱码/失败 | input(..., paste=True) 或 mode='uia' |
| 组合键无效 | post 模式用 hotkey(0x11, 0x43),勿用 keys('{Ctrl}c') |
聊天区 find [笔记] 找不到 | 气泡滚动 / 多条同名 |
| 消息轮询始终空 | 首帧建基线正常 |
| 弹窗读不到 | ClassName 与 dump 不一致 |
更多 API 参数见各专题页:查找与点击、剪贴板、消息列表、消息处理指南。
#API 参考
#调试
#✨dump_windows_controls
导出可见顶级窗口 UIA 树。
windows = auto.dump_windows_controls(same_process_only=True)
WinAuto.print_windows_dump(windows)参数:
| 参数名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| max_windows | int | 30 | 最多导出窗口数 |
| max_lines | int | 800 | 每个控件树最大行数 |
| same_process_only | bool | True | 是否仅导出同进程窗口 |
返回值:
- 类型:
list[dict] - 描述:每项
{hwnd, pid, class_name, title, rect, tree}