#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