ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Excel VBA对象模型与70个代码技巧:Range、工作表、工作簿及易错点

Excel VBA对象模型与70个代码技巧:Range、工作表、工作簿及易错点

简介:《EXCEL_VBA常用代码实战大全》面向希望用VBA自动化Excel重复操作、提升报表处理效率的初中级用户,也可作为办公自动化学习者的常用代码速查手册。内容围绕Range与Cells对象展开,涵盖单元格引用、Offset与Resize区域调整、Union合并区域、UsedRange与CurrentRegion识别数据边界,并延伸到Select/Activate/Goto选定区域、定位最后一个非空单元格、Find/FindNext查找、Like运算符匹配、单元格内字符串替换、区域复制与选择性粘贴、禁用拖放等实战技巧,强调遇到问题能查到、照着改就能用。压缩包共1个docx文档,约15.12MB,按章与技巧编号组织,目录层级清楚,便于按知识点检索和逐条练习。目前已有2071人学习下载,适合需要系统梳理VBA常用写法、积累代码片段并提升Excel自动化能力的读者。

1. VBA 代码地图:从对象模型到常用技巧的整体骨架

学 Excel VBA 最容易走的一条弯路,是把它当成“背语法”,结果背了一堆 If 和 For,打开别人的表还是看不懂。真正决定代码能不能写稳的,是 Excel 的对象模型:Application 在最外层,往下是 Workbook、Worksheet,再往下才是 Range、Shape、Chart、Comment 这些具体的东西。这份 Excel VBA 常用代码实战整理最值钱的地方,正是它没有按语法罗列,而是按对象层级把 70 个技巧排了一遍:第 1 章全给 Range,第 2 章给 Worksheet,第 3 章给 Workbook,后面才是图形图表和 Application。复习的时候,建议按这个由内到外的顺序走一遍。适合已经能写几行宏、但代码一改表结构就报 1004 的人;也适合正准备把日常报表工作交给自己写的小工具的人。

2. Range 对象:引用、定位与格式化的复习主线

Range 是 VBA 里出现频率最高的对象,也是最容易在细节上出问题的地方。这一章的内容看起来全是“怎么写地址”,实际考的是另一件事:你写的引用方式,能不能在表格插了一行、删了一列之后还正常工作。硬编码地址写起来最快,但维护成本最高;用变量算出地址麻烦一点,却能让代码活得更久。复习时不要只记写法,要记每种写法在什么场景下必须用、什么时候坚决不用。

2.1 八种引用写法与它们的适用边界

Range、Cells、快捷记号、Offset、Resize、Union、UsedRange、CurrentRegion,这八种写法覆盖了日常九成以上的引用需求,但它们各自的“脾气”完全不同,混着用就会出问题。下表按使用频率排列,最后一列是复习时要重点记的坑。

写法示例典型场景需要留意
Range 属性Range("A1:C3")地址固定、区域规则地址是字符串,拼错不会在编译期报错
Cells 属性Cells(3, 2)行号列号由变量算出参数顺序是先行后列,写反了不报错但取错格
快捷记号[A1]临时调试图省事不支持变量,正式代码里少用
OffsetRange("A1").Offset(1, 2)相对当前格偏移参数是行偏移、列偏移,越界直接报 1004
ResizeRange("A1").Resize(2, 3)行列数动态变化参数是行数、列数,不是结束单元格地址
UnionUnion(Range("A1"), Range("C3"))多块不连续区域一次处理区域必须同表,跨表合并会报错
UsedRangeActiveSheet.UsedRange粗略判断数据边界只剩格式的空行空列也会被算进去
CurrentRegionRange("A1").CurrentRegion取一整块连续数据数据中间有整行空白,块就断在这里

一个常被忽略的细节:快捷记号[A1]和单元格地址里的$不是一回事。$A$1说的是绝对引用,属于公式层面的相对/绝对概念;[A1]是 VBA 里对Range("A1")的简写。考试或面试里经常把这两个混在一起问,复习时要分清楚它们属于不同层。另外,写Range("A1", "C3")和Range("A1:C3")效果一样,前者在需要把两个字符串变量拼成区域时更好用,比如Range(Cells(1, 1), Cells(n, m))。

