Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Exception Handling

Status
Not open for further replies.

Elysium

Programmer
Aug 7, 2002
212
US
Development Platform: Visual Basic 6

If someone knows of a FAQ that will answer my question, please post it. I didn't find one elaborate enough to do so.

Okay, here's what's happening. User types in a PO Number into a field, and on the Lost Focus event, a function in the form attempts to look up values and place them in subsequent controls. The process map is:

Out: frmMain ---> clsDB_Ops ---> clsApp_Configs ---> loads XML

In: XML data ---> clsApp_Configs ---> clsDB_Ops ---> frmMain

In the event that the XML data cannot be returned to the clsDB_Ops class because of some sort of error, I want to handle that nicely back at the frmMain.

How is this done using custom exception handling?

Regards,


Randy
[afro]
 
Use a Try...Catch or a GoTo ErrorHandler pointer

The Try Catch is like this

Try
Do Something

Catch ex as Exception
Oops...something went wrong

End Try

You can also add a Finally in there that will always run whether there is an error or not.

The GoTo ErrorHandler option is like this:

On Error GoTo ErrorHandler

DoSomethingToCauseanError()
...
OtherProcessing
...

ErrorHandler:
MsgBox("No ice cream for joo")

There's a lot of tutorials avaialable for error handling if you google it. Make sure to add Visual Basic into the search params tho.
 
Thanks, but I'm using VB6. Unless it can use structured exception handling, I'm relegated to unstructured exception handling. All of the examples that I can find on Google are very basic and don't show how to bubble an error up to a form that happened in a class two levels down.

Randy
[afro]
 
You can still use the suggestions, you just have to change them to functions so you can return a value.

errorcode = DoSomething()
If errorcode = -1
ProcessErrorfromdosomething
End If

Public Class DoSomething()
Try
...
Catch
Return -1
End Try
 
macleod1021

Try ... Catch is a good idea ... but it's a VB.Net idea. In VB 5 & 6 it needs to be something like
Code:
On Error Resume Next
X = 10 / 0     [COLOR=green]' Raise an Error[/color]
If Err.Number <> 0 Then
   X = 0       [COLOR=green]' Fix It[/color]
   Err.Clear
End If

[small]No! No! You're not thinking ... you're only being logical.
- Neils Bohr[/small]
 
I couldn't remember if that 6.0 or .net. That's why I included the goto errorhandler reference. Personally I avoid resume next like the plague. VB 6.0 has a nasty habit of keeping the current settings. If you put in a resume next statement and don't explicitly end it; VB will continue that throughout the rest of the code.
 
True ... it has it's dangers ... but then, that's why they pay us the really big bucks, isn't it? Hazard pay and all.

[small]No! No! You're not thinking ... you're only being logical.
- Neils Bohr[/small]
 
VB6 can do a fair approximation of Try/Catch/Finally
Code:
Function Fake(blah as long) As Long
Try:
  On Error GoTo Catch
 'Main code goes here
  X = blah / 0     

Finally:
  'Code for cleanup/exit goes here
  Fake = x
  Exit Function

Catch:
  'Error handling goes here
  'Note: if you want to re-raise the error to
  '      bubble it up to the calling function,
  '      be sure to first do "On Error Goto 0"
  Fake = vbObjectError
  Resume Finally
End Function
The "try" label has no functional purpose in this example, just for a visual cue to the curlybrace guys
 
Am I right in using this structure?:

*************************
frmMain
*************************
Code:
Private Function fillDeliveryFields() As Boolean
Dim oDBO As New clsDB_Ops

    With oDBO
        Debug.Print Err.Number
        If Err.Number <> 0 Then
            Err.Raise Err.Number, Err.Source, Err.Description
        Else
            .Data_Source = Import
            .Filter_Statement = "WHERE po_number='" & txtDelivery(0) & "'"
            Set oTemp_Rec = oDBO.returnRecord()
        End If
    End With

End Function

*************************
clsDB_Ops
*************************
Code:
Private Sub Class_Initialize()
Dim Import_Source As String
Dim Export_Source As String

On Error GoTo 0

    'Create and form our database connections
    If Err.Number = 0 Then
        Import_Source = oAC.Import_DSN
    Else
        Err.Raise Err.Number, Err.Source, Err.Description
    End If
    
    If Err.Number = 0 Then
        Export_Source = oAC.Export_DSN
    Else
        Err.Raise Err.Number, Err.Source, Err.Description
    End If

End Sub

*************************
clsApp_Configs
*************************
Code:
Public Property Get Import_DSN() As String

    Import_DSN = returnXMLNodeValue("Database_Config", "DSN_Import")
    
End Property

Private Function returnXMLNodeValue(ByVal sGroup As String, ByVal sNodeName As String) As String
'----------------------------------------------------------------
'Purpose: Parses the XML Configuration file
'Returns: Configuration key value
'----------------------------------------------------------------

