VBA中的循环错误处理

25

我刚开始学习VBA,尝试使用'On Error GoTo'语句,但是一直收到“索引超出范围”的错误。

我只想创建一个组合框,其中包含查询表的工作表名称。

    For Each oSheet In ActiveWorkbook.Sheets
        On Error GoTo NextSheet:
         Set qry = oSheet.ListObjects(1).QueryTable
         oCmbBox.AddItem oSheet.Name

NextSheet:
    Next oSheet

我不确定问题是否与在循环内嵌套 On Error GoTo 有关,或者如何避免使用循环。

7个回答

39

问题可能是您还没有从第一个错误中恢复。您不能在错误处理程序内部抛出错误。您应该添加一个 resume 语句,例如下面的内容,这样 VBA 就不再认为您在错误处理程序内:

For Each oSheet In ActiveWorkbook.Sheets
    On Error GoTo NextSheet:
     Set qry = oSheet.ListObjects(1).QueryTable
     oCmbBox.AddItem oSheet.Name
NextSheet:
    Resume NextSheet2
NextSheet2:
Next oSheet

3
错误:无错误的恢复 - Frank
简历对我来说是一个很好的提示!对我来说理解一个被 On Error Goto 引用的行标签被认为是一个错误处理程序非常重要。这样的程序必须使用 Resume、Exit sub、exit function 或 exit property 来关闭。 - Nord.Kind

16

作为处理类似于您示例代码中循环错误的一般方式,我更愿意使用:

on error resume next
for each...
    'do something that might raise an error, then
    if err.number <> 0 then
         ...
    end if
 next ....

3

实际上,Gabin Smith的答案需要进行一些修改才能正常工作,因为您无法在没有错误的情况下恢复。

Sub MyFunc()
...
    For Each oSheet In ActiveWorkbook.Sheets
        On Error GoTo errHandler:
        Set qry = oSheet.ListObjects(1).QueryTable
        oCmbBox.AddItem oSheet.name

    ...
NextSheet:
    Next oSheet

...
Exit Sub

errHandler:
Resume NextSheet        
End Sub

非常好的观点,Makah。 - the_web

3
如何呢?
    For Each oSheet In ActiveWorkbook.Sheets
        If oSheet.ListObjects.Count > 0 Then
          oCmbBox.AddItem oSheet.Name
        End If
    Next oSheet

有没有不是查询表的“列表对象”?我需要该工作表具有查询表。 - justin cress
@Justin,如果是这样,请为ListObjects(1).QueryTable Is Nothing添加一个测试 - 您的代码也没有进行此测试。 我示例的主要目的是在引用第一个元素之前检查ListObjects集合是否有任何元素。 - Joe

2

有一种控制循环错误处理的方法。创建一个名为here的字符串变量,并使用该变量确定单个错误处理程序如何处理错误。

代码模板如下:

On error goto errhandler

Dim here as String

here = "in loop"
For i = 1 to 20 
    some code
Next i

afterloop:
here = "after loop"
more code

exitproc:    
exit sub

errhandler:
If here = "in loop" Then 
    resume afterloop
elseif here = "after loop" Then
    msgbox "An error has occurred" & err.desc
    resume exitproc
End if

1
我不明白为什么你要在这里使用字符串。只需创建一个名为“inLoop”的布尔变量并将其赋值为True/False即可。 - baka_toroi

1
我不想为代码中的每个循环结构编写特殊的错误处理程序,因此我需要一种方法来使用我的标准错误处理程序找到问题循环,以便我可以为它们编写特殊的错误处理程序。
如果在循环中发生错误,通常我希望知道导致错误的原因,而不仅仅是跳过它。为了了解这些错误,我像许多人一样将错误消息写入日志文件。但是,如果在循环中发生错误,则写入日志文件是危险的,因为错误可能会在循环迭代的每次触发,并且在我的情况下,80000次迭代并不罕见。因此,我已经将一些代码放入我的错误记录函数中,以检测相同的错误并跳过将它们写入错误日志。
我用于每个过程的标准错误处理程序如下所示。它记录错误类型、发生错误的过程以及过程接收的任何参数(在本例中为FileType)。
procerr:
    Call NewErrorLog(Err.number, Err.Description, "GetOutputFileType", FileType)
    Resume exitproc