选择哪种写法,本质是在选“代码跟表格结构的耦合程度”。全部用Range("B2")写死,表格一插列就全错位;全部用 Cells 加变量,代码变长但抗变化。梳理下来会发现一个相对稳妥的惯例:入口处用常量或名称定义好起始位置,中间用 Offset 和 Resize 推算,收尾再用 CurrentRegion 或 UsedRange 兜底。

2.2 选定、定位与查找:Select 之外的三条路

初学者最爱用Range("A1").Select,因为它录宏的时候就是这么产生的。但 Select 有三个代价:要求目标工作表处于活动状态、每次操作都要刷新界面所以慢、以及会覆盖用户当前的选区。Activate和Application.Goto同理,它们改变的是“用户看到什么”,而不是“代码操作什么”。真正的分界线是:需要让用户看到结果时才用 Select,纯计算和写入一律直接操作对象。

Sub 定位与查找() Dim lastRow As Long Dim rng As Range ' 从工作表最底部往上找 A 列最后一个非空单元格,Rows.Count 兼容不同版本的行的上限 lastRow = Cells(Rows.Count, 1).End(xlUp).Row ' Find 更稳的版本:按行倒序找"任意内容",LookIn 用 xlFormulas 连公式结果为空串的也能找到 Set rng = Cells.Find(What:="*", After:=Cells(1, 1), _ LookIn:=xlFormulas, LookAt:=xlPart, _ SearchOrder:=xlByRows, SearchDirection:=xlPrevious) ' 没找到时 Find 返回 Nothing,不判断就取 .Row 会直接 91 错误 If Not rng Is Nothing Then Debug.Print "最后一个单元格:" & rng.Address End Sub

这段里几个参数值得单独记:LookIn选xlValues只看显示值,选xlFormulas连公式本身一起看,后者在单元格公式返回空字符串时更可靠;SearchDirection设成xlPrevious才是从后往前找,这也是找“最后一个”的关键;After决定起点,默认从左上角开始,容易漏掉 A1 本身。End(xlUp)和Find是两条不同的路,前者快但怕整列空白,后者慢一点但边界情况更少。

查找还有一条轻量路线是Like运算符,配合通配符做模式匹配:*任意多个字符、?单个字符、#单个数字、[a-z]字符范围。它适合在已经读进数组的数据里做筛选,不适合直接对单元格区域逐格跑,几万行会明显卡。替换则用Replace方法,注意它默认是全局替换,只想换第一个要手动控制范围。

2.3 复制、赋值与公式:三条数据通道的成本

把数据从 A 区搬到 B 区,至少有三种写法,性能差着数量级。Copy加PasteSpecial最直观但最慢;直接赋值Range("B1").Value = Range("A1").Value快一些,但一次只能搬一个格;真正高效的是数组整体读写——一次性把区域读进内存,处理完再一次性写回,中间不碰工作表。

Sub 快速取值与写回() Dim v As Variant Dim i As Long, j As Long ' 一次性读入,返回二维数组,两个维度的下标都从 1 开始 v = Range("A1:D1000").Value For i = 1 To UBound(v, 1) For j = 1 To UBound(v, 2) If IsEmpty(v(i, j)) Then v(i, j) = 0 ' 空单元格在数组里是 Empty,不是空字符串 Next j Next i ' 写回时用 Resize 撑开到和数组一样的尺寸,否则只填左上角一格 Range("F1").Resize(UBound(v, 1), UBound(v, 2)).Value = v End Sub

参数说明:UBound(v, 1)是第一维(行)的上界,UBound(v, 2)是第二维(列)的上界;IsEmpty判空比= ""更准,因为空单元格读进数组后是Empty而不是空字符串。写回那一步最容易翻车——忘记 Resize 的话,VBA 只把数组第一个元素写进 F1,剩下的悄悄丢掉,不报任何错。