Dim oDom As New MSXML2.DOMDocument50
Dim oDB_Configs As IXMLDOMNodeList
Dim oKeys As IXMLDOMNode
Dim bOK As Boolean, bFoundKey As Boolean

'On Error GoTo Catch

    'Load the XML document
    bOK = oDom.Load(App.Path & "\App_Configs.xml")
    bFoundKey = False
    
    'Loop through the nodes to fill properties
    If bOK Then
        Set oDB_Configs = oDom.selectNodes("//" & sGroup & "/*")
        
        'If even one Group fails, we need to bail
        If oDB_Configs.length <> 0 Then
            
            'Search the keys and return the value for the one we are
            'looking for.
            For Each oKeys In oDB_Configs
                If oKeys.nodeName = sNodeName Then
                    returnXMLNodeValue = oKeys.Text
                    bFoundKey = True
                    Exit For
                End If
            Next oKeys
            
            'If we didn't find the key, we need to bail
            If bFoundKey = False Then
                'Error Code
                Exit Function
            End If
            
        Else
            With Err
                .Number = 1002
                .Source = "returnXMLNodeList"
                .Description = "Invalid Group Name passed to config file."
            End With
            Exit Function
        End If
    Else
        'Error Code
        Exit Function
    End If
    
End Function

The error can occur in the code block immediately above. How do I pass it back (specifically error 1002) to the frmMain?

Randy
[afro]
 
I like to add a little private constant at the top of each class that I use when raising errors

for clsApp_Configs:
Code:
Private Const CLASS_NAME As String = "clsApp_Configs"

Public Property Get Import_DSN() As String
  On Error GoTo Handler
  Import_DSN = returnXMLNodeValue("Database_Config", "DSN_Import")
  Exit Property
  
Handler:
  Import_DSN = ""
  
  'All errors are unexpected errors
  err.Raise err.Number, _
            CLASS_NAME & ".Import_DSN [Property Get] -> " & err.Source, _
            "Unexpected Error: " & err.Description
End Property


'----------------------------------------------------------------
'Purpose: Parses the XML Configuration file
'Returns: Configuration key value
'----------------------------------------------------------------
Private Function returnXMLNodeValue(ByVal sGroup As String, _
                                    ByVal sNodeName As String) _
                                    As String
  On Error GoTo Handler

  Dim oDom As New MSXML2.DOMDocument50
  Dim oDB_Configs As IXMLDOMNodeList
  Dim oKeys As IXMLDOMNode
  Dim bFoundKey As Boolean
  
  'Load the XML document
  If Not CBool(oDom.Load(App.path & "\App_Configs.xml")) Then
    'i just made up error #1000 here
    err.Raise 1000, _
              CLASS_NAME & ".returnXMLNodeValue", _
              "Failure loading config file."
    Exit Function   'this line is unreachable
  End If
  
  
  'get group node
  Set oDB_Configs = oDom.selectNodes("//" & sGroup & "/*")
  
  
  'error if failed to get node
  If oDB_Configs.Length = 0 Then
    err.Raise 1002, _
              CLASS_NAME & ".returnXMLNodeValue", _
              "Invalid Group Name passed to config file."
    Exit Function   'this line is unreachable
  End If
      
      
  'Search the keys and return the value for the one we are
  'looking for.  If even one Group fails, we need to bail
  For Each oKeys In oDB_Configs
    If oKeys.nodeName = sNodeName Then
      returnXMLNodeValue = oKeys.Text
      bFoundKey = True
      Exit For
    End If
  Next oKeys
  
  
  'error if we didn't find the key
  If Not bFoundKey Then
    'i just made up error #1003 here
    err.Raise 1003, _
              CLASS_NAME & ".returnXMLNodeValue", _
              "Key not found."
    Exit Function   'this line is unreachable
  End If
      
      
  'clean up and exit
  Set oDom = Nothing
  Set oDB_Configs = Nothing
  Set oKeys = Nothing
  Exit Function
  
  
Handler:
  returnXMLNodeValue = ""
  Set oDom = Nothing
  Set oDB_Configs = Nothing
  Set oKeys = Nothing
  
  If (err.Source = (CLASS_NAME & ".returnXMLNodeValue")) Then
    'Expected errors that were intentionally raised within
    'this method or property... re-raise to bubble it up.
    err.Raise err.Number, err.Source, err.Description
  Else
    'Unexpected errors
    err.Raise err.Number, _
              CLASS_NAME & ".returnXMLNodeValue -> " & err.Source, _
              "Unexpected Error: " & err.Description
  End If
End Function

for frmMain:
Code:
Private Const CLASS_NAME As String = "frmMain"
              
Private Function fillDeliveryFields() As Boolean
  On Error GoTo Handler

  Dim oDBO As New clsDB_Ops

  With oDBO
    .Data_Source = Import
    .Filter_Statement = "WHERE po_number='" & txtDelivery(0) & "'"
    Set oTemp_Rec = oDBO.returnRecord()
  End With
  Set clsDB_Ops = Nothing
  
  Exit Function
  
