关于本文
本文是通过利用生成式 AI 的自动化工作流创建的。参考了 Microsoft Learn 中关于 ListObject 和 DataBodyRange 的定义,总结了区分 Excel 表格标题行和数据行的读取步骤。验证状态:📘 已确认官方规范,未在 Excel 实体机上运行
Excel VBA 表格为空时程序停止的原因:安全读取 ListObject 和 DataBodyRange
问题:上个月还能运行,这个月却停了
在针对 Excel 表格的 VBA 中,如果月初的台账中还没有数据,而你本打算确认数据量却去评估了 DataBodyRange.Rows.Count,就可能会报错。原因之一是将“至少存在一行数据”作为了隐式前提。只有标题的表格与有数据的表格,获取行数的条件是不同的。
对初学者来说容易混淆的是“表格存在但数据范围不存在”的情况。ListObject 代表表格对象本身,而 DataBodyRange 代表除标题以外的数据部分。因此,在判断表格是否为空时,需要分别确认表格的存在性以及是否存在数据行。
制作虚拟练习表格
在 Windows 版 Excel 桌面端打开一个新工作簿,将工作表命名为“确认用”。在 A1 输入“品目”,B1 输入“负责人”,C1 输入“状态”,选中表格并通过[插入]->[表格]创建一个带标题的表格。请在 Excel 的表格设计中将表格名称更改为“案件一览”。
首先只添加一行数据,输入 A2“资料整理”、B2“负责人A”、C2“未开始”之类的虚拟值。之后再删除数据行来测试空表格。没有必要使用企业的正式台账。另外,如果表格名称发生改变,VBA 将无法找到目标。请同时核对工作表名称和表格名称。
只读的安全示例
Option Explicit
Public Sub InspectTableRows()
Dim ws As Worksheet
Dim tbl As ListObject
Dim dataRange As Range
Dim rowCount As Long
Set ws = ThisWorkbook.Worksheets("確認用")
Set tbl = ws.ListObjects("案件一覧")
Set dataRange = tbl.DataBodyRange
If dataRange Is Nothing Then
Debug.Print "データ行はありません"
Exit Sub
End If
rowCount = dataRange.Rows.Count
Debug.Print "データ行数=" & CStr(rowCount)
Debug.Print "先頭行の品目=" & CStr(dataRange.Cells(1, 1).Value2)
End Sub
此代码不会更新表格。当有一行数据时,预期会显示两行:“数据行数=1”、“首行品目=资料整理”。在删除所有数据行的状态下,则会变成“没有数据行”这一行,设计上不会进入数据范围的 Rows.Count。这些是根据官方 API 的说明构建的预期结果,并非通过 Excel 实体机获取的日志。
为什么要确认 Nothing
Nothing 表示未设置对象引用的状态。在 VBA 中,确认作为对象的 Range 时要使用“Is Nothing”。这与检查字符串是否为空白的方法不同。如果表格内容为零行,DataBodyRange 可能会变成 Nothing。因此,需要先进行存在性确认,然后再按顺序使用 Rows.Count 或 Cells。
如果误以为 Count 会变为零而直接调用 DataBodyRange.Rows.Count,当 DataBodyRange 本身为 Nothing 时,就无法引用其属性。将“无数据”作为正常的业务状态来处理是设计的关键。对于月初的凭证一览、未开始的任务表、删除了已处理数据的检查表等可能存在零件数的报表,都需要这种思路。
ListObject 的 HeaderRowRange 与标题相关,DataBodyRange 与数据相关,TotalsRowRange 与汇总行相关。如果将这些混在一起计算行数,就会导致“把标题行当作数据行来数”之类的错误。通过使用 DataBodyRange,可以在代码中明确意图,但如果连表格本身都没有,则需要另外处理。
修改一处以确认行为
在放入一条数据的状态下运行,然后仅删除表格的数据行并重新运行。将其与预期的显示进行比对。接着再次向 A2 放入数值,确认是否能再次检测到单条数据。
接下来,我们故意将 VBA 中的表格名称更改为“案件一览2”来进行测试。在这种情况下,在进入 DataBodyRange 的检查之前,ListObjects 的名称解析会引发运行时错误。名称拼写错误与“空表格”的原因并不相同。在实际业务中,当缺少所需的表单或表格时,如果添加能够以清晰明了的消息进行中断的异常处理,将有助于运维人员更轻松地进行问题排查。
工作中切实有用的排查方法
如果表格中还留有一行空白行,则可能仍然存在 DataBodyRange。“行的存在”与“输入了业务项目”是两码事。如果连空白行也想排除,请不仅判断数量,还要判定必填列的值或状态的内容。相反,如果是“未输入行也属于管理对象”,那么轻率地排除空白行就会导致漏掉数据。
被 AutoFilter 隐藏的行也包含在名为 DataBodyRange 的表格数据范围内。在只想统计显示行的处理中,需要考虑过滤器和 SpecialCells 等其他因素。本次的示例是“表格的总数据行数”,而不是“当前屏幕上可见的行数”。如果这种区分含糊不清,报表的数据量就会对不上。
如果要将处理对象从 ThisWorkbook 更改为 ActiveWorkbook,请注意操作目标会根据活动状态而改变。如果设计的宏是从其他工作簿启动的,那么显式接收目标工作簿会更加安全。此外,在扩展到用于删除或加工的 VBA 之前,准备好零件、一件、多件、过滤中、带有汇总行这五个测试用例,将能减少事故的发生。
当只想知道行数时也可以使用 ListRows
本次的主题是在不存在 DataBodyRange 时的安全处理,不过,如果只是单纯想计算数据行的数量,也可以选择使用 ListRows 集合的 Count。ListRows 是表格数据行的集合,微软官方文档说明它不包含表头、汇总行和插入行。
Debug.Print "データ行数=" & CStr(tbl.ListRows.Count)
上述代码是在通过第一段代码成功获取到tbl之后执行的一行代码。它无法应对表格本身不存在时的异常。如果只是想知道数据行数是否为 0,那么比起从 DataBodyRange 接收 Range,这种方式更加直接。另一方面,如果要引用数据行中的单元格,则先获取 DataBodyRange 并检查其是否为 Nothing 再进行处理,这样意图会更明确。
切记不要认为“表格行数为零”、“存在一行但单元格为空”以及“因筛选而屏幕不可见”这三者是相同状态。前者是表格的数据行数,第二种是单元格的内容,第三种是显示状态。请在决定了到底想要统计什么之后再选择属性。
数量不符时的确认顺序
“表格的行数”与“输入了某种值的行数”有时并不一致。请分别创建仅删除了数据行的状态、仅清除了值但留下空白行的状态以及被筛选隐藏的状态来进行观察。DataBodyRange.Rows.Count 表示的是数据范围的行数,既不是已输入的记录数,也不是屏幕上的显示行数。在报表规格书中写明数量的定义非常重要。
如果找不到表格,首先应怀疑是工作表名称或表格名称被修改了。在直接将工作表名称写入代码的设计中,应对用户更改名称的能力较弱。在运维环境中,能够确认作业对象的消息以及保留目标文件名和表格名称的诊断日志会很有用。日志中没有必要输出包含客户名或个人信息的实际数据。
官方信息
这段简短的代码表明,应将空表视为正常情况而非异常。与仅在有数据时才运行的代码相比,能够在零条数据时安全退出的代码在日常运营中更加可靠。