公式相关的技巧自成一小组:写入公式用.Formula(A1 样式)或.FormulaR1C1(R1C1 样式,适合批量填充相对公式);判断是否含公式用HasFormula;判断公式是否出错用IsError(cell.Value),别去比对"#N/A"字符串;取引用单元格用.Precedents和.DirectPrecedents,注意它们只对当前工作表内的引用有效;公式转数值最省事的写法是rng.Value = rng.Value,一行搞定,比复制加选择性粘贴干净。

提示:Precedents在目标单元格所在工作簿未打开时会报错,跨工作簿取引用关系前先确认文件已打开。

2.4 格式、数据有效性与批注的批量处理

格式类操作单独看都很简单,批量处理时问题集中在“一次设置一个属性太慢”和“改完无法还原”两点。字体、内部填充、边框分别对应Font、Interior、Borders三个子对象,颜色统一用RGB(r, g, b)生成,注意 VBA 里颜色值的内部顺序和 RGB 函数的参数顺序是反的,所以永远用 RGB 函数而不要手写长整数。行高列宽在批量设置前先把RowHeight设成固定值、或者用AutoFit一次性处理,比逐行设快得多。

高亮显示是格式技巧的典型应用,也是易错点:想“高亮后再恢复”,必须在改色之前把原色存进变量或数组,否则原色丢失,用户只能撤销或重开文件。这类“改之前先备份状态”的思维,在后面工作表隐藏、窗口标题、状态栏的恢复里会反复出现。

数据有效性(下拉列表)的复习重点在三个动作:建立、判断存在、动态扩展。建立时Validation.Add Type:=xlValidateList,Formula1可以直接写死"甲,乙,丙",也可以指向一个区域。判断是否存在不能看Validation.Type报不报错,而要先读Validation.Type,等于xlValidateNone说明没有。动态下拉的常规做法是用定义名称配合OFFSET加COUNTA算出实际长度,这样新增条目不用改代码。至于“选中单元格自动展开下拉箭头”,靠的是Application.SendKeys "%{DOWN}",属于玄学写法,在不同输入法和系统语言下表现不一,知道有这回事即可,别在正式工具里依赖它。

批注只有三个动作需要记牢:HasComment判断有无、AddComment添加(注意对已有批注的单元格再 Add 会报错,要先删)、Comment.Delete删除。批量清理时用Cells.ClearComments一次清空整表,比循环快。

3. Worksheet 与 Workbook:引用方式、增删与生命周期

从单元格往上跳一层,就是工作表的操作。这一层的代码有个明显特征:它管理的是“容器”,所以出问题往往不是逻辑错,而是引用错、顺序错、或者被 Excel 的保护机制挡住了。复习这一章的方法,是把每个技巧都还原成一个具体场景——我要遍历哪些表、我要删哪些行、我要把这个文件另存出去——场景清楚了,写法自然就固定下来。

3.1 四种引用方式与两种遍历

工作表有四种引用方式,各有各的适用场合:Worksheets("汇总")按名字引用,可读性最好但改名就断;Worksheets(1)按索引引用,位置一变就指错表;Sheet1是代码名称,在属性窗口里能看到,用户改表名也不影响它,是三者里最稳的;ActiveSheet指当前活动表,写交互式代码时方便,但后台运行时它可能根本不是你想要的表。还有一个容易被忽略的差别:Worksheets集合里只有工作表,Sheets集合里既有工作表也有图表工作表,用后者遍历时对图表表调用.Cells会直接报错。

遍历有两种写法,选哪个取决于循环里要不要用到索引。

Sub 遍历工作表() Dim ws As Worksheet Dim i As Long ' 写法一:For Each,只关心对象本身时用它,代码短且不用管索引 For Each ws In ThisWorkbook.Worksheets Debug.Print ws.Name, ws.Visible Next ws ' 写法二:按索引循环,需要"第几张"或者要跳着取的时候用它 For i = 1 To ThisWorkbook.Worksheets.Count Debug.Print i, ThisWorkbook.Worksheets(i).Name Next i End Sub

区别在于:For Each 在循环体内删除工作表会漏掉元素,索引循环配合Step -1倒序删除才安全。这一点和后面删行、删图形是同一个道理——凡是“边遍历边删”,一律倒序。

3.2 增删、隐藏与保护:结构层面的操作

