干穿Excel系列之【VBA窗体】
- 2026-11-07 19:22:32

=LET(delim,"、",slots,8,dates,$A$2:$A$200,names,$B$2:$B$200,allTokens,REDUCE("",names,LAMBDA(acc,rowText,VSTACK(acc,TRIM(MID(SUBSTITUTE(rowText,delim,REPT(" ",99)),SEQUENCE(slots)*99-98,99))))),persons,UNIQUE(FILTER(allTokens,allTokens<>"")),counts,MAP(persons,LAMBDA(personName,SUMPRODUCT(--ISNUMBER(FIND(personName,names))))),nCols,2+MAX(counts),MAP(SEQUENCE(ROWS(persons)+1,nCols),LAMBDA(cellNum,LET(rowNum,ROUNDUP(cellNum/nCols,0),colNum,MOD(cellNum-1,nCols)+1,IF(rowNum=1,IF(colNum=1,"姓名",IF(colNum=2,"出差天数","出差日期"&(colNum-2))),LET(onePerson,INDEX(persons,rowNum-1),IF(colNum=1,onePerson,IF(colNum=2,INDEX(counts,rowNum-1),IFERROR(SMALL(IF(ISNUMBER(FIND(onePerson,names)),dates,""),colNum-2),"")))))))))

今天没做什么大事。我只是想验证一个问题:当 AI 通过 MCP 接上本机的 Excel 之后,它到底是“看着我操作”,还是真的伸手进去了?答案比我想的更彻底——它拿到的是结构,不是像素。
一句话,它说出了我窗体长什么样
工作簿叫《销售数据统计表.xlsm》,只有一张表「演示数据」,A1:M84 共 83 条订单。里面有一个我自己做的订单查询窗体,名字叫 OrderQueryForm。
我给它的话只有一句:读取当前工作簿的 VBA 窗体。
它回来的第一份东西是工程清单——五个组件,哪个有多少行代码,一目了然:

接着是窗体本身:标题「订单查询 - 销售数据统计表」,尺寸 712.5 × 624.75,背景色 #F4F4F5,全窗体统一用微软雅黑 10 号。再往下,22 个控件逐个列出——左边距、上边距、宽高、字号、是否加粗、前景色、背景色、边框色、对齐方式,一个不落。
它甚至读出了我当时的配色心思:主按钮「查询」是 #409EFF 的白字,状态行用 #3C6EB4 的蓝,出错时才切成 RGB(192, 57, 43) 的红。
这不是“识别图片”。这是把一个可交互的界面,读成了一份可以计算的数据。
它读出来的结构,和我脑子里的设计图一致
窗体分了三个区,AI 是按控件坐标还原出来的,我没提示过它:
然后它去读了逻辑,挑出几个关键过程:LoadCascadeCombo(69 行,级联下拉)、ParseDate(日期容错)、RunQuery(100 行,核心查询)、lstResult_DblClick(双击跳回源数据行)。
它读 RunQuery 读得很细。它注意到我是逐行扫描 2 到末行、七个条件按 AND 串联;注意到我先用 ParseDate 验日期格式,格式错就直接在状态行写“请按 2025-03-01 的写法输入”,而不是弹窗;注意到我用一个 gRows 数组悄悄存了每一行在源表里的真实行号,所以双击结果能精准跳回去。
也注意到结果行那句话是我手写的文案:
OrderQueryForm · RunQuery · 第 385 行附近
OrderQueryForm · RunQuery · 第 385 行附近lblCount.Caption = "命中 " & lstResult.ListCount & " 条 · 销售额合计 " _& Format$(total, "#,##0.00") & " 元 · 双击行可跳转到源数据"
看到这里我笑了一下。那句“双击行可跳转到源数据”是我三个月前写窗体时随手加的提示,我自己都忘了。它读出来了。
MCP 强在哪:它拿到的是“可执行的世界”
如果只到这里,这不过是“AI 会看代码”。真正让我觉得不一样的是后面几层。
第一层:读的是结构,不是截图
同样是“看懂一个窗体”,截图方案给到模型的是像素,它只能说“大概有个下拉框在左上角”;MCP 给到的是每个控件的 name、类型、坐标、字号、颜色、Caption。前者只能聊,后者能算——能判断两个标签有没有对齐、能算出按钮间距是不是等距、能发现 lblHeader 和 lstResult 只差了 2 个像素。
截图 / 人眼“窗体上面有一排下拉框,下面有个列表,最下面三个按钮。”
MCP 结构读“cboRegion 位于 (96, 48),246 × 18,边框 #D8DCE6;lstResult 位于 (14.25, 222),667.75 × 280。”
第二层:读完能改,改完能跑,跑完能核对
这才是关键。读取只是入口,同一个通道里还挂着写代码、加控件、改属性、运行宏、再读回来的能力。所以它不是一个“只读的观察者”,而是一个能闭环的施工队:
写进去 → 立刻读回核对,改错了当场发现; 改完 → 直接跑一次宏验证,用毫秒级的实际耗时证明“真的跑通了”; 跑完 → 再读一次窗体,确认 22 个控件都在、坐标没漂。
工程日志里留着前几天的记录:一次窗体改动“共 21 处改动”,另一次布局重排后“入口宏实跑一次成功(47 ms,无错误)”。47 毫秒这个数字,是它跑完自己报的,不是它猜的。
第三层:它会记事,而且记的是“坑”
这次对话里最让我意外的,是它顺手读出了这个工程的工作日志。日志不是流水账,每条都带四个字段:我当时的原话(intent)、它做了什么(did)、结论(summary)、以及踩过的坑(pitfall)。
比如 10 月 16 日那条,pitfall 写着:
VBAProject · 工作日志 · pitfall
1) 写 "8 / 5" 这类带斜杠文本前必须先把单元格 NumberFormat 设成 "@",否则变成日期序列值。2) 汇总表取图表区域要用 标题行+2 起, 别把表头行当数据。3) 插件会移除 Application.DisplayAlerts=False, 别留对应的 =True。第一条是真的疼过:KPI 单元格里写“8 / 5”,Excel 当场把它变成日期,显示成 46239。这种坑,人踩一次记一辈子,但换个会话、换个模型,AI 通常会再踩一遍。而它把坑写进了工程里——下次谁接手(哪怕是另一个会话的它自己),开工前先读日志。
说点冷静的
MCP 不神秘,它本质上就是一套“让模型调用外部能力”的协议。它的强大不在协议本身,而在于有人把 Excel、把文件系统、把浏览器、把数据库,都包成了模型能理解的动作。
所以它也有边界:它读不到你没授权的地方;它改代码也会改错,需要读回核对;它跑宏也会遇到弹窗卡住。今天这次之所以顺利,是因为整个操作都是“读”,风险最低。越是能写的动作,越要让它自证——这也是为什么好用的 MCP 工具都带着“读回核对”和“日志”这两件东西。
判断一个 AI 工具值不值,别看它答得多漂亮。看它敢不敢在改完之后,自己跑一遍,再把结果读回来给你看。