我平时写 Excel 宏时,最容易忽略的一件事是:Excel 不只能处理单元格。只要用好 Office 自动化,它可以去 Access 查库、去 Word 填模板、去 PowerPoint 做演示、去 Outlook 发邮件,还能用 Shell 启动外部程序。
说白了,Excel 变成了一个控制台。数据在 Excel 里整理,其他软件各做各的事。Access 擅长存和查,Word 擅长排版成文档,PowerPoint 擅长展示,Outlook 擅长分发,Shell 负责把 Office 之外的程序也叫起来。
这篇我按“能拿去写代码”的方式讲,不照搬附件里的案例。我换成一些更贴近日常办公的场景:异常订单导出、供应商对账函、经营看板、库存预警邮件、外部脚本启动。
1. Office 自动化到底在干什么
我把 Office 自动化理解成一句话:在一个程序里,用 VBA 操作另一个程序的对象。
每个 Office 程序都有自己的对象库。Excel 有 Workbook、Worksheet、Range;Word 有 Document、Bookmark、Selection;PowerPoint 有 Presentation、Slide、Shape;Outlook 有 MailItem、Attachments。对象库里放着对象、属性、方法和常量。
当 Excel 想控制 Word、Access、PowerPoint 或 Outlook 时,就要先让 Excel “认识”对方。这里就引出一个挺重要的概念:绑定。
早期绑定:写代码时就认识对方
早期绑定要在 VBE 里提前勾选对象库。路径一般是:工具 → 引用,再勾对应的 Microsoft Word xx.0 Object Library、Microsoft PowerPoint xx.0 Object Library、Microsoft Outlook xx.0 Object Library、Microsoft Access xx.0 Object Library。
版本号会跟 Office 版本走。比如 Office 2016 常见的是 16.0,Office 2013 常见的是 15.0。
早期绑定的写法像这样:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Sub新建Word_早期绑定()DimwdAsWord.ApplicationSetwd=NewWord.Applicationwd.Visible=Truewd.Documents.Addwd.QuitSetwd=NothingEndSub
我喜欢用早期绑定写开发版,原因很直接:有智能提示,能看对象浏览器,代码能提前编译检查,还能直接用对方程序里的常量。写复杂宏时,这些提示能少踩很多坑。
后期绑定:运行时再找对方
后期绑定不提前勾对象库。变量统一写成 Object,运行时用 CreateObject 创建对象。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Sub记录Word版本_后期绑定()DimwdAsObjectSetwd=CreateObject("Word.Application")ThisWorkbook.Sheets("参数").Range("B2").Value=wd.Versionwd.QuitSetwd=NothingEndSub
我把文件发给别人用时,更常把最终版改成后期绑定。因为别人电脑上的 Office 版本可能不一样,早期绑定容易因为对象库版本缺失报错。后期绑定少受这个影响。
CreateObject 和 GetObject 怎么选
CreateObject 会创建一个新的程序实例。比如:
- 1
- 2
DimwdAsObjectSetwd=CreateObject("Word.Application")
GetObject 更适合拿已经打开的对象,或者通过文件路径拿一个已加载文件对象。比如打开某个工作簿对象:
- 1
- 2
DimwbAsObjectSetwb=GetObject("D:\\日报\\门店库存.xlsx")
我自己的习惯是:需要从零启动程序,用 CreateObject;需要接住一个指定文件或已打开对象,用 GetObject。
对了,很多自动化对象刚创建出来是隐藏的。调试时我会加:
- 1
wd.Visible=True代码跑完别忘了收尾:该 Quit 的 Quit,该 Close 的 Close,对象变量设成 Nothing。这事看着小,长期跑宏时挺重要。
2. 从 Excel 自动执行 Access 任务
我觉得 Excel 调 Access,最常见就两类:
一类是跑查询,把结果拿回 Excel;另一类是触发 Access 里的宏,让 Access 自己完成一串动作。
2.1 在 Excel 里运行 Access 查询
这个场景我常用在“数据库里有明细,Excel 只要结果”的时候。
我举一个自己的例子:运营同事每天要看“本月异常订单”。数据在 Access 数据库 运营数据.accdb 里,Access 里提前做好查询 q_本月异常订单。Excel 负责按按钮,把结果拉到 异常订单 工作表。
核心对象是 DAO:
DBEngine.OpenDatabase:打开 Access 数据库;QueryDefs("查询名"):找到已有查询;OpenRecordset:把查询结果变成记录集;CopyFromRecordset:把记录集一次性写进 Excel;Fields(i).Name:把字段名写成表头。
早期绑定时,我会在引用里勾 DAO 或 Access Database Engine 相关对象库。要是还要启动 Access 窗口,再勾 Access 对象库。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
Sub拉取本月异常订单()DimdbAsDAO.DatabaseDimqAsDAO.QueryDefDimrsAsDAO.RecordsetDimiAsLongDimwsAsWorksheetSetws=ThisWorkbook.Sheets("异常订单")Setdb=DBEngine.OpenDatabase(ThisWorkbook.Path&"\\运营数据.accdb")Setq=db.QueryDefs("q_本月异常订单")Setrs=q.OpenRecordsetws.Range("A5:Z50000").ClearContentsws.Range("A6").CopyFromRecordsetrsFori=1Tors.Fields.Countws.Cells(5,i).Value=rs.Fields(i-1).NameNextirs.Closedb.CloseSetrs=NothingSetq=NothingSetdb=NothingEndSub
我会把结果从第 6 行开始写,表头放第 5 行。这样前面还能留标题、刷新时间、筛选说明。
这个方法的价值在于:Access 继续负责数据查询,Excel 负责展示和二次计算。代码里不用重写 SQL,也不用手工复制粘贴。
2.2 在 Excel 里运行 Access 宏
有些 Access 操作不是一个查询能搞定的。比如先清临时表,再追加数据,再跑几个汇总查询,再生成一个导出表。遇到这种情况,我会把复杂动作放在 Access 宏里,Excel 只负责触发。
我换个例子:仓库每天要刷新一张 仓储日报,Access 里有宏 m_刷新仓储日报。Excel 里按一下按钮,Access 在后台打开数据库、跑宏、关闭。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Sub触发Access刷新仓储日报()DimacAsAccess.ApplicationSetac=NewAccess.Applicationac.OpenCurrentDatabaseThisWorkbook.Path&"\\仓储数据.accdb"Withac.DoCmd.RunMacro"m_刷新仓储日报".QuitEndWithSetac=NothingEndSub
这个知识点的关键是 Access.Application 和 DoCmd.RunMacro。Excel 不一定要把 Access 界面打开给人看,只要能打开目标数据库并执行宏就行。
3. 从 Excel 自动执行 Word 任务
Word 这块,我最常用两种玩法:把 Excel 表格塞进 Word 模板;按客户清单批量生成类似邮件合并的文档。
3.1 把 Excel 数据送进 Word 文档
Word 的关键点是:先在模板里放书签。
比如我要做一份供应商对账函,Word 模板叫 供应商对账函模板.docx。模板里提前插入一个书签,名字叫 对账表位置。Excel 里有一张 对账明细,范围 B4:H20 是要放进 Word 的表格。
我会让宏做这几件事:
复制 Excel 里的目标区域; 打开 Word 模板; 找到书签位置; 删除旧表格; 粘贴新表格; 调整列宽; 把书签重新插回去。
重新插书签很关键。因为 Word 里一旦往书签位置粘东西,原书签可能会丢。下次再跑宏就找不到位置了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
Sub生成供应商对账函()DimsrcAsExcel.RangeDimwdAsWord.ApplicationDimdocAsWord.DocumentDimposAsWord.RangeSetsrc=ThisWorkbook.Sheets("对账明细").Range("B4:H20")src.CopySetwd=NewWord.ApplicationSetdoc=wd.Documents.Open(ThisWorkbook.Path&"\\供应商对账函模板.docx")wd.Visible=TrueSetpos=doc.Bookmarks("对账表位置").RangeOnErrorResumeNextpos.Tables(1).DeleteOnErrorGoTo0pos.Pastepos.Tables(1).Columns.SetWidth_(src.Width/src.Columns.Count),wdAdjustSameWidthdoc.Bookmarks.Add"对账表位置",posSetpos=NothingSetdoc=NothingSetwd=NothingEndSub
我在正式项目里会再加保存文件名,比如按供应商名和日期保存,避免每次都覆盖模板。
3.2 用 Excel 模拟 Word 邮件合并
Word 自带邮件合并,很多时候够用。可一旦你想在 Excel 里先筛选客户、拼接文案、判断字段缺失,再批量生成 Word 文档,VBA 模拟邮件合并就很方便。
我的例子是“客户续费提醒”。Excel 有 客户清单,字段包括客户名、项目名、到期日、负责人邮箱。Word 模板 续费提醒模板.docx 里放这些书签:客户名、项目名、到期日、负责人。
宏的动作是:
新建一个 Word 文档; 遍历 Excel 客户清单; 每个客户插入一份模板内容; 跳到各个书签处填字段; 删除没用的书签; 插入分页符,继续下一个客户。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
Sub批量生成续费提醒()DimwdAsWord.ApplicationDimdocAsWord.DocumentDimcellAsExcel.RangeDimlistRangeAsExcel.RangeDimcustomerAsStringDimprojectNameAsStringDimexpireDateAsStringDimownerNameAsStringSetlistRange=ThisWorkbook.Sheets("客户清单").Range("A2:A50")Setwd=NewWord.ApplicationSetdoc=wd.Documents.Addwd.Visible=TrueForEachcellInlistRange.CellsIfLen(cell.Value)>0Thencustomer=cell.ValueprojectName=cell.Offset(0,1).ValueexpireDate=Format(cell.Offset(0,2).Value,"yyyy-mm-dd")ownerName=cell.Offset(0,3).Valuewd.Selection.InsertFileThisWorkbook.Path&"\\续费提醒模板.docx"wd.Selection.GoToWhat:=wdGoToBookmark,Name:="客户名"wd.Selection.TypeTextText:=customerwd.Selection.GoToWhat:=wdGoToBookmark,Name:="项目名"wd.Selection.TypeTextText:=projectNamewd.Selection.GoToWhat:=wdGoToBookmark,Name:="到期日"wd.Selection.TypeTextText:=expireDatewd.Selection.GoToWhat:=wdGoToBookmark,Name:="负责人"wd.Selection.TypeTextText:=ownerNameOnErrorResumeNextdoc.Bookmarks("客户名").Deletedoc.Bookmarks("项目名").Deletedoc.Bookmarks("到期日").Deletedoc.Bookmarks("负责人").DeleteOnErrorGoTo0wd.Selection.EndKeyUnit:=wdStorywd.Selection.InsertBreakType:=wdPageBreakEndIfNextcellwd.Selection.HomeKeyUnit:=wdStorySetdoc=NothingSetwd=NothingEndSub
这里我会注意一点:书签名要和代码完全一致,少一个字都不行。模板做好后,我一般会先跑一条数据测试,再批量跑。
4. 从 Excel 自动执行 PowerPoint 任务
PowerPoint 这块的思路很清楚:Excel 负责做数据和图表,PPT 负责展示。VBA 把 Excel 的区域或图表复制成图片,再贴进 PPT。
4.1 把一块 Excel 区域送进一张 PPT
比如我在 Excel 里做好一页经营快照,范围是 A1:H18。我要新建 PPT,放到一张标题页里。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
Sub生成经营快照PPT()DimppAsPowerPoint.ApplicationDimpresAsPowerPoint.PresentationDimsldAsPowerPoint.SlideDimslideTitleAsStringslideTitle="本周经营快照"Setpp=NewPowerPoint.ApplicationSetpres=pp.Presentations.Addpp.Visible=TrueSetsld=pres.Slides.Add(1,ppLayoutTitleOnly)sld.SelectThisWorkbook.Sheets("经营快照").Range("A1:H18").CopyPicture_Appearance:=xlScreen,Format:=xlPicturesld.Shapes.Paste.Selectpp.ActiveWindow.Selection.ShapeRange.AlignmsoAlignCenters,Truepp.ActiveWindow.Selection.ShapeRange.AlignmsoAlignMiddles,Truesld.Shapes.Title.TextFrame.TextRange.Text=slideTitleSetsld=NothingSetpres=NothingSetpp=NothingEndSub
我常用 CopyPicture,因为它把区域当图片复制。这样 PPT 里版式更容易保持,看的人也不容易误改单元格。
4.2 把一张表里的所有图表送进 PPT
如果工作表里有很多图表,我会遍历 ChartObjects。每个图表复制成图片,新建一张空白幻灯片,再贴进去。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
Sub把所有销售图表做成PPT()DimppAsPowerPoint.ApplicationDimpresAsPowerPoint.PresentationDimsldAsPowerPoint.SlideDimiAsLongDimslideCountAsLongDimwsAsWorksheetSetws=ThisWorkbook.Sheets("销售图表")Ifws.ChartObjects.Count<1ThenMsgBox"销售图表工作表里没有图表"ExitSubEndIfSetpp=NewPowerPoint.ApplicationSetpres=pp.Presentations.Addpp.Visible=TrueFori=1Tows.ChartObjects.Countws.ChartObjects(i).Chart.CopyPicture_Appearance:=xlScreen,Format:=xlPictureApplication.WaitNow+TimeValue("0:00:01")slideCount=pres.Slides.CountSetsld=pres.Slides.Add(slideCount+1,ppLayoutBlank)sld.Selectsld.Shapes.Paste.Selectpp.ActiveWindow.Selection.ShapeRange.AlignmsoAlignCenters,Truepp.ActiveWindow.Selection.ShapeRange.AlignmsoAlignMiddles,TrueNextiSetsld=NothingSetpres=NothingSetpp=NothingEndSub
这里的 Application.Wait 看起来有点笨,但我试过,图表多时剪贴板偶尔会慢半拍。加一秒等待,能少很多莫名其妙的粘贴失败。
4.3 把整个工作簿变成 PPT
还有一种挺实用的方式:把工作簿里的每张工作表当成一页幻灯片。Excel 里每张表做成固定版式,VBA 循环每张表,复制指定区域,贴到 PPT。
我用一个例子:区域经营报告.xlsx 里每张工作表代表一个区域。每张表的 B2 是标题,A1:N24 是要展示的内容。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
Sub工作簿转经营报告PPT()DimppAsPowerPoint.ApplicationDimpresAsPowerPoint.PresentationDimsldAsPowerPoint.SlideDimwsAsWorksheetDimpageRangeAsStringDimpageTitleAsStringDimslideCountAsLongpageRange="A1:N24"Setpp=NewPowerPoint.ApplicationSetpres=pp.Presentations.Addpp.Visible=TrueForEachwsInThisWorkbook.Worksheetsws.ActivatepageTitle=ws.Range("B2").Valuews.Range(pageRange).CopyPictureAppearance:=xlScreen,Format:=xlPictureApplication.WaitNow+TimeValue("0:00:01")slideCount=pres.Slides.CountSetsld=pres.Slides.Add(slideCount+1,ppLayoutTitleOnly)sld.Selectsld.Shapes.Paste.Selectpp.ActiveWindow.Selection.ShapeRange.AlignmsoAlignCenters,Truepp.ActiveWindow.Selection.ShapeRange.Top=110sld.Shapes.Title.TextFrame.TextRange.Text=pageTitleNextwsSetsld=NothingSetpres=NothingSetpp=NothingEndSub
我觉得这招适合做固定周报、月报。设计一次 Excel 页面,后面每周刷新数据后直接转 PPT。
5. 从 Excel 自动执行 Outlook 任务
Outlook 自动化的核心对象是 MailItem。Excel 创建一封邮件,填收件人、抄送、密送、标题、正文、附件,再选择展示或发送。
我一般先用 .Display,让邮件弹出来给人确认。等规则完全稳定,再考虑 .Send。
5.1 把当前工作簿作为附件发送
这个场景最简单:把当前 Excel 文件当附件发出去。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
Sub发送当前工作簿给运营组()DimolAppAsOutlook.ApplicationDimmailAsObjectSetolApp=NewOutlook.ApplicationSetmail=olApp.CreateItem(0)olApp.Session.LogonWithmail.To="ops-team@example.com".CC="manager@example.com".BCC="".Subject="今日运营日报".Body="大家好,附件是今日运营日报,请查收。".Attachments.AddActiveWorkbook.FullName.DisplayEndWithSetmail=NothingSetolApp=NothingEndSub
这里的知识点是:CreateItem(0) 创建邮件,Attachments.Add 添加附件,.Display 展示邮件窗口。
5.2 只把指定区域作为附件发送
很多时候,我不想把整本工作簿发出去,只想发一个区域。我的做法是:复制目标区域到一个临时工作簿,粘贴值和格式,保存成临时 xlsx,再把这个临时文件挂到邮件里,发完删掉。
例子:把 门店日报 的 A1:G30 发给区域经理。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
Sub发送门店日报区域()DimolAppAsOutlook.ApplicationDimmailAsObjectDimtempPathAsStringtempPath=ThisWorkbook.Path&"\\门店日报_临时附件.xlsx"ThisWorkbook.Sheets("门店日报").Range("A1:G30").CopyWorkbooks.AddRange("A1").PasteSpecialxlPasteValuesRange("A1").PasteSpecialxlPasteFormatsActiveWorkbook.SaveAstempPathSetolApp=NewOutlook.ApplicationSetmail=olApp.CreateItem(0)olApp.Session.LogonWithmail.To="region-manager@example.com".Subject="门店日报摘要".Body="我只放了日报摘要区域,方便快速查看。".Attachments.AddtempPath.DisplayEndWithActiveWorkbook.CloseSaveChanges:=TrueKilltempPathSetmail=NothingSetolApp=NothingEndSub
这里有两个细节我会盯住:临时文件路径要能写入;发完要 Kill 掉,别让文件夹越来越乱。
5.3 只把指定工作表作为附件发送
如果我要发的是一整张表,比如 库存预警,我会直接复制工作表到新工作簿,再保存成临时附件。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
Sub发送库存预警工作表()DimolAppAsOutlook.ApplicationDimmailAsObjectDimtempPathAsStringtempPath=ThisWorkbook.Path&"\\库存预警_临时附件.xlsx"ThisWorkbook.Sheets("库存预警").CopyActiveWorkbook.SaveAstempPathSetolApp=NewOutlook.ApplicationSetmail=olApp.CreateItem(0)olApp.Session.LogonWithmail.To="warehouse@example.com".Subject="本周库存预警".Body="附件是本周库存预警工作表,请安排处理。".Attachments.AddtempPath.DisplayEndWithActiveWorkbook.CloseSaveChanges:=TrueKilltempPathSetmail=NothingSetolApp=NothingEndSub
区域附件和单表附件很像。区别在于:区域需要复制到新工作簿并粘贴值和格式;单表可以直接 Sheet.Copy。
5.4 从联系人列表里批量加入邮件地址
如果 Excel 里有联系人列表,我不想手工一个个复制地址。VBA 可以遍历单元格,把地址拼到 BCC 里。邮箱之间用英文分号分隔,也可以用 Chr(59)。
例子:联系人 工作表的 D2:D200 放邮箱,要把当前工作簿发给所有门店负责人。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
Sub发给联系人列表()DimolAppAsOutlook.ApplicationDimmailAsObjectDimcellAsRangeDimaddressListAsRangeSetaddressList=ThisWorkbook.Sheets("联系人").Range("D2:D200")SetolApp=NewOutlook.ApplicationSetmail=olApp.CreateItem(0)olApp.Session.LogonWithmailForEachcellInaddressList.CellsIfLen(cell.Value)>0Then.BCC=.BCC&Chr(59)&cell.ValueEndIfNextcell.Subject="门店运营资料包".Body="各位好,附件是本周门店运营资料包。".Attachments.AddActiveWorkbook.FullName.DisplayEndWithSetmail=NothingSetolApp=NothingEndSub
我更愿意把大量联系人放进 BCC,避免收件人互相看到邮箱。实际使用前,也要确认公司邮件规则允许这样发。
6. 从 Excel 启动其他应用程序
Office 之外的程序也能被 Excel 叫起来。附件里讲到的重点是 Shell 函数。
Shell 的基本写法是:
- 1
TaskID=Shell(程序名,窗口样式)第一个参数是要启动的程序,比如 notepad.exe、calc.exe,也可以是一个脚本或批处理文件。第二个参数控制窗口显示方式,1 常用来表示正常窗口并获得焦点。
Shell 成功后会返回任务编号。失败时会报错,所以我会加错误处理。
我换个例子:用 Excel 打开记事本,提醒同事记录交接事项。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
Sub打开交接记录工具()DimprogramAsStringDimtaskIDAsDoubleOnErrorResumeNextprogram="notepad.exe"taskID=Shell(program,1)IfErr.Number<>0ThenMsgBox"无法启动:"&program,vbCritical,"启动失败"ElseMsgBox"已启动程序,任务编号:"&taskIDEndIfOnErrorGoTo0EndSub
Shell 有一个很容易被忽略的点:它会异步执行。也就是说,Excel 启动外部程序后,会继续执行后面的 VBA 代码,不会自动等外部程序关掉。
如果后面的代码依赖外部程序生成的文件,我会用等待方式。比如调用一个本地脚本生成日报,必须等脚本结束后再读取文件,我会这样写:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Sub运行脚本并等待完成()DimshAsObjectDimcmdAsStringSetsh=CreateObject("WScript.Shell")cmd="cmd/c"""&ThisWorkbook.Path&"\\生成日报.bat"""sh.Runcmd,1,TrueMsgBox"脚本已结束,可以读取结果文件了。"Setsh=NothingEndSub
这里第三个参数 True 表示等待命令结束。严格说,这段用的是 WScript.Shell.Run,我在实际项目里经常搭配 Shell 的思路使用。它解决的就是“Excel 不等外部程序”的问题。
7. 我会固定检查这些点
写 Office 自动化宏时,我会把下面这些点当成检查清单:
对象库引用。 早期绑定要勾对应对象库。Word、PowerPoint、Outlook、Access 都有自己的 Object Library。版本号跟 Office 版本有关,发给别人用时要特别小心。
早期绑定和后期绑定的取舍。 开发时我喜欢早期绑定,提示多;交付给别人时,我常改后期绑定,少受版本影响。
CreateObject 和 GetObject。 新开程序用 CreateObject,拿已有对象或指定文件对象用 GetObject。
Visible。 Word、PowerPoint、Access 这些程序可见不可见都能跑。调试阶段我会设 Visible = True,正式跑时再按需求改。
Access 的结果写回。 查询结果放到 Recordset,再用 CopyFromRecordset 写回 Excel。表头可以用 Fields(i).Name 循环写。
Word 的落点。 Word 模板里要提前放书签。粘贴完数据后,很多时候要把书签重新加回去。
PowerPoint 的复制方式。 区域和图表都可以 CopyPicture。PPT 里用 Shapes.Paste,再用 Align 居中。
Outlook 的安全发送。 我会先 .Display,确认主题、正文、附件、收件人无误,再考虑 .Send。
临时文件。 发送指定区域或指定工作表时,通常要保存临时工作簿。发完要关闭并删除。
错误处理。 On Error Resume Next 不能一直开着。跳过某个可能不存在的旧表格、旧书签后,我会尽快 On Error GoTo 0。
等待动作。 图表复制到 PPT、外部程序生成文件,这类动作都可能有时间差。该等就等,不然宏会出现偶发失败。
对象清理。 Quit、Close、Set Nothing 都要写清楚。自动化对象残留太多,会让电脑上多出一堆看不见的 Word、PPT 或 Outlook 进程。
8. 我对这章的理解
这章其实讲的是一套跨 Office 的指挥方法:Excel 负责组织数据和发指令,其他软件完成它们擅长的部分。
Access 适合查库和跑宏;Word 适合模板填充和批量文档;PowerPoint 适合把报表图表变成演示;Outlook 适合邮件分发;Shell 适合把 Office 之外的程序也拉进来。
你知道每个软件该干什么。只要对象找对、落点找对、文件清理干净,很多重复办公动作都可以交给 Excel 去跑。
夜雨聆风