新增工作表用Worksheets.Add,Before和After两个参数决定位置,都不写就插在当前活动表之前。删除工作表必须先关掉警告框,否则每删一张弹一次确认,自动化就断了:

Sub 删除指定工作表() Dim ws As Worksheet Application.DisplayAlerts = False ' 关掉"是否删除"的确认框 On Error Resume Next ' 表不存在时 Delete 会报错,先跳过 Set ws = ThisWorkbook.Worksheets("临时表") If Not ws Is Nothing Then ws.Delete On Error GoTo 0 ' 立刻恢复错误处理,别让它一直生效 Application.DisplayAlerts = True End Sub

DisplayAlerts和EnableEvents这类开关有个共同的血泪教训:设成 False 之后一旦中间出错跳出过程,开关就留在关闭状态,用户后续所有操作都不再弹提示,看起来像 Excel 坏了。规范做法是用错误处理兜底,或者在过程末尾无条件恢复。

隐藏分三个级别:xlSheetHidden是普通隐藏,用户右键就能取消;xlSheetVeryHidden是深度隐藏,只能在 VBA 属性窗口里改回来,适合放参数表;xlSheetVisible恢复显示。深度隐藏不是安全措施,只是防误操作。真要防止别人删除或改名,思路是保护工作簿结构(Protect Structure:=True),代价是用户不能再增删表。这类“保护”全部是防手滑,不是防破解,别把重要逻辑只放在隐藏表里。

3.3 行级清洗:插入、删空行、删重复、筛选后复制

行级操作是这份整理里最实用的部分,因为日常报表九成时间花在清洗上。一次插入多行有两种写法:Rows("2:5").Insert直接指定区间,或者循环调用Insert并配合CopyOrigin控制格式来源。删空行的关键在倒序和判空方式:

Sub 删除空行() Dim i As Long Application.ScreenUpdating = False ' 关掉屏幕刷新,几千行能快好几倍 ' 从最后一行往上走,正序删会跳过相邻的空行 For i = Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1 If Application.CountA(Rows(i)) = 0 Then Rows(i).Delete Next i Application.ScreenUpdating = True End Sub

用CountA(Rows(i)) = 0判断整行为空,比逐格检查省事;缺点是公式返回空字符串的单元格也算“非空”,表格里有大量=""公式时会漏删。这种情况改用SpecialCells(xlCellTypeBlanks)更准,但它对“看起来空其实有公式”的格无能为力,两种方法各有盲区,复习时记住盲区比记住写法更重要。

删重复行有两条路:RemoveDuplicates一行搞定,适合按某几列判重;需要保留“第一次出现”以外的复杂规则时,用字典(Scripting.Dictionary)或CountIf标记再删。高级筛选也能拿到不重复记录,AdvancedFilter Action:=xlFilterCopy, Unique:=True,好处是结果直接落到另一个区域,不动原数据。

筛选后的数据复制是另一个高频场景,普通Copy会把隐藏行一起带走,必须先取可见单元格:Range("A1:D100").SpecialCells(xlCellTypeVisible).Copy。注意如果筛选结果为空,SpecialCells会报错,前面加个判断。判断当前是否选中整行的写法也有讲究,靠Selection.Address和Selection.EntireRow.Address比较,比数行列数更可靠。限制滚动区域用ActiveWindow.ScrollArea = "A1:H50",适合做展示型表格,但记得退出时清空该属性,否则用户以为表格坏了。

3.4 工作簿:打开、判断已打开与保存的几种写法

工作簿层的引用同样有四种:名字Workbooks("报表.xlsx")、索引Workbooks(1)、ThisWorkbook(代码所在的那个文件,永远指向自己)、ActiveWorkbook(当前激活的,可能是任意一个)。这四个里最不容易出错的是ThisWorkbook,写在自己文件里的代码操作自己的表,一律用它。

判断某个工作簿是否已经打开,常规做法是遍历Workbooks集合逐个比对名字,找不到会抛错,所以外面要包错误处理。另一种是直接On Error Resume Next后取对象再判断Is Nothing,代码更短。打开文件用Workbooks.Open FileName:=路径,路径建议拼绝对路径,ThisWorkbook.Path加上文件名,比写死盘符可靠。

