
简介一套面向C#开发者的微信自动化桌面工具源码依托Winform界面与FlaUI库实现对微信客户端UI的自动操控解决定时发送消息、关键词自动回复及群聊机器人等重复性操作场景适合有一定C#基础、希望入门Windows UI自动化或构建个人微信助手的开发者。资源包共365个文件约47.8MB包含127个dll运行库、55个cs源码、18个json配置及30余个编译缓存类文件另有少量exe与数据库资源整体结构覆盖主程序、FlaUI封装、定时任务、消息处理和配置文件等模块。压缩包内除可运行的Winform工程外还提供项目文件、编译中间产物与数据库相关脚本便于直接打开、编译和二次开发。已有659人学习下载适合参考其UI元素定位、消息监听和定时触发等实现思路也可作为企业内训或个人自动化办公的起步模板。需要特别提醒的是微信官方不开放自动化接口使用前应评估账号风险并遵守平台规则。1. 没有官方接口的微信自动化为什么用 FlaUI 而不是抓协议微信桌面端没有开放消息收发接口要做定时提醒、值班通知、巡检播报这类工具抓协议有合规风险第三方库又大多维护停滞。更务实可控的路线是把微信当成一个普通 Windows 桌面程序用 UI 自动化去驱动。这套 Winform 加 FlaUI 的方案就是基于 Windows UI Automation 对微信客户端做界面级的定位、点击和输入不碰协议、不依赖特定版本。它能解决给指定联系人发消息、读取会话最新消息、批量触发内部提醒这类需求适合做内部 RPA、自动化测试和巡检脚本的 .NET 开发者参考。全文从环境搭建讲起中间展开微信元素定位与消息发送最后落在 Winform 集成和稳定性排坑上。这套思路不只适用于 Winform迁到 WPF 甚至 .NET MAUI 的 Windows 端FlaUI 部分几乎不用改。2. FlaUI 环境搭建与最小闭环先拿到微信主窗口2.1 为什么选 FlaUI从原生 UIA 到封装库的取舍Windows 自带 UIAutomation 托管接口不是不能用而是用起来太原始。想查一个文本框得先构造 Condition 再调 FindFirst还要手动处理 COM 异常和超时代码里六成是基础设施、两成才是业务。FlaUI 把这一层封装成了接近直觉的 API窗口就是 Window、控件就是 AutomationElement、查找就 FindDescendant、点击就 Click。对 Winform 开发者来说上手成本几乎为零。FlaUI 同时支持 UIA2 和 UIA3 两种模式。UIA2 走托管实现兼容老框架但遇到 Chromium 渲染的界面经常拿不到子节点UIA3 走微软的 COM 原生实现性能和子元素覆盖率都更好。微信 Windows 客户端的会话列表和聊天区有相当一部分是自绘加 Chromium 混合渲染实测 UIA3 能拿到的文本节点明显更多所以默认选 FlaUI.UIA3。对比项UIA2UIA3实现方式托管代码COM 原生子元素覆盖率老框架控件好新渲染层更好查找性能一般更好延迟更低微信场景推荐度低高选型结论直接引 FlaUI.Core 和 FlaUI.UIA3 两个 NuGet 包目标框架用 .NET Framework 4.8Winform 项目模板直接建不需要额外配置原生依赖。如果你拿 WPF 工程跑FlaUI 层的代码原样搬唯一要改的是界面线程的调度方式。2.2 最小 Demo附加到微信进程并读取主窗口NuGet 装完包第一个要验证的不是发消息而是能不能稳定拿到微信主窗口。这一步是后面所有操作的地基拿不到窗口后面全是空谈。using FlaUI.Core; using FlaUI.UIA3; using System.Diagnostics; using System.Linq; Process[] procs Process.GetProcessesByName(WeChat); if (procs.Length 0) { Console.WriteLine(微信未启动先手动登录一次); return; } using (var automation new UIA3Automation()) { var app FlaUI.Core.Application.Attach(procs[0]); Window mainWindow app.GetMainWindow(automation, TimeSpan.FromSeconds(10)); if (mainWindow null) { Console.WriteLine(拿不到主窗口可能还停在登录页); return; } Console.WriteLine($主窗口: {mainWindow.Title}); }代码分三步先按进程名找微信没启动就提示再用 Attach 附加到已存在进程而不是 Launch 重新拉起最后 GetMainWindow 带 10 秒超时。三个参数值得单独说进程名是 WeChat 不是 Weixin区分大小写写错直接拿不到Attach 和 Launch 的差别在于一个操作已有实例、一个自己拉起新实例工具里我统一用 Attach把微信的启动交给用户避免多开时附加到错误进程超时时间给 10 秒足够覆盖从启动到登录完成的空窗期。提示登录完成后主窗口标题通常包含当前微信昵称如果停在扫码登录页标题可能是微信登录或微信。判断窗口是否就绪看 Title 比看 MainWindow 是否为 null 更可靠。在 Winform 里我一般把这段放在 Form_Load 之后的一个 Task 里界面先弹出来后台去探测微信结果写到状态栏 Label。用户打开工具就能看到微信已连接或微信未就绪比黑匣子式的静默失败直观得多。同时注意 using 块不要嵌套在每次操作的循环里一个进程生命周期内 automation 实例保持全局单例否则句柄会越积越多跑一晚上内存涨几百 MB 就是这么来的。3. 微信 UIA 元素定位实战从搜索框到发送消息的完整链路3.1 先搞懂微信的 UIA 元素树长什么样写定位条件之前必须知道微信主窗口在 UIA 视角下是什么结构。用 FlaUI 自带工具或者自己写一个递归打印控件树的小程序把微信主窗口 dump 一遍大致长这样Window 微信 (昵称) ├── Pane │ ├── Edit 搜索 ← 搜索入口 │ ├── List ← 会话列表 │ │ ├── ListItem 文件传输助手 │ │ ├── ListItem 项目群 │ │ └── ListItem 某联系人 │ └── Pane ← 右侧聊天区 │ ├── Text 聊天信息 │ └── Edit ← 消息输入框这个结构并不严谨不同版本差异会很大但主脉络稳定搜索框是 Edit、会话列表是 List、会话项是 ListItem、输入框是 Edit。定位时我优先用 Name 属性因为它和界面上显示的文字一致微信换版本后 AutomationId 经常变Name 反而稳定得多。控制类型加 Name 双条件是最稳的组合只靠 AutomationId 的代码基本属于给自己埋坑。3.2 查找与等待封装带超时重试的定位方法FlaUI 的 FindDescendant 找不到元素时返回 null继续往下走就是空引用异常。所以要先封装一个带重试的查找方法把找不到就睡 100 毫秒再试写死在公共代码里。public static AutomationElement FindOrWait(Window window, FuncAutomationElement, bool condition, int timeoutMs 3000) { var sw Stopwatch.StartNew(); while (sw.ElapsedMilliseconds timeoutMs) { var el window.FindDescendant(condition); if (el ! null) return el; Thread.Sleep(100); } return null; }方法的两个参数很关键condition 是委托回调里写你自定义的匹配规则timeoutMs 是超时上限默认 3 秒。这种方式把轮询等待变成了公共能力后面任何一步找不到元素都能直接复用。注意 FindDescendant 是全窗口深度遍历高频调用会消耗性能所以我只在每次动作前调用一次拿到元素立刻操作不做二次缓存。我一般还会配套一个等条件成立的 WaitFor 方法逻辑类似只是不用等元素而是等任意布尔条件。这两个方法加起来后面所有步骤都用条件等待代替固定 Thread.Sleep慢机器上不会超时快机器上不会空等。3.3 搜索联系人、进入会话、发送消息的完整链路给指定联系人发消息微信没有全局跳转接口只能模拟人工操作点搜索框、输入备注名、等结果、点第一个匹配项、然后发消息。public void SendToContact(string contactName, string message) { // 1. 聚焦搜索框并输入联系人名 var searchBox FindOrWait(window, el el.ControlType ControlType.Edit el.Name.Contains(搜索)); if (searchBox null) throw new TimeoutException(搜索框未找到); searchBox.Click(); Keyboard.Type(contactName); // 2. 等待搜索结果出现点第一个匹配项 var item FindOrWait(window, el el.ControlType ControlType.ListItem el.Name.Contains(contactName), 5000); if (item null) throw new TimeoutException($联系人 {contactName} 不在结果里); item.AsListBoxItem().Select(); // 3. 等会话标题变为目标联系人确认已经进入会话 WaitFor(() window.Name.Contains(contactName), 3000); // 4. 定位消息输入框输入内容并回车发送 var input FindOrWait(window, el el.ControlType ControlType.Edit el.Properties.ClassName.Value.Contains(Edit)); input.Focus(); Keyboard.Type(message); Keyboard.Type(VirtualKeyShort.ENTER); // 5. 校验编辑框已清空为空说明发送成功 if (!string.IsNullOrEmpty(input.Patterns.Value.Pattern.Value.Value)) throw new Exception(编辑框未清空发送可能失败); }五个步骤里藏着几个容易翻车的细节。第一搜索框输入用 Keyboard.Type 而不是 SetValue真实键盘序列才能触发微信的搜索逻辑SetValue 直接赋值经常输入进去了但搜索不触发。第二搜索结果里可能同时出现联系人、群聊和聊天记录FindDescendant 返回的是元素树深度优先的第一个匹配项通常就是联系人分类下的目标。第三进入会话后校验窗口标题包含联系人名这一步能拦掉点到了聊天记录结果的误操作。第四发送完成的判据是编辑框 Value 被清空微信发送成功会清空输入区这个信号比任何日志都可靠。这段链路跑通等于拿到了微信自动化的核心能力。后面无论是改成遍历多个联系人还是做成定时任务都是在 SendToContact 外面套循环和调度。到这里 Winform 还没真正集成下一步就是把这段代码塞进界面里同时保证界面不卡、状态可见。4. Winform 集成方案线程、DataGridView 与断线重连4.1 跨线程调度自动化放后台界面用 BeginInvoke 更新FlaUI 查找元素动辄几百毫秒从搜索到最后发送整个流程要 2 到 5 秒放 UI 线程上窗口直接假死用户第一反应是程序崩了。标准做法是自动化流程跑在 Task 里界面更新统一走 Invoke 或 BeginInvoke。private async void btnSend_Click(object sender, EventArgs e) { string contact txtContact.Text.Trim(); string message txtMessage.Text.Trim(); if (string.IsNullOrEmpty(contact) || string.IsNullOrEmpty(message)) return; btnSend.Enabled false; try { await Task.Run(() SendToContact(contact, message)); AppendLog(发送成功); } catch (Exception ex) { AppendLog($发送失败: {ex.Message}); } finally { btnSend.Enabled true; } } private void AppendLog(string text) { if (InvokeRequired) { BeginInvoke(new Action(() listLog.Items.Add(${DateTime.Now:HH:mm:ss} {text}))); return; } listLog.Items.Add(${DateTime.Now:HH:mm:ss} {text}); }两个写法的细节值得注意。第一界面值 txtContact.Text 必须在 Task.Run 之前拷贝到局部变量千万别在后台线程直接读控件属性InvokeRequired 那一套只解决写回不解决读取。第二BeginInvoke 是异步投递连续打日志时不会排队卡界面如果用 Invoke 同步更新日志一多界面反而变慢。AppendLog 里的 InvokeRequired 判断是为了兼容手动拖日志和代码异步打日志两条路径。4.2 发送记录展示DataGridView 把 bool 渲染成复选框很多人在网上搜winform datagridview 将 list 的一列 0 和 1 的值显示为 checkbox其实就是给 DataGridView 绑定一个含 bool 属性的对象列表它会自动渲染成复选框列约等于白送的交互体验。public class SendRecord { public DateTime Time { get; set; } public string Contact { get; set; } public bool Success { get; set; } public string Error { get; set; } } // 界面初始化时设置数据源 var records new BindingListSendRecord(); dataGrid.AutoGenerateColumns true; dataGrid.DataSource records; // 每次发送完成后追加记录 records.Add(new SendRecord { Time DateTime.Now, Contact 文件传输助手, Success true, Error });BindingList 在新增元素时会自动通知 DataGridView 刷新比每次重新设置 DataSource 省事得多也不会丢失列宽和排序状态。Success 是 bool 类型时DataGridView 默认生成 CheckBox 列不需要写任何自定义 CellTemplate。如果业务模型里存的是 int 0 和 1加一个只读属性public bool SuccessFlag SuccessValue 1;绑定这个属性即可原始列用 Visible false 藏掉。整体界面我用最朴素的布局顶部输入区放联系人、内容、发送按钮中间 DataGridView 展示发送记录底部 ListBox 滚动日志。winform 界面美化不是重点重点是状态可视化。顺手在窗体底部做一个 StatusStrip显示已连接微信或未连接排查问题时一眼看出是自动化层挂了还是微信层挂了省掉一半的定位时间。4.3 长时间运行的稳定性心跳循环与引用重建值班提醒工具要挂一整天最大的敌人是微信中途退登、窗口最小化恢复、UI 元素引用失效。方案是做一个心跳循环定时巡检异常自动重建连接。var cts new CancellationTokenSource(); while (!cts.IsCancellationRequested) { try { EnsureWeChatWindow(); // 检查进程存在、主窗口可获取 DoPendingTasks(); // 执行发送、读取等队列任务 } catch (Exception ex) { AppendLog($心跳异常: {ex.Message}); ReAcquireWindow(); // 重建 window 引用 } await Task.Delay(2000, cts.Token); }EnsureWeChatWindow 每次循环都重新走一遍进程探测和 GetMainWindow不缓存 Window 对象。这个设计的原理是UIA 元素是活引用窗口重绘、微信版本升级、最小化再恢复后缓存的 AutomationElement 可能指向已销毁的旧对象重新查找的代价很低收益却很大。DoPendingTasks 里每个动作都走 FindOrWait进一步保证每次拿到的都是新元素。三段心跳循环配合条件等待是我在 winform 项目案例里见过的抗长时间运行最有效的组合。5. 避坑微信自动化五个高频问题的现象与解法5.1 微信进程存在却拿不到主窗口现象Process.GetProcessesByName 能找到微信但 GetMainWindow 返回 null或者拿到一个标题只有微信的空壳窗口。原因微信启动到登录完成之间主窗口还没创建另外如果机器上有多个微信登录会话附加到的进程和当前可见的窗口可能不是同一个。解决加 5 次重试每次间隔 1 秒同时判断窗口 Title 是否包含登录完成后的标志比如当前微信昵称。不推荐直接自动化登录窗口输入账号密码账号安全不该交给脚本工具只做等待登录完成的轮询就好。5.2 元素找到了点击却没反应现象FindDescendant 正常返回元素调用 Click() 不报异常但微信界面纹丝不动。原因微信部分搜索结果项是自绘控件没有实现 Invoke 模式。FlaUI 的 Click 走 Invoke失败后虽然会尝试坐标点击但坐标点经常落在控件边缘或遮挡层上。解决优先用元素内部的文本子元素来点击取不到就用 BoundingRectangle 中心点做手动坐标点击。再不行就切键盘路径先 Click 搜索框再 Keyboard.Type(contactName) 加 Enter键盘序列是通用后备方案基本不会失效。5.3 发送了但聊天框内容没清空现象编辑框里能看到输入的文字回车也按了但消息没出现在会话里编辑框内容还在。原因微信输入框对 Value 模式写入的文本监听不完整Send 按钮没有被激活。直接用 Keyboard.Type 整串输入偶尔也会丢字符尤其在慢机器上。解决统一用 Keyboard.Type 输入实测逐字符输入比一次性整串输入更稳。发送后读编辑框 Value 做校验非空说明没发出去重试一次两次失败就记录告警而不是无限循环。5.4 微信一升级定位条件全部失效现象微信自动更新后之前用的 AutomationId、ClassName 全都对不上脚本大面积报找不到元素。原因微信客户端 UI 结构随版本调整UIA 属性会跟着变这是 UI 自动化的宿命不是代码写错了。解决升级后先用元素树 dump 工具把新版本完整跑一遍和旧版本对比只改定位条件不动业务逻辑。把定位条件全部抽到配置文件里改配置不改代码一条在 5.4 场景下能省掉半天返工。5.5 长时间运行内存膨胀、界面响应变慢现象工具跑了一天后进程内存涨到几百 MB微信窗口和其他程序交互开始卡顿。原因每次查找元素都新建 UIA3Automation 实例没释放或者订阅了事件没有退订句柄和缓存越积越多。解决整个程序只维护一个 automation 单例Dispose 逻辑只在退出时执行。事件处理器用完后立刻取消订阅尤其是窗口关闭事件。这个坑在 winform 项目案例里太常见了FlaUI 的 Automation 实现了 IDisposable用 using 包裹或者全局单例二选一别每次操作都 new 一个。6. 进阶三段式验证与元素树基线对比自动化脚本从能跑到可信差的不是功能是验证。我把每次发送拆成定位、点击、验证三段每段独立记录耗时和结果定位超过 1 秒打警告点击后检查窗口标题是否变成目标联系人验证读编辑框 Value 是否清空。三段全过才算成功任何一段失败都能日志定位到具体环节而不是只看到一句发送失败。真正救过大命的是元素树基线对比。微信每次升级我先用 dump 工具导出一份新元素树和上一版配置存到一起做 diff差异列表就是这次要改的定位条件。这个习惯源于一次翻车微信大版本更新后搜索框从 Edit 变成了自定义控件当时没有基线硬是排查了一整个下午。现在我把 dump 脚本挂在工具菜单里升级微信后第一步就是重新 dump、对比、更新配置整套流程十分钟内完成。最后补一个验证技巧不要用固定 Sleep 等待发送结果封装一个 WaitFor条件里读编辑框的 Value 属性配合 3 秒超时和 100 毫秒轮询发送成功与否在 300 毫秒内就能判定。从那以后我每次升级微信版本都强制先跑一遍元素树 dump 和基线对比这个习惯救了我至少三次大版本翻车。希望帮到你。本文还有配套的精品资源点击获取