обработка ошибок vba в цикле

#vba #error-handling

#vba #обработка ошибок

Вопрос:

Новичок в vba, пытаюсь выполнить переход к ошибке, но я продолжаю получать ошибки «индекс вне диапазона».

Я просто хочу создать поле со списком, которое заполняется именами рабочих листов, содержащих таблицу запросов.

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

NextSheet:
    Next oSheet
  

Я не уверен, связана ли проблема с вложением GoTo ошибки On внутри цикла или с тем, как избежать использования цикла.

Ответ №1:

Вероятно, проблема в том, что вы не возобновили работу с первой ошибки. Вы не можете выдать ошибку из обработчика ошибок. Вы должны добавить в инструкцию 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
  

Комментарии:

1. Ошибка: возобновление без ошибок

2. Резюме было хорошим советом для меня! Для меня было важно понять, что метка строки, на которую ссылается Goto при ошибке, считается процедурой обработки ошибок. И такие процедуры должны быть закрыты с помощью Resume , Exit sub, exit function или exit propoerty .

Ответ №2:

В качестве общего способа обработки ошибок в цикле, подобного вашему образцу кода, я бы предпочел использовать:

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

Ответ №3:

Как насчет:

     For Each oSheet In ActiveWorkbook.Sheets
        If oSheet.ListObjects.Count > 0 Then
          oCmbBox.AddItem oSheet.Name
        End If
    Next oSheet
  

Комментарии:

1. разве нет «объектов списка», которые не являются таблицами запросов? Мне нужно, чтобы на листе была таблица запросов.

2. @Justin, если это так, добавьте тест для ListObjects(1).QueryTable Is Nothing — в вашем коде также не было этого теста. Основная цель моего примера — проверить, есть ли в коллекции ListObjects какие-либо элементы, прежде чем разыменовывать первый элемент.

Ответ №4:

На самом деле ответ Габина Смита необходимо немного изменить, чтобы он работал, потому что вы не можете возобновить работу без ошибки.

 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
  

Ответ №5:

Существует другой способ управления обработкой ошибок, который хорошо работает для циклов. Создайте вызываемую строковую переменную 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" amp; err.desc
    resume exitproc
End if
  

Комментарии:

1. Я не понимаю, почему вы здесь используете строку. Просто создайте логическое значение с именем «inLoop» и присвоите ему значение True / False.

Ответ №6:

Я не хочу создавать специальные обработчики ошибок для каждой структуры цикла в моем коде, поэтому у меня есть способ находить проблемные циклы, используя мой стандартный обработчик ошибок, чтобы затем я мог написать для них специальный обработчик ошибок.

Если в цикле возникает ошибка, я обычно хочу знать, что вызвало ошибку, а не просто пропустить ее. Чтобы узнать об этих ошибках, я записываю сообщения об ошибках в файл журнала, как это делают многие люди. Однако запись в файл журнала опасна, если в цикле возникает ошибка, поскольку ошибка может возникать при каждом повторении цикла, и в моем случае 80 000 итераций не редкость. Поэтому я ввел некоторый код в свою функцию регистрации ошибок, которая обнаруживает идентичные ошибки и пропускает запись их в журнал ошибок.

Мой стандартный обработчик ошибок, который используется для каждой процедуры, выглядит следующим образом. Он записывает тип ошибки, процедуру, в которой произошла ошибка, и любые параметры, полученные процедурой (в данном случае тип файла).

 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: " amp; Nz(ErrCode, 0) amp; Space(1) amp; Nz(ErrDesc, "") amp; ": " amp; Nz(Source, "") amp; "[" amp; Nz(ErrData, "") amp; "]", vbExclamation, Appname
        If Not gDeveloping Then  'Allow debugging
            Stop
            Exit Function
        Else
            ErrDesc = "[loop]" amp; 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 amp; "" amp; 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 " amp; vbNewLine amp; _
    "Send details to Developer: " amp; Err.number amp; ", " amp; Err.Description, vbCritical, "Please e-mail this message to developer"
    Resume exitLogError

End Function
  

Обратите внимание, что регистратор ошибок должен быть наиболее защищенной функцией в вашем приложении, поскольку приложение не может корректно обрабатывать ошибки в регистраторе ошибок. По этой причине я использую NZ(), чтобы убедиться, что нули не могут проникнуть. Обратите внимание, что я также добавляю [цикл] ко второй идентичной ошибке, чтобы я знал, что сначала нужно просмотреть циклы в процедуре ошибки.

Ответ №7:

О чем?

 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