保存这块区分三个方法:Save原地保存、SaveAs另存为新文件(可以指定格式,比如只存数值不存宏就用xlOpenXMLWorkbook)、SaveCopyAs存一个副本但不改变当前文件状态。关闭时不弹保存提示有两个思路:Close SaveChanges:=False直接丢弃修改,或者在BeforeClose事件里把ThisWorkbook.Saved设成 True 再让系统自己关。前者干脆,后者适合需要保留用户操作痕迹的场景。

还有一个跨文件的技巧值得单独记:不打开工作簿就取数据。轻量做法是用公式引用,='C:\路径\[数据.xlsx]Sheet1'!A1,Excel 会自动去磁盘取值;再重一点用GetObject拿到对象再读;最重的是用 ADO 走 SQL 查询,适合几万行以上的汇总。三种方式的开销依次上升,按数据量选就行,别一上来就上 SQL。

4. Shape、Chart 与 Application:外围对象的考点分布

对象模型最外面这几层,平时用得少,但考试和实际做工具时反而是区分度最高的部分:图形、图表属于“看得见”的对象,代码写错立刻能发现;Application 属于“改环境”的对象,写错的时候往往整个 Excel 都变得怪怪的。这一章的复习策略是少记属性、多记“改完要还原”。

4.1 Shapes:新增、遍历删除与位置固定

工作表上的每一个图形、图片、艺术字、按钮,都是Shapes集合里的一个成员。新增用Shapes.AddShape(自选图形)、AddPicture(插入图片,参数是路径、是否链接、左边距、上边距、宽、高)、AddTextEffect(艺术字)。插入图片时最容易踩的坑是尺寸:不显式给宽高,图片会按原始像素铺开,一张手机照片就能把表格撑爆,务必按目标单元格的尺寸做等比缩放。

遍历删除必须倒序,这是图形操作里最经典的一处翻车:

Sub 清空所有图形() Dim i As Long For i = ActiveSheet.Shapes.Count To 1 Step -1 ' 正序删会跳过元素,因为集合在实时收缩 ActiveSheet.Shapes(i).Delete Next i End Sub

位置和旋转由Left、Top、Rotation三个属性控制,Rotation的单位是度,取值 0 到 360,负值会自动折算。让图形“跟着单元格走”靠的是Placement属性:xlMoveAndSize随单元格移动并改变大小、xlMove只移动不变形、xlFreeFloating完全自由。做模板时把图片设为xlMove最省心,行高列宽怎么调,图片都不会被拉变形。

自动插入图片的思路是“一列文件名、一个循环、按单元格定位”,每轮读一个路径,用AddPicture插到对应单元格的Left和Top位置,再把宽高缩放到单元格尺寸。文件名对不上就跳过,不要用On Error Resume Next一路闷头吞错,那样最后你不知道漏了哪几张。

4.2 Chart:生成、独立窗口与导出

图表对象有两层:ChartObjects是嵌在工作表里的图表容器,Chart是容器里的图表本体,两者的属性和方法不完全通用,混用会报“对象不支持该属性”。新建嵌入式图表用ChartObjects.Add(Left, Top, Width, Height),拿到容器后再操作.Chart;把图表放到独立窗口,用Chart.Location Where:=xlLocationAsChart,它会变成一张独立的图表工作表,适合做大屏展示。

多图表批量生成的关键是数据源用变量拼:Set rng = Range(Cells(1, 1), Cells(n, 2)),再用SetArea指定源区域,循环里只改变量不重写代码。导出图表用.Export Filename:=路径 & ".png", FilterName:="PNG",导出的图片是当前渲染效果,所以导出前要确认图表已完成重绘,必要时先DoEvents让界面刷一次。

图表这块有个反直觉的点:修改图表数据源比重新创建图表快得多,也稳定得多。批量做几十张图时,正确姿势是建一张模板图,复制容器、改数据源,而不是每张都从零 Add。

4.3 Application:定时器、组合键与状态栏

