SimplyVBA and SimplyVB6
Global Error Handler v1.7.2
QuickStart: Bookmarks
On this page you'll find:
Overview
Bookmarks are a new feature introduced with SimplyVBA Global Error Handler v1.2.
In this context, a bookmark is simply a label that you can define in a VB subroutine - the label itself doesn't affect the running code in the local procedure. In this sense, a bookmark is the same as normal VBA labels.
Normal VBA labels are defined like this:
Public Sub Example()
On Error GoTo LocalErrorHandler
' Code here
Exit Sub
LocalErrorHandler: ' This is a normal VBA line label
MsgBox Err.Description
End Sub From a global error handling perspective, normal VB labels are very limited as we can't 'see' these labels from within our global error handling procedure when an error occurs. If we could 'see' the labels and jump to them conditionally from within our global error handler, wouldn't that be cool?...
Bookmarks
Bookmarks allow us to do just that.
Instead of defining normal VB labels, we can instead define ErrEx bookmarks and then conditionally jump to these bookmarks from inside our global error handler by using some extra methods provided by the ErrEx object.
The Basics: Defining Bookmarks
' This is defined in your global error handler module (or any standard module)
Public Const BOOKMARK_ONERROR As Long = 5
Public Sub Example()
' Code here
Exit Sub
ErrEx.Bookmark = BOOKMARK_ONERROR
MsgBox Err.Description
End Sub The first thing you will notice is that there is no 'On Error Goto XYZ' and the reason is simple: When an error occurs, our global error handler will be jumping to the line below BOOKMARK_ONERROR for us, so there is no longer any need for specific local error handling to be set.
ErrEx.Bookmark is a write-only property of data-type Long (numeric). In this example we have chosen a bookmark value of 5 to uniquely identify our bookmark, but you can choose any value for this example as long as the values are unique if you define more than one bookmark.
We suggest that you define bookmarks as constants in a standard VB module - keeping the constants in the same module as your global error handler is probably a good idea.
Jumping to bookmarks from within a global error handler
To jump to a bookmark location from within a global error handler, we use the ErrEx.SetBookmarkAsOnErrorGoto method:
ErrEx.SetBookmarkAsOnErrorGoto(BookmarkID As Long) As Boolean
Public Sub MyGlobalErrorHandler()
If ErrEx.SetBookmarkAsOnErrorGoto(BOOKMARK_ONERROR) = True Then
' The developer has set a BOOKMARK_ONERROR bookmark, so jump to that.
Exit Sub
Else
' The developer hasn't defined a BOOKMARK_ONERROR bookmark in this procedure,
' so we will deal with the error here
ErrEx.State = ErrEx.ShowErrorDialog
End If
End Sub In our example BookmarkID is the value BOOKMARK_ONERROR (5). What this call effectively does is set 'On Error Goto BOOKMARK_ONERROR' for the error, and since the error has already occured (since we are inside a global error handler when calling this), then once your global error handler has completed (Exit Sub), the program flow will be set just beneath the bookmark - at the "MsgBox Err.Description" line.
The return value indicates whether or not the procedure of error actually had that bookmark or not.
If it returns TRUE then the bookmark exists and program flow will continue to that bookmark upon leaving the global error handler.
If it returns FALSE then the procedure of error does not have that particular bookmark set and the program flow is not changed.
Determining the current bookmark in the local procedure
From within your global error handler you can also determine the current bookmark that is active. We use the ErrEx.CurrentBookmark property:
ErrEx.CurrentBookmark As Variant
The return value of this read-only property is the BookmarkID for you to compare to your constant values, or an Empty variant if no bookmark is currently active.
This is particularly useful for writing bookmarks for 'cleanup' routines where for example, you might always want 'On Error Resume Next' to be assumed:
Public Sub Example()
Dim objRcdSet As DAO.Recordset
ErrEx.Bookmark = BOOKMARK_CLEANUP
objRcdSet.Close ' Recordset wasn't open - this will cause an exception, but it's ignored
Set objRcdSet = Nothing ' because we are within the BOOKMARK_CLEANUP bookmark
End Sub Public Const BOOKMARK_CLEANUP As Long = 6
Public Sub MyGlobalErrorHandler()
' Check if the error occured in the BOOKMARK_CLEANUP section of code
If ErrEx.CurrentBookmark = BOOKMARK_CLEANUP Then
ErrEx.State = OnErrorResumeNext
Exit Sub
End If
' Show the error dialog
ErrEx.ShowErrorDialog
If ErrEx.SetBookmarkAsOnErrorGoto(BOOKMARK_CLEANUP) = True Then
' The developer has set a BOOKMARK_CLEANUP bookmark, so jump to that.
Exit Sub
Else
' The developer hasn't defined a BOOKMARK_CLEANUP bookmark in this procedure
' so we will end abruptly here
ErrEx.State = OnErrorEnd
End If
End SubJumping to bookmarks inside our local procedure
To jump to a bookmark location from within a local procedure, we use the ErrEx.GoToBookmark method:
ErrEx.GoToBookmark(BookmarkID As Long)
In our example BookmarkID is the value BOOKMARK_CLEANUP (6). This call is a replacement for 'Goto XYZ' which is used when working with normal VBA labels.
Public Sub Example()
Dim objRcdSet As DAO.Recordset
ErrEx.Bookmark = BOOKMARK_CLEANUP
objRcdSet.Close ' If the recordset wasn't open, this will cause an exception
Set objRcdSet = Nothing
Exit Sub
ErrEx.Bookmark = BOOKMARK_ONERROR
MsgBox Err.Description
Call ErrEx.GoToBookmark(BOOKMARK_CLEANUP)
End SubUsing error numbers as BookmarkIDs (like try/catch blocks)
Although there are no implicit restrictions on what values you can assign to the constants for BookmarkIDs, we advise using actual error numbers if you want to provide a simplified form of local error handling.
For example, if you want to assign a local error handler for handling a division-by-zero error, we could define it like this:
Public Const BOOKMARK_CLEANUP = -1 ' *** Read comments below ***
Public Const BOOKMARK_ONERROR_DIVBYZERO As Long = 11 ' 11 is the Err.Number for division by zero errors
Public Sub Example()
Dim objRcdSet As DAO.Recordset
Debug.Print 1/0
ErrEx.Bookmark = BOOKMARK_CLEANUP
objRcdSet.Close ' If the recordset wasn't open, this will cause an exception
Set objRcdSet = Nothing
Exit Sub
ErrEx.Bookmark = BOOKMARK_ONERROR_DIVBYZERO
Resume Next
End Sub
Public Sub MyGlobalErrorHandler()
' Check if the error occured in the BOOKMARK_CLEANUP section of code
If ErrEx.CurrentBookmark = BOOKMARK_CLEANUP Then
' It did - so just ignore it and continue
ErrEx.State = OnErrorResumeNext
Exit Sub
End If
' In this example Err.Number will be 11 which is BOOKMARK_ONERROR_DIVBYZERO
If ErrEx.SetBookmarkAsOnErrorGoto(Err.Number) = True Then
' The procedure of error has designated a bookmark for handling this type of error
Exit Sub
Else
' The error is not handled locally, so we will handle it here...
ErrEx.ShowErrorDialog
If ErrEx.SetBookmarkAsOnErrorGoto(BOOKMARK_CLEANUP) = True Then
' The developer has set a BOOKMARK_CLEANUP bookmark, so jump to that.
Exit Sub
Else
' The developer hasn't defined a BOOKMARK_CLEANUP bookmark in this procedure, so we will end abruptly here
ErrEx.State = OnErrorEnd
End If
End If
End Sub By using BookmarkIDs in this way, you can add new bookmarks without needing to change the global error handler code - just assign the new global constant to the value of an error number and use it in your local error handlers.
*** The only situation you need to be aware of when implementing it this way is that for non-error handling bookmarks (like BOOKMARK_CLEANUP) you must assign values to these bookmark constants that are unlikely to ever be valid error numbers (to avoid number conflicts). We advise using low negative numbers as these are rarely used for VBA error numbers.
A practical example
Let's take an example:
We want to deal with a specific type of error locally (rather than in the global handler) and also ensure that all local variables are properly cleaned up.
Prior to using bookmarks we might have written something like this:
Public Function ChangeProperty(stPropName As String, PropType As DAO.DataTypeEnum, vPropVal As Variant) As Boolean
On Error GoTo LocalErrorHandler
Dim Db As DAO.Database
Dim Prop As DAO.Property
Set Db = CurrentDb
Db.Properties.Delete stPropName
Db.Properties.Delete stPropName ' Causes an error because the property doesn't exist
Set Prop = Db.CreateProperty(stPropName, PropType, vPropVal, True)
Db.Properties.Append Prop
ChangeProperty = True
Cleanup:
On Error Resume Next ' Ignore errors when cleaning up.
Set Prop = Nothing
Set Db = Nothing
Exit Function
LocalErrorHandler:
' Handle local specific errors here:
Const conItemNotFoundInCollectionError As Long = 3265
Select Case Err.Number
Case conItemNotFoundInCollectionError
' We will ignore the 'property does not exist' error
Resume Next
End Select
ErrEx.CallGlobalErrorHandler ' Call the global error handler to deal with unhandled errors
Resume Cleanup ' After showing the error dialog, we will clean up
End Function By using the new bookmarks features of the SimplyVBA Global Error Handler, we can now cut down this code considerably:
(Assume the global error handler is set up as per Using error numbers as BookmarkIDs)
Public Const BOOKMARK_CLEANUP As Long = -1 ' -1 to avoid conflicts with normal error numbers
Public Const BOOKMARK_ONERROR_ITEMNOTFOUNDINCOLLECTION As Long = 3265
Public Function ChangeProperty(stPropName As String, PropType As DAO.DataTypeEnum, vPropVal As Variant) As Boolean
Dim Db As DAO.Database
Dim Prop As DAO.Property
Set Db = CurrentDb
Db.Properties.Delete stPropName
Db.Properties.Delete stPropName ' Causes an error because the property doesn't exist
Set Prop = Db.CreateProperty(stPropName, PropType, vPropVal, True)
Db.Properties.Append Prop
ChangeProperty = True
ErrEx.Bookmark = BOOKMARK_CLEANUP
Set Prop = Nothing
Set Db = Nothing
Exit Function
ErrEx.Bookmark = BOOKMARK_ONERROR_ITEMNOTFOUNDINCOLLECTION
Resume Next
End Function Also check out the BookmarksSample.mdb included with v1.2 and above of the installed application.
Verifying Bookmarks at compile time
Unlike normal VB labels, Bookmarks are not checked at compile time. For this reason, we have provided a simple tool in the Add-ins menu that can be run after compiling to ensure that;
1) There are no duplicate bookmarks defined in the same local procedure.
2) If ErrEx.GoToBookmark is used in a local procedure, then the bookmark that is referenced by it has actually been defined.
This tool only does a very simplistic text based check of the source code, but this is sufficient in most circumstances.
To run the tool, go to the VBE Window, Add-ins menu > SimplyVBA Global Error Handler > Verify Bookmarks Usage
Further reading / related pages: