2016-04-04 8 views
2

Ich benutze MZ Tools für Excel VBA bei der Arbeit, und ich benutze ihre automatische Fehlerhandler-Funktion für die meisten meiner Verfahren, weil es mir ermöglicht, leicht meinen Kontakt Informationen in der Fehlermeldung und automatisch Display-Warnungen und Bildschirmaktualisierung wieder einschalten. Wenn jedoch ein Fehlerhandler in VBA verwendet wird, wird es schwierig, die genaue Codezeile zu finden, die den Fehler ausgelöst hat, insbesondere in einer längeren Prozedur. Der Standardwert war die einzige Möglichkeit, einen benutzerdefinierten Error-Handler zu verwenden und die Codezeile zu erhalten, die den Fehler ausgelöst hat, indem diese beiden Zeilen am Ende des Error-Handlers hinzugefügt wurden (damit die Problem-Zeile erneut ausgeführt wird) die Standard-Fehlerbehandlung nach dem Handler benutzerdefinierten Fehlers hatte seine Arbeit) getan:Wie zu vermeiden, Kaskadierung bei der Verwendung von Standard-Fehlerhandler zusätzlich zu benutzerdefinierten Fehlerhandler

On Error GoTo 0 
Resume 

das funktioniert gut, wenn es nur eine Fehler-Handler ist; Der Benutzer muss durch ein zusätzliches Dialogfeld klicken, aber ich kann normal debuggen, während die in meinen benutzerdefinierten Fehlerhandlern integrierte Funktionalität beibehalten wird. Wenn sowohl die aufrufende als auch die untergeordnete Routine unterschiedliche Fehlerbehandlungsroutinen haben, beginnt der Benutzer eine lange Kaskade ähnlich aussehender Dialogfelder zu erhalten. Genau, ich bekomme 1 + n! Dialogfelder, wobei n die Anzahl der Ebenen von Unterprogrammen mit Fehlerbehandlern ist.

Der einfachste Weg, um das Problem zu veranschaulichen, wenn ich die erste Routine laufen, bekomme ich 4 Fehlermeldungen statt nur 2:

Sub TstErrHndlr() 

    On Error GoTo TstErrHndlr_Error1 

    Call TstErrHndlrA 

    On Error GoTo 0 
    Exit Sub 

TstErrHndlr_Error1: 
    Application.ScreenUpdating = True 
    Application.DisplayAlerts = True 
    Call MsgBox("Error " & Err.Number & " (" & Err.Description _ 
      & ") in procedure TstErrHndlr " _ 
      & "of Module Create_Package." _ 
      & " Contact [My Name] for assistance " _ 
      & "([email protected], (123)456-7890)") 
    On Error GoTo 0 
    Resume 
End Sub 

Sub TstErrHndlrA() 

    On Error GoTo TstErrHndlrA_Error1 

    Dim X As Double 
    X = 1/0 

    On Error GoTo 0 
    Exit Sub 

TstErrHndlrA_Error1: 
    Application.Calculation = xlCalculationAutomatic 
    Application.ScreenUpdating = True 
    Application.DisplayAlerts = True 
    Call MsgBox("Error " & Err.Number & " (" & Err.Description _ 
      & ") in procedure TstErrHndlrA " _ 
      & "of Module Create_Package." _ 
      & " Contact [My Name] for assistance " _ 
      & "([email protected], (123)456-7890") 
    On Error GoTo 0 
    Resume 
End Sub 

Nachdem im Debug-Modus durch den Code gehen, scheint es wie wann immer Eine Prozedur wird von einer anderen Prozedur aufgerufen. Je nachdem, welcher Fehlerhandler in der aufrufenden Funktion aktiviert wurde, wird der Fehlerhandler, der von der Zeile On Error GoTo 0 aktiviert wird, unabhängig davon, wie oft er wiederholt wird. Ich würde gerne wissen, warum sich VBA so verhält, wie man es so verhält und/oder ob es besser ist, mein Ziel zu erreichen, die Codezeile zu bekommen, die einen Fehler ausgelöst hat, während man einen Fehlerhandler benutzt. Ich weiß, dass ich zum Standard-Fehlerhandler zurückkehren könnte, bevor eine Funktion mit einem neuen Fehlerhandler aufgerufen wird (z. B. On Error GoTo 0: Call TstErrHndlrA), aber das führt zu hässlichem, verwirrendem Code und behandelt keine Fehler beim Funktionsaufruf.

+1