Application 层管的不是数据,是运行环境。取版本号Application.Version做兼容判断,取用户名Application.UserName做简单标记,改标题栏Application.Caption,改状态栏Application.StatusBar——最后一个记得用完清空,设成 False 就恢复默认,否则它会把“正在处理…”永久留在屏幕底部。

定时器靠OnTime实现,它的本质是“预约一个未来时刻执行某个过程”:

Public g_下次时间 As Date ' 模块级变量,记住预约的时刻,取消时要一模一样 Sub 启动定时() g_下次时间 = Now + TimeValue("00:00:10") Application.OnTime EarliestTime:=g_下次时间, _ Procedure:="刷新单元格", Schedule:=True End Sub Sub 刷新单元格() [A1].Value = Now 启动定时 ' 执行完再约下一次,形成循环 End Sub Sub 取消定时() On Error Resume Next ' 没预约过就取消会报错,忽略即可 Application.OnTime EarliestTime:=g_下次时间, _ Procedure:="刷新单元格", Schedule:=False End Sub

参数里的EarliestTime是“不早于这个时刻执行”,不是精确时刻;Schedule为 True 是预约、False 是取消。取消时必须传入和预约时完全相同的时刻和过程名,所以要把时间存进模块级变量——这是 OnTime 最容易被忽略的细节,凭记忆写个Now + 10 秒是取消不掉的。

屏蔽或改写组合键用Application.OnKey,比如把 Ctrl+S 换成本该由系统处理的保存逻辑,或者干脆屏蔽掉防止用户乱存。写这类代码的铁律是:在关闭工作簿的事件里一定记得Application.OnKey "^s"恢复(不带第二参数就是还原),否则这个键在整个 Excel 会话里都失效,用户只能重启——这属于典型的“自己挖坑自己跳”。

4.4 对话框:文件选择与内置对话框

对话框这一层很薄,但用法有明确分工。MsgBox负责告知和确认,InputBox负责收一个简单值,GetOpenFilename收文件路径,FileDialog是更现代的文件选择器(能多选、能指定类型、能选文件夹),Application.Dialogs(xlDialog...)则是调用 Excel 自带的内置对话框。

选型其实很简单:只收一个字符串就用 InputBox;要让用户选文件并用路径做后续处理,用GetOpenFilename,它只返回路径不打开文件;要选文件夹就用FileDialog的msoFileDialogFolderPicker。内置对话框的价值在于“用户熟悉”,比如Application.Dialogs(xlDialogPrint).Show直接弹出打印设置,省得自己画界面。它们的返回值需要判断,用户点取消时返回 False,不判断就会拿着空路径去Open,报一个不好看的错误。

5. 易错与易漏:几处写得出来但会翻车的细节

这一章收录的都是“语法没错、编译能过、跑起来也不报错,但结果就是不对”的写法。它们不会被编译器和 Excel 拦住,只能靠经验提前避开。

5.1 引用与边界:三处取数动作里的漏

易漏点一:End(xlUp)在整列为空时返回第 1 行。为什么漏:平时测试时表里总有数据,lastRow一切正常;等到空表跑一次,代码认为“最后一行是第 1 行”,接着去处理第 1 行,把标题行当数据改了。怎么补:取完立刻校验,If lastRow = 1 And IsEmpty(Cells(1, 1)) Then lastRow = 0,或者在取之前先看Application.CountA(Columns(1))是否为 0。更稳的做法是改用上一章那段Find倒序找,没找到就返回 Nothing,天然能区分“空表”和“只有一行”。

易漏点二:Find没找到时返回 Nothing,FindNext不判断起点会死循环。为什么漏:有数据时一切正常,空结果时rng.Row直接抛 91 错误,或者循环里永远找不到结束条件,Excel 卡住不动。怎么补:取到结果先判断If Not rng Is Nothing;需要在多个结果间循环时,用变量记住第一个命中的Address,每轮FindNext之后比对地址,一旦回到起点就退出。这套写法看着啰嗦,但它是唯一能保证不死循环的结构。