我的错误日志功能会将内容写入表格(我在使用ms-access),如下所示。它使用静态变量来保留先前的错误数据并将其与当前版本进行比较。第一个错误被记录,然后第二个相同的错误会将应用程序推入调试模式,如果我是用户或在其他用户模式下,则退出应用程序。
Public Function NewErrorLog(ErrCode As Variant, ErrDesc As Variant, Optional Source As Variant = "", Optional ErrData As Variant = Null) As Boolean
On Error GoTo errLogError

    'Records errors from application code
    Dim dbs As Database
    Dim rst As Recordset

    Dim ErrorLogID As Long
    Dim StackInfo As String
    Dim MustQuit As Boolean
    Dim i As Long

    Static ErrCodeOld As Long
    Static SourceOld As String
    Static ErrDataOld As String

    'Detects errors that occur in loops and records only the first two.
    If Nz(ErrCode, 0) = ErrCodeOld And Nz(Source, "") = SourceOld And Nz(ErrData, "") = ErrDataOld Then
        NewErrorLog = True
        MsgBox "Error has occured in a loop: " & Nz(ErrCode, 0) & Space(1) & Nz(ErrDesc, "") & ": " & Nz(Source, "") & "[" & Nz(ErrData, "") & "]", vbExclamation, Appname
        If Not gDeveloping Then  'Allow debugging
            Stop
            Exit Function
        Else
            ErrDesc = "[loop]" & Nz(ErrDesc, "")  'Flag this error as coming from a loop
            MsgBox "Error has been logged, now Quiting", vbInformation, Appname
            MustQuit = True  'will Quit after error has been logged
        End If
    Else
        'Save current values to static variables
        ErrCodeOld = Nz(ErrCode, 0)
        SourceOld = Nz(Source, "")
        ErrDataOld = Nz(ErrData, "")
    End If

    'From FMS tools pushstack/popstack - tells me the names of the calling procedures
    For i = 1 To UBound(mCallStack)
        If Len(mCallStack(i)) > 0 Then StackInfo = StackInfo & "\" & mCallStack(i)
    Next

    'Open error table
    Set dbs = CurrentDb()
    Set rst = dbs.OpenRecordset("tbl_ErrLog", dbOpenTable)

    'Write the error to the error table
    With rst
        .AddNew
        !ErrSource = Source
        !ErrTime = Now()
        !ErrCode = ErrCode
        !ErrDesc = ErrDesc
        !ErrData = ErrData
        !StackTrace = StackInfo
        .Update
        .BookMark = .LastModified
        ErrorLogID = !ErrLogID
    End With


    rst.Close: Set rst = Nothing
    dbs.Close: Set dbs = Nothing
    DoCmd.Hourglass False
    DoCmd.Echo True
    DoEvents
    If MustQuit = True Then DoCmd.Quit

exitLogError:
    Exit Function

errLogError:
    MsgBox "An error occured whilst logging the details of another error " & vbNewLine & _
    "Send details to Developer: " & Err.number & ", " & Err.Description, vbCritical, "Please e-mail this message to developer"
    Resume exitLogError

End Function

注意,错误记录器必须是应用程序中最坚固的功能,因为应用程序无法在错误记录器中优雅地处理错误。因此,我使用NZ()来确保空值不能悄悄进入。请注意,我还将[loop]添加到第二个相同的错误中,以便我知道首先要查看错误过程中的循环。

0

关于什么?

If oSheet.QueryTables.Count > 0 Then
  oCmbBox.AddItem oSheet.Name
End If 

或者

If oSheet.ListObjects.Count > 0 Then
    '// Source type 3 = xlSrcQuery
    If oSheet.ListObjects(1).SourceType = 3 Then
         oCmbBox.AddItem oSheet.Name
    End IF
End IF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接