环境与版本
控件树被藏起来是这套方案最硬的约束。先判断自己属于哪种情况,再谈别的。
这是所有 UIA 方案的共同前提
微信 4.1 起,主窗口在 GPU 合成渲染下会退化成一个“纯画布”, UIA 读不到任何子控件。这时脚本找不到会话列表、消息列表、输入框。
这与用不用本库无关 —— 任何基于 UIA 的方案都受同一条约束。
实测现象
| 微信版本 | 状态 | 顶层窗口 ClassName | UIA 子树 |
|---|---|---|---|
| 4.1.15.13 | 已登录、GPU 合成渲染 | Qt51514QWindowIcon |
只有 2 个节点 |
| 4.1.15.13 | 登录页 | mmui::LoginWindow |
正常,10 个节点 |
| 4.1.8.107 | 已登录主界面 | mmui::MainWindow |
完整控件树 |
最要命的一点:同一个窗口句柄的 ClassName 会在
mmui::MainWindow(有完整子树)和 Qt51514QWindowIcon(0 个子节点)之间来回切换。
也就是说树是会「掉」的。
最常见的元凶:微信自己更新
同一天实测记录:
| 时间 | 顶层 UIA ClassName | 控件树 |
|---|---|---|
| 20:10 | mmui::MainWindow |
完整,收发跑通 |
| 20:38 | 自动更新落地 | — |
| 21:11 | Qt51514QWindowIcon |
children = 2 |
⚠ 判断版本别看 mtime 或文件大小:更新器会保留归档时间戳, 两份不同版本的
Weixin.exe实测大小和时间戳可以完全一样。 要用win32api.GetFileVersionInfo()读,或看运行进程的版本。
为什么会这样
微信 4.0 之后换成自绘 UI 框架,两条机制叠加:
- 按需暴露 —— 默认只上报顶层窗口,检测到系统里有「无障碍客户端」 (讲述人 / NVDA 这类屏幕阅读器)时才加载完整 UIA Provider。
- 渲染后端切换 —— Qt 在显卡驱动、DPI、多屏、远程桌面、是否管理员运行
等条件下会切到 ANGLE/DXGI 合成渲染,一旦进入合成模式主窗口就变成
Qt51514QWindowIcon。
两条都是环境相关的,所以有人机器上一直正常,有人一升级就全废。
判断自己属于哪种情况
python -m wx4auto.selftest # 卡住就跑下一步
python tools\probe_a11y.py # 诊断报告
输出里看三件事:
- 顶层窗口 ClassName 是
mmui::MainWindow还是Qt51514QWindowIcon MMUIRenderSubWindowHW下面有没有子节点- 有没有任何
mmui::前缀的元素
对策(按推荐顺序试)
1. 确保「无障碍客户端」在场
启动微信之前就让系统知道有屏幕阅读器。
最简单:Win + Ctrl + Enter 打开 Windows「讲述人」,再启动微信;
确认控件树出来后可以把讲述人关掉。
2. 强制 Qt 走软件渲染
给微信加环境变量再启动:
set QT_ANGLE_PLATFORM=software
set QT_OPENGL=software
set QT_QUICK_BACKEND=software
"D:\ruanjian\Weixin_Old\Weixin\Weixin.exe"
仓库里有现成脚本:tools\launch_weixin_uia.cmd(双击即可)。
3. 关掉高 DPI 兼容性缩放
右键 Weixin.exe → 属性 → 兼容性 → 更改高 DPI 设置 →
勾选「替代高 DPI 缩放行为」并选 系统。
4. 避免远程桌面 / 多屏混合 DPI
远程会话是 Qt 切换到合成渲染的高发场景。尽量在本机、单屏、100% 缩放下跑。
5. 以管理员身份运行脚本
UIA 客户端与目标进程权限不一致时,Windows 会隐藏部分控件层。
实测记录:1、2 两项单独试过都还没能让主窗口变回
mmui::MainWindow(登录窗口一直正常),说明还叠加了渲染后端的问题。这部分属于环境调优,与代码无关。
库自身怎么处理
不假设树一直在,每次操作前体检:
wx._main.tree_exposed() # -> bool,树在不在
wx._main.explain_tree_missing() # -> 不在时打印可执行的对策
树不在时明确报错,绝不静默失败 —— 否则你会看到“接口不报错但全是空结果”, 那是最难查的一类现象。
另外两种“看不见”,要分开治
| 状态 | 判断依据 | 能不能自动救 |
|---|---|---|
| 最小化 | IsIconic(hwnd) == True |
✅ 还原即可 |
| 被收进托盘(点 × / Esc) | IsWindowVisible == 0 且没最小化 |
✅ 走微信自己的托盘逻辑(点托盘图标) |
| 窗口可见但树为空 | tree_exposed() == False |
等 1~2 分钟,Qt 会自己重建 Provider |
被收进托盘后,任何 Win32 手段都救不回来。
ShowWindow(SW_SHOW/SW_RESTORE)、SetWindowPos(SWP_SHOWWINDOW)、
SetForegroundWindow、AttachThreadInput 组合、RedrawWindow 全都实测无效。
其中 SetWindowPos(SWP_SHOWWINDOW) 最坑:它能让 IsWindowVisible 变 True,
但 Qt 收不到“窗口该显示了”,结果是白屏 + 控件树永远为空的窗口 —— 比隐藏还糟。
所以库里绝不会这么做。
适配新版本微信
微信更新后如果某些功能失效,九成是控件类名变了:
python -m wx4auto probe --out dump.txt
在 dump 里找到对应控件,把新类名补进 wx4auto/controls.py 的候选列表(新的放前面)。
每一项都是候选列表,按顺序回退,所以补名字不会破坏旧版本兼容。
界面文案在 languages.py,中/繁/英三份。