易漏点三:合并单元格只有左上角有值。为什么漏:肉眼看是一片合并区域,代码读中间那格得到的是Empty,于是统计出来的人数永远比实际少。怎么补:读取前先判断cell.MergeCells,是合并区域就用cell.MergeArea.Cells(1, 1).Value取值;如果需要“合并时把多个格子的文本拼起来”,那要先循环取原始值再调用Merge,因为合并动作本身会丢弃左上角以外的内容。反过来,取消合并要在合并前把内容记进数组,取消后再逐格填回,否则信息一次性丢光。

5.2 事件与运行环境:两处不报错但会失效的写法

易漏点四:在Worksheet_Change里改单元格导致无限递归,以及EnableEvents没恢复。为什么漏:事件代码里如果又写了修改单元格的语句,会再次触发 Change 事件,形成自己调自己;很多人加Application.EnableEvents = False挡掉,但过程中间一旦出错跳出,这个开关就永远关着,之后所有事件代码全部失灵,用户只会说“我的表突然不自动算了”。怎么补:固定写成开关成对出现的结构,中间用错误处理兜底恢复,On Error GoTo 收尾里先EnableEvents = True再退出。习惯养成之后,这类问题基本就绝迹了。

易漏点五:Select依赖活动工作表,保护后写入失败。为什么漏:手工按 F8 单步执行时,你眼前的表正好是活动表,Range("A1").Select一切正常;一旦从按钮触发、或者切到别的表再跑,立刻 1004 错误。同理,工作表被保护后,代码往锁定单元格写值会失败,而Protect UserInterfaceOnly:=True这个参数只在当前会话有效,文件关掉再打开,代码又没权限了。怎么补:一是戒掉 Select,需要操作哪个表就用With Worksheets("名字")包住;二是在Workbook_Open里重新执行一次带UserInterfaceOnly的保护,把这个动作变成打开文件时的固定流程。

6. 查漏补缺:把 70 个技巧压成一张自测表

复习到这一章,不建议再从头翻目录。更有效的做法是拿一张表逐项自测:能不能不看资料写出关键行、能不能说出这种写法在什么情况下会失效。下面这张表按“动作—关键写法—失效条件”三列压缩了全篇的主干,能全部口述出来,基本就算过了一遍。

动作关键写法失效条件
取最后一行Cells(Rows.Count,1).End(xlUp).Row整列为空时返回 1
找最后一个单元格Cells.Find("*", , , , xlByRows, xlPrevious)未判断 Nothing
多区域合并Union(r1, r2)两块区域不在同一张表
取连续数据块Range("A1").CurrentRegion中间有整行空白
只粘贴数值PasteSpecial xlPasteValues或rng.Value = rng.Value区域含合并单元格时赋值报错
批量读写数组v = rng.Value加Resize写回写回忘记 Resize 只填一格
倒序删除For i = n To 1 Step -1正序删会漏元素
删除工作表DisplayAlerts = False后Delete开关没恢复,后续不再弹提示
深度隐藏Visible = xlSheetVeryHidden被当成安全措施使用
只复制可见行SpecialCells(xlCellTypeVisible)筛选结果为空时报错
定时任务Application.OnTime配合模块级变量取消时时刻对不上,取消不掉
事件里改单元格EnableEvents = False成对恢复中途出错,开关永久关闭
保护后写值Protect UserInterfaceOnly:=True重开文件后失效,需在 Open 事件重设

自测的方式可以很土但很有效:把表里每一行的“关键写法”遮住,只留动作名,看能不能默写出来;再把“失效条件”遮住,看能不能说出它在什么场景下不灵。第二项比第一项重要得多,因为写出来只是及格线,知道什么时候不能用才是真的掌握了。我在整理这份东西的时候吃过一次亏:一段删空行的代码在测试表上跑得好好的,换到一张有大量=""公式的月报上,一行都没删掉,还因为正序循环把相邻两行合并成了一行,最后只能从备份恢复。从那以后我每次写涉及删除、覆盖、隐藏的代码,都强制走一遍“先备份一份副本、再在副本上跑、确认输出行数对得上”的流程,宁可多花两分钟。希望这些梳理能帮到你,少走几段我走过的路。

本文还有配套的精品资源,点击获取

返回列表