【问题标题】:Is there a way to programmatically determine if error handling has been turned on in VBA?有没有办法以编程方式确定是否在 VBA 中打开了错误处理?
【发布时间】:2020-03-27 14:45:29
【问题描述】:

背景

你好!我被困在试图在效率和稳健性之间做出决定。这是Excel中VBA的问题。

我有一个函数GetTable(如下),它接受一个表名并在我的工作簿中返回相应的 ListObject。与显式调用 ThisWorkbook.Worksheet.ListObjects("strTableName") 相比,我更喜欢这种方法,因为如果表格移动到不同的工作表中,它可以提供编码灵活性。

最初这是通过遍历每个 Worksheet 并检查每个 ListObject 是否具有与提供的输入匹配的名称来完成的。这是下面代码的Option 1。这种方法效果很好,但效率不是特别高,如果您有一个包含许多工作表和表格的大型工作簿,并且您在宏中抓取多个表格,它可能会减慢速度。

为了提高效率,我改为Option 2,它显式调用每张纸上的表格。如果该表不存在,则它会引发错误,并且错误处理允许它直接移动到下一个工作表。当打开错误处理时,这非常适合正常使用工作簿。但是,如果在调试过程中关闭错误处理,这会变得很痛苦,因为代码总是会卡在这里。

问题

  1. 有没有一种方法可以以编程方式判断错误处理是打开还是关闭,以便函数可以根据具体情况在这两种方法之间切换?我意识到这可能会有风险,因为它会在调试期间运行不同的代码,但我仍然想知道这是否可能。
  2. 如果没有,是否有其他方法可以完成与选项 #2 类似的方法(如果您有想法,可以使用更有效的方法!)而不会引发错误?

我知道这个函数的效率提升通常并不重要,除非你有大量的表,但我将它作为一个更大的函数库的一部分分享给不太懂代码的同事,所以他们可能无法有效地使用它并包括它在大循环中。此外,找到正确的方法对于应用到其他功能可能会派上用场。

提前感谢您,祝您身体健康!

代码

Function GetTable(strTableName As String) As ListObject
'This function searches the workbook for a table with the exact name strTableName
'Returns the table object
'If nothing found then display message
On Error Resume Next

Dim sht As Worksheet
Dim tbl As ListObject

'#Option 1: Slower but doesn't throw errors
'For Each sht In ThisWorkbook.Worksheets
'    For Each tbl In sht.ListObjects
'        'Debug.Print sht.Name & " " & tbl.Name      'uncomment to print all table names
'
'        If LCase(tbl.Name) = LCase(strTableName) Then
'            Set GetTable = tbl
'            Exit Function
'        End If
'    Next tbl
'Next sht

'#Option 2: More efficient but causes problems when debugging
For Each sht In ThisWorkbook.Worksheets
    
    Set GetTable = sht.ListObjects(strTableName) 'Generates runtime error 9 if table doesn't exist on sheet
    
    If Err.Number = 0 Then Exit Function  'No error means we've found the answer
    
    Err.Clear

Next sht

'If the code reaches this point it means the table wasn't found.
'This may have negative implications depending on where this function is called.
'This message gives the user an out
Dim ans As Byte
ans = MsgBox("Could not find table with name '" & strTableName & "'." & vbNewLine & vbNewLine & _
      "Would you like to abort code?", vbCritical + vbYesNo, "Table not found")
      
If ans = vbYes Then End

'Set GetTable = Nothing   '#This is redundant

End Function