Handler:
  Set clsDB_Ops = Nothing
  
  MsgBox "Error #" & err.Number & vbCRLF _
          & "Description: " & err.Description & vbCRLF _
          & "Source: " & err.Source, _
         vbOKOnly + vbCritical, _
         "Oh nooooooo!"
End Function

for clsDB_Ops
Code:
Private Const CLASS_NAME As String = "clsDB_Ops"

Private Sub Class_Initialize()
  On Error GoTo Handler

  Dim Import_Source As String
  Dim Export_Source As String

  Import_Source = oAC.Import_DSN
  Export_Source = oAC.Export_DSN
  
  'Note: What is the purpose of setting these local string variables if
  '      they go out of scope immediately when the sub ends?
  
  Exit Sub
  
Handler:
  'All errors are unexpected errors
  err.Raise err.Number, _
            CLASS_NAME & ".Class_Initialize -> " & err.Source, _
            "Unexpected Error: " & err.Description
End Sub


In this example, the only errors that are not "unexpected" are the ones explictly raised with the err.Raise syntax.

All of your "lower" modules bubble up to the form. The form handles all errors with a messagebox. The messagebox will show you the error source, which shows the call stack because of the way we append the class name onto our errors.
 
Sheco,

There's something missing maybe. I structured my code to match your example, and the error notification stops at the property's Catch statement. It won't flow back into the frmMain. In other words, it's not bubbling back up.

Randy
[afro]
 
Ok, to better explain, start a new project as standard EXE and then drop a button on the form and then use the code below.

It will randomly create either a divide by zero error or an error that is raised intentionally in code.

Code:
Private Const CLASS_NAME As String = "frmMain"

Private Sub Command1_Click()
  On Error GoTo Handler
  
  MsgBox foo(5)
  Exit Sub
  
Handler:
  MsgBox "Error #" & Err.Number & vbCrLf _
          & "Description: " & Err.Description & vbCrLf _
          & "Source: " & CLASS_NAME & ".Command1_Click -> " & Err.Source, _
         vbOKOnly + vbCritical, _
         "Oh nooooooo!"
End Sub

Private Function foo(lol As Integer) As Integer
   On Error GoTo Handler
   
   bar lol
   foo = 10
   Exit Function
   
Handler:
  'All errors are unexpected errors
  Err.Raise Err.Number, _
            CLASS_NAME & ".foo -> " & Err.Source, _
            "Unexpected Error: " & Err.Description
End Function

Private Sub bar(ByRef eex As Integer)
  On Error GoTo Handler
  Dim val As Integer
  
  Randomize
  val = Int(2 * Rnd)
  
  If val = 1 Then
    'make divide by zero error
    eex = eex / 0
  Else
    'intentionall raise our own error
    Err.Raise 1000, _
              CLASS_NAME & ".bar", _
              "Test error!"
  End If
  
  Exit Sub
  
Handler:
  returnXMLNodeValue = ""
  Set oDom = Nothing
  Set oDB_Configs = Nothing
  Set oKeys = Nothing
  
  If (Err.Source = (CLASS_NAME & ".bar")) Then
    'Expected errors that were intentionally raised within
    'this method or property... re-raise to bubble it up.
    Err.Raise Err.Number, Err.Source, Err.Description
  Else
    'Unexpected errors
    Err.Raise Err.Number, _
              CLASS_NAME & ".bar -> " & Err.Source, _
              "Unexpected Error: " & Err.Description
  End If
End Sub
 
Randy,

To comment on the last code that you sent: the way that you would get the error to bubble back up is to raise it, which you haven't done in your code.

The "bubble up" rule is simple, really: if an error happens in a proc that doesn't have an error handler specified, the error will be passed up the call stack to the first error handler it finds. (If it doesn't find one, the default error handling--that of "On error goto 0"--will apply.)

Here's a short example that I use in my classes that might be helpful, that illustrates way the error handler bubbles errors up the call stack:

Code:
Option Explicit

Private Sub Command1_Click()
Dim qIn As Double
Dim qOut As Double
On Error GoTo ErrHandle
ResumeHere:
qIn = InputBox("Enter a number: ")
qOut = GetRecip(qIn)
MsgBox "The reciprocal of " & qIn & " is " & qOut
Exit Sub
ErrHandle:
Select Case Err.Number
    Case 11
        MsgBox "Can't get the reciprocal of zero.  Try another number."
        Resume ResumeHere
    Case 13
        MsgBox "That wasn't a number!!!  Try again."
        Resume ResumeHere
End Select
End Sub

Private Function GetRecip(SomeNumber As Double) As Double
GetRecip = 1 / SomeNumber
End Function
Try stepping through this code, entering a non-number and entering a zero. You'll see that the zero raises an error inside the GetRecip function, and that that error gets handled in Command1_Click's error handler.

HTH

Bob
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top