'OEG0' deaktiviert den Fehlerhandler für die aufgerufene Routine. Bei einem zweiten Fehler wird daher die Steuerung an die aufrufende Routine und ihren Fehlerbehandler zurückgegeben. Ich schlage vor, dass Sie bedingte Kompilierung für Ihre On Error-Anweisungen verwenden, oder Zeilennummern hinzufügen und 'Erl' verwenden. – Rory

+0

@Rory Ich bin mir nicht sicher, ob ich den Vorschlag zur bedingten Kompilierung verstehe - Meinst du, dass ich bedingte Kompilierung verwenden würde, um meine Fehlerbehandlungsroutinen zu deaktivieren, wenn ich der Benutzer bin, und sie für andere Benutzer zu aktivieren? Oder gibt es eine andere Möglichkeit, wie eine bedingte Kompilierung dieses Problem lösen könnte? – nateAtwork

Antwort

2

ich schlage vor, eine Umstrukturierung Ihres Handler Fehler wie folgt

  • einen Debug-Modus für den eigenen Gebrauch hinzufügen, die in der Fehlerbehandlungsroutine bricht und bietet die Möglichkeit einer Wiederaufnahme der Linie den Fehler verursacht
  • Nur um zu sehen, erhöhen das Fehler-Popup auf der Ebene, die den tatsächlichen Fehler verursacht
  • Reset-Anwendungseigenschaften auf der obersten Ebene nur
  • Lower-Level-Routine Anrufe passieren, nicht behandelte Fehler

.

Option Explicit 

' Debug Mode Flag (or you could use Conditional Compilation) 
' Set to TRUE for developer mode debugging 
Const DebugMode As Boolean = False ' True 

Sub TstErrHndlr() 
    On Error GoTo TstErrHndlr_Error1 

    TstErrHndlrA 

Exit Sub 
TstErrHndlr_Error1: 
    Application.Calculation = xlCalculationAutomatic 
    Application.ScreenUpdating = True 
    Application.DisplayAlerts = True 

    ' display message if error is raised in this module 
    If Err.Source = Application.VBE.ActiveVBProject.Name Then 
     MsgBox "Error " & Err.Number & " (" & Err.Description & ")" & vbLf & _ 
      "in procedure TstErrHndlr" & vbLf & _ 
      "Contact [My Name] for assistance " & _ 
      "([email protected], (123)456-7890)" 
    End If 
    ' Break in Debug mode 
    If DebugMode Then 
     Debug.Assert False 
     Resume 
    End If 

End Sub 

Sub TstErrHndlrA() 
    On Error GoTo TstErrHndlrA_Error1 

    Dim X As Double 
    X = 1/0 

Exit Sub 
TstErrHndlrA_Error1: 
' These should be handled at top level for unhandled errors only 
' Application.Calculation = xlCalculationAutomatic 
' Application.ScreenUpdating = True 
' Application.DisplayAlerts = True 

    ' display message if error is raised in this module 
    If Err.Source = Application.VBE.ActiveVBProject.Name Then 
     MsgBox "Error " & Err.Number & " (" & Err.Description & ")" & vbLf & _ 
      "in procedure TstErrHndlrA" & vbLf & _ 
      "Contact [My Name] for assistance " & _ 
      "([email protected], (123)456-7890)" 
    End If 
    ' Break in Debug mode 
    If DebugMode Then 
     Debug.Assert False 
     Resume 
    End If 
    ' Pass unhandled errors up the tree 
    Err.Raise Err.Number, "TstErrHndlrA", Err.Description 
End Sub 

Mit Debug-Modus OFF der Benutzer erhält ein Popup identifiziert den Fehler und Routine es

Mit Debug-Modus ON Sie auch eine Pause in der Routine erhalten den Fehler verursacht in

auftritt, und die Möglichkeit, a Fahren Sie mit der Zeile fort, die den Fehler verursacht.(Oder drücken Sie Strg-F9 über den Lebenslauf zu Schritt)

+0

Große Lösungen insgesamt, aber können Sie erklären, warum es wichtig ist, dass ich den unbehandelten Fehler überlasse? Ich denke, es gibt Zeiten, in denen ich dies tun möchte, wenn ich eine komplizierte Fehlerbehandlung hatte, um das Risiko zu reduzieren, mehrere Fehlerhandler zu aktualisieren, wenn ich später etwas ändere, aber ich sehe keinen Schaden in der Redundanz, wenn ich ScreenUpdating habe und DisplayAlerts haben in meinem Standard-MZ Tools-Fehlerhandler sowieso den Wert true gesetzt. – nateAtwork

+1