【问题讨论】:

  • 使用错误处理。在 Set GetTable =... 行之前,添加以下内容:On Error Resume Next。就在(If Err.Number = 0... 行之前)之后添加以下内容:On Error Goto 0。现在,即使该行抛出错误,您的代码也不会停止,If Err.Numbre... 行将被触发
  • @Zac 感谢您的建议,但是当错误处理关闭时,这并不能解决问题。 On Error Resume Next 也已经存在于函数的开头,因此它已经生效。将它移到 Set GetTable 之前并没有什么不同。
  • 我错过了最初的On Error Resume Next 声明,你是对的,如果你已经拥有它,那么没有任何意义。只是出于好奇,为什么你的错误处理关闭了?
  • 追踪意外(但已处理)错误的根源并解决根本问题。
  • 您可以使用“slow”选项并在您的函数中添加一个静态字典以按名称缓存表:它只会在您第一次调用它时“slow”。当然,如果您要添加新表,则需要对其进行管理...或者将错误处理程序更改为 If Not DEBUG Then On Error Goto whatever 并拥有一个全局 DEBUG 标志变量。

标签: excel vba error-handling


【解决方案1】:

这就是我所说的缓存:

Function GetTable(ByVal strTableName As String, _
                         Optional reset As Boolean = False) As ListObject
    Static dict As Object 'Static, so persists between calls
    Dim sht As Worksheet
    Dim tbl As ListObject, nm
    If reset Then Set dict = Nothing  '<< clear the cache
    If dict Is Nothing Then
        Set dict = CreateObject("scripting.dictionary")
        For Each sht In ThisWorkbook.Worksheets
            For Each tbl In sht.ListObjects
                nm = LCase(tbl.Name)
                If Not dict.exists(nm) Then dict.Add nm, tbl
            Next tbl
        Next sht
    End If
    strTableName = LCase(strTableName)
    If dict.exists(strTableName) Then Set GetTable = dict(strTableName)
End Function

第一次调用它时,它会扫描所有的列表对象,但之后它会使用字典作为查找对象。

您需要注意何时可能需要清除缓存,以考虑添加或删除的列表对象。

【讨论】:

    【解决方案2】:

    If ans = vbYes Then End 替换为替换If ans = vbYes Then Exit Function,因为End 是代码的“自我毁灭”按钮。查看 MS documentation 以进一步阅读。

    End 语句会突然停止代码执行,而不调用 Unload、QueryUnload 或 Terminate 事件或任何其他 Visual Basic 代码。您在窗体和类模块的 Unload、QueryUnload 和 Terminate 事件中放置的代码不会被执行。从类模块创建的对象被销毁,使用 Open 语句打开的文件被关闭,程序使用的内存被释放。其他程序持有的对象引用无效。

    PS如果您正在谈论可以通过 VBE 设置的错误选项,您可能会不走运,请参阅 Set the appropriate error handling level

    【讨论】:

    • 您的链接编辑正是我想要找到的,谢谢。很高兴知道这是其他应用程序中的一个选项,而不是 Excel。对于您的第一点,我确信您已经发现 End 是这里想要的声明。退出函数是否则会发生什么。
    • 好的,我不确定End 是否有意。通常应该避免它,除非你真的想完全结束程序。
    【解决方案3】:

    更多是对您关于更有效方法的问题的评论......

    我个人不喜欢这种方法,因为不合格的Range 调用和Activate,但您可以使用如下函数,而不是循环遍历所有工作表和表格:

    Private Function GetTable(ByVal TableName As String, Optional ByVal wb as Workbook) As ListObject
        If wb Is Nothing Then Set wb = ThisWorkbook
        wb.Activate
        Set GetTable = Range(TableName).ListObject
    End Function
    

    当然,如果工作簿中不存在具有该名称的表,则使其更加健壮...(这基本上意味着我还没有回答您问题的症结所在)。

    【讨论】:

    • 我没有意识到您可以通过这种方式访问​​ ListObjects,所以谢谢您,这肯定更有效。我不得不同意不喜欢.Activate 和不合格的Range。不过有一个问题,您没有指定ByVal TableName as String 是否有原因?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-15
    • 2011-09-29
    • 1970-01-01
    • 2022-10-24
    • 2018-02-21
    • 2013-07-16
    • 1970-01-01
    相关资源
    最近更新 更多