Allgemeine Argumentation für Fehler: Wenn eine Routine sich von einem Fehler erholen kann, behandeln Sie sie und fahren Sie fort. Wenn es sich nicht erholen kann, vergib es. Die aufrufende Routine _kann in der Lage sein, zu verarbeiten und fortzufahren, ohne die Ausführung anzuhalten. Derselbe Grund, warum Anwendungseigenschaften nicht zurückgesetzt werden: Wenn die aufrufende Routine den Fehler behandeln kann, sollten diese Dinge nicht zurückgesetzt werden –

1

Von MSDN On Error Seite:

Ein „freigegeben“ Fehlerbehandlung ist eine, die eingeschaltet wird durch eine On Error-Anweisung; Ein "aktiver" Fehlerhandler ist ein aktivierter Handler, der sich im Prozess der Behandlung eines Fehlers befindet. Wenn ein Fehler auftritt, während eine Fehlerbehandlungsroutine aktiv ist (zwischen dem Auftreten des Fehlers und einer Resume-, Exit Sub-, Exit-Funktion oder Exit-Eigenschaft), kann der Fehlerhandler der aktuellen Prozedur den Fehler nicht behandeln. Die Steuerung kehrt zur aufrufenden Prozedur zurück. Wenn die aufrufende Prozedur einen aktivierten Fehlerhandler hat, wird aktiviert, um den Fehler zu behandeln. Wenn auch der Fehlerhandler der aufrufenden Prozedur aktiv ist, wird die Steuerung durch vorherige Aufrufe der Prozedur fortgesetzt, bis ein aktivierter, aber inaktiver Fehlerhandler gefunden wird. Wenn nicht inaktiv ist, wurde ein Fehlerhandler gefunden, der Fehler ist fatal bei Punkt, an dem es tatsächlich aufgetreten ist. Jedes Mal, wenn der Fehlerbehandler die Steuerung an eine aufrufende Prozedur zurückgibt, wird diese Prozedur zur aktuellen Prozedur . Sobald ein Fehler von einem Fehlerbehandler in einer Prozedur behandelt wird, wird die Ausführung in der aktuellen Prozedur an dem durch die Resume-Anweisung angegebenen Punkt fortgesetzt.

Also um zu antworten "Warum verhält sich VBA so?": Weil sie es so gemacht haben.

Damit es sich nicht auf diese Weise verhält, müssen Sie (wie Sie bereits erwähnt haben) den aktuellen Error-Handler deaktivieren, bevor Sie den Sub/Function aufrufen.

Mit ERL als @Rory erwähnt erhalten Sie die genaue Zeile, wo Ihr Code fehlschlägt, und Sie möglicherweise On Error Goto -1 in einer allgemein generischen Fehlerabfangroutine verwenden können. Es kommt wirklich darauf an, vorsichtig zu sein, andere Subs/Funktionen aufzurufen oder Funktionen zu haben, die einen Fehlercode als ihren Wert zurückgeben können (dh den Fehler manuell aufblubbern). Zum Beispiel ist hier eine Funktion, die den Fehler als Wert der Funktion zurückgibt, anstatt zu versuchen, irgendeine Art von Ausnahme während des Funktionsaufrufs auszulösen. Sie können auch feststellen, dass einige der aufgerufenen Funktionen möglicherweise ebenfalls Fehler zurückgeben.

Public Function SetTask(ByVal strHost As String, strUser As String, strDomain as String, strPass As String) As String 
Dim service As Object 
Dim rootFolder As Object 
Dim taskDefinition As Object 
Dim strCMD As String 
Dim strResult As String 

On Error GoTo TaskNotSet 
SetTask = "Task Not Set" 

'Open the firewall 
strResult = OpenFirewall (strHost) 
If strResult <> "Ok" Then 
    SetTask = "Error Opening Firewall (" & err.Number & ") " & err.Description 
    Exit Function 
End If 

Set service = CreateObject("Schedule.Service") 
service.Connect strHost, strUser, strDomain, strPass 

Set rootFolder = service.GetFolder("\") 
Set taskDefinition = service.newtask(0) 
taskDefinition.XmlText = TaskXML 
Call rootFolder.RegisterTaskDefinition("Weekly VMC Inventory", taskDefinition, 6, , , 3) 

'Close the firewall 
strResult = CloseFirewall (strHost) 
If strResult <> "Ok" Then 
    SetTask = "Error Closing Firewall (" & err.Number & ") " & err.Description 
    Exit Function 
End If 

SetTask = "Task Set" 
Set taskDefinition = Nothing 
Set rootFolder = Nothing 
Set service = Nothing 
Exit Function 

TaskNotSet: 
CloseFirewall (strHost) 
SetTask = "Error Setting Task (" & err.Number & ") " & err.Description 
Set taskDefinition = Nothing 
Set rootFolder = Nothing 
Set service = Nothing 
End Function 
Verwandte Themen