|
It is especially important to use this method for marking errors when you are coding ActiveX DLL servers. Since servers may run at a remote location on the network, you cannot be sure that users ever see any error dialog you display. Also, remember that, even if the DLL is running on the local PC, error dialog boxes are application-modal. No other processing occurs until the dialog box is dismissed. You learn to use the Err.Raise method when you create your errHandler object library later in this chapter. So far, you have seen how to build a simple error handler and the different ways to exit error handlers. Now you need to learn about the different types of errors that you may encounter in your Visual Basic programs and how to plan for them in advance. Types of ErrorsIn order to make writing error handlers easier and more efficient, you can group errors into typical types. These error types can usually be handled in a similar manner. When you get an idea of the types of errors you may encounter, you can begin to write error handlers that take care of more than one error. You can write handlers that take care of error types. There are four types of Visual Basic errors:
Each of these types of errors needs to be handled differently within your Visual Basic programs. You learn general rules for handling these errors in the following sections. General File ErrorsGeneral file errors occur because of invalid data file information such as a bad filename, data path, or device name. Usually the user can fix these errors, and the program can continue from the point of failure. The basic approach to handling general file errors is to create an error handler that reports the problem to the user and asks for additional information to complete or retry the operation. In Listing 14.9, the error handler is called when the program attempts to open a control file called CONTROL.TXT. The error handler then prompts the user for the proper file location and continues processing. Start a new Standard EXE project (ERRTYPES.VBP) and add a command button to the form. Set its Caption property to Control and its Name property to cmdControl. Also, add a CommonDialog control to the project. Enter the code in Listing 14.9 into the cmdControl_Click event. Listing 14.9. Adding code to the cmdControl_Click event.
Figure 14.8. Displaying the File not Found
error. Figure 14.9. Attempting to locate the CONTROL.TXT file.
Table 14.2 lists errors that are similar to the File
not found error illustrated in Listing 14.9. Errors of this type usually involve
giving the user a chance to re-enter the filename or reset some value. Most of the time,
you can write an error trap that anticipates these errors, prompts the user to supply the
corrected information, and then retries the operation that caused the error.
Physical Media ErrorsAnother group of common errors is caused by problems with physical media. Unresponsive printers, disk drives that do not contain diskettes, and downed communications ports are the most common examples of physical media errors. These errors might, or might not, be easily fixed by your user. Usually, you can report the error, wait for the user to fix the problem, and then continue with the process. For example, if the printer is jammed with paper, all you need to do is report the error to the user, and then wait for the OK to continue. Let's add another button to the PRJERRTYPES.VBP project to display an example of physical media error handling. Add a new command button to the project. Set its Caption property to &Media and its Name property to cmdMedia. Enter the code in Listing 14.10 into the cmdMedia_Click event. Listing 14.10. Trapping media errors.
Save and run the project. When you
click on the Media button, you should get results that look like those in Figure 14.10.
|
Control | Property | Setting |
VB.Form | Name | frmDataErr |
Caption | "Data Error Demo" | |
ClientHeight | 1335 | |
ClientLeft | 60 | |
ClientTop | 345 | |
ClientWidth | 4665 | |
StartUpPosition | 3 `Windows Default | |
VB.CommandButton | Name | cmdAdd |
Caption | "&Add" | |
Height | 375 | |
Left | 3300 | |
Top | 60 | |
Width | 1215 | |
VB.TextBox | Name | txtName |
DataField | "Name" | |
DataSource | "Data1" | |
Height | 315 | |
Left | 1500 | |
Top | 540 | |
Width | 3015 | |
VB.TextBox | Name | txtKeyField |
DataField | "KeyField" | |
DataSource | "Data1" | |
Height | 375 | |
Left | 1500 | |
Top | 60 | |
Width | 1515 | |
VB.Data | Name | Data1 |
Align | 2 `Align Bottom | |
Caption | "Data1" | |
Connect | "Access" | |
DatabaseName | C:\TYSDBVB5\SOURCE\DATA\ERRORS\ | |
ERRORDB.MDB | ||
Height | 360 | |
RecordSource | "Table1" | |
Top | 975 | |
Width | 4665 | |
VB.Label | Name | lblName |
Caption | "Name" | |
Height | 255 | |
Left | 120 | |
Top | 540 | |
Width | 1215 | |
VB.Label | Name | lblKeyField |
Caption | "Key Field" | |
Height | 255 | |
Left | 120 | |
Top | 120 | |
Width | 1215 |
The only code you need to add to this form is a single line to support the Add button.
Place the following code behind the cmdAdd_Click event.
Now save the new form as DATAERR.FRM and the project as DATAERR.VBP. When you run the project, you can test the built-in error trapping for Microsoft data controls by adding a new, duplicate record to the table. Press the Add button, then enter KF109 in the KeyField input box and press one of the arrows on the data control to force it to save the record. You should see a database error message that looks like the one in Figure 14.12.
Are you surprised? You didn't add an
error trap to the data entry form, but you still got a complete database error message!
The Visual Basic data control is kind enough to provide complete database error reporting
even if you have no error handlers in your Visual Basic program. Along with the automatic
errors, the data control also has the Error event. Each time a data-related error
occurs, this event occurs. You can add code in the Data1_Error event to
automatically fix errors, display better error messages, and so on.
Figure 14.12. A sample
Microsoft data control error message.
Let's modify the program a bit to show you how you can use the Data1_Error event.
First, add a CommonDialog control to your form. Then edit the DatabaseName property of the
data control to read C:\ERRORDB.MDB. Next, add the code from Listing 14.11 to the
Data1_Error event.
Private Sub Data1_Error(DataErr As
Integer, Response As Integer) ` ` add error-trapping for data errors ` Dim strFileName As String ` Select Case DataErr Case 3044 ` database not found MsgBox "Unable to locate data file", vbExclamation, "Database ÂMissing" ` CommonDialog1.DialogTitle = "Locate ERRORDB.MDB" CommonDialog1.filename = "ERRORDB.MDB" CommonDialog1.Filter = "*.mdb" CommonDialog1.ShowOpen Data1.DatabaseName = CommonDialog1.filename ` Response = vbCancel ` cancel auto-message End Select ` End Sub |
Notice that the code in Listing 14.11 checks to see whether the error code is 3044.
This is the error number that corresponds to the "database missing" message. If
the 3044 code is reported, the user sees a short message and then the file open
dialog, ready to locate and load the database. Finally, notice the line that sets the Response
parameter to vbCancel. This step tells Visual Basic not to display the default
message.
TIP: Usually, it is not a good idea to attempt to override this facility with your own database errors. As long as you use the Visual Basic data control, you do not need to add database error-trapping routines to your data entry forms. The only time you need to add error-related code is when you want to perform special actions in the Error event of the data control.
You need to add one more bit of code to complete this error trap. Add the following line of code to the Form_Activate event.
Private Sub Form_Activate() Data1.Refresh End SubThis code makes sure the data entry fields on the form are updated with the most recent data from the database.
Now save and run the project. You
first see a message telling you that the database is missing (Figure 14.13).
Figure 14.13. Custom
error message in the Data1_Error event.
Next, the open file dialog waits for you to locate and load the requested database (Figure
14.14).
Figure 14.14. Locating
the requested database.
Finally, once you load the database, the data entry screen comes up ready for your input.
If you use Microsoft data access objects instead of the Visual Basic data control, you need to add error-handling routines to your project. For example, if you want to create a Dynaset using Visual Basic code, you need to trap for any error that might occur along the way.
Add the code in Listing 14.12 to the Form_Load event of frmData. This code opens the database and creates a Dynaset to stuff into the data control that already exists on the form.
Private Sub Form_Load() ` ` create recordset using DAO ` On Error GoTo LocalErr ` Dim ws As Workspace Dim db As Database Dim rs As Recordset Dim strSQL As String ` strSQL = "SELECT * FROM Table2" Set ws = DBEngine.Workspaces(0) Set db = ws.OpenDatabase(App.Path & "\..\..\Data\Errors\ErrorDB.mdb") Set rs = db.OpenRecordset(strSQL, dbOpenDynaset) Exit Sub ` LocalErr: MsgBox "<" & CStr(Errors(0).Number) & "> " & Errors(0).Description, ÂvbCritical, "Form_Load Error" Unload Me ` End Sub |
The code in Listing 14.12 establishes some variables and then opens the database and
creates a new Dynaset from a data table called Table2.
NOTE: Notice that instead of the Visual Basic Err object, the DAO Errors collection is used to retrieve the most recent database error. The Errors collection is only available if you loaded the Microsoft DAO library using the Project | References option from the main Visual Basic 5 menu.
Because there is no Table2 in
ERRORDB.MDB, you see a database error when the program runs. The error message is
displayed, and then the form is unloaded completely (see Figure 14.15).
Figure 14.15. Displaying
an error message from the Form_Load event.
It is a good idea to open any data tables or files that you need for a data entry form
during the Form_Load event. That way, if there are problems, you can catch them
before data entry begins.
In the previous sections, you created several error handlers, each tuned to handle a special set of problems. Although this approach works for small projects, it can be tedious and burdensome if you have to put together a large application. Also, after you've written an error handler that works well for one type of error, you can use that error handler in every other program that might have the same error. Why write it more than once?
Even though Visual Basic requires error traps to be set for each Sub or Function, you can still create a generic approach to error handling that takes advantage of code you have already written. In this section, you write a set of routines that you can install in all your Visual Basic programs--the error-handling OLE Server. This OLE Server offers some generic error-handling capabilities along with the ability to log these errors to a disk file and to keep track of the procedure call stack. These last two services can be very valuable when you encounter a vexing bug in your program and need to get additional information on the exact subroutines and functions that were executed before the error occurred.
To build the new error handling OLE Server, you need to start a new Visual Basic ActiveX DLL project. Name the default Class module errHandler and set the project Name to prjErrHandler. You also need to add a BAS module and a form to the project. The form acts as the new customized error dialog box. The BAS module holds a new user-defined type and some API definitions for use with the customized error dialog box.
Building the errHandler class involves several steps. First, add some code to the general declaration section of the Class object (see Listing 14.13).
Option Explicit ` ` error types Enum errType erritem = 0 errcoll = 1 End Enum ` ` return/option values Enum errReturn errExit = 0 errresume = 1 errNext = 2 errselect = 3 End Enum ` ` handler storage Private errDefRtn As errReturn Private errDefType As errType |
The first two items in the declaration section define enumerated types. These are a
special type of user-defined type that are a mix between a standard user-defined type and
a Public constant. Enumerated types make it easy to write well-documented code. Along with
the enumerated types, you see two Private variables declared for local use.
Next, you need to add some declaration code to the BAS module in your DLL project. This code defines a special custom data type that you can use to control the display of your custom error dialog box. The errHandler DLL allows you to access any possible help topics associated with the error messages, too. For this reason, the BAS module contains the WinHelp API declaration. This is used on the custom dialog box. Open the BAS module, set its Name property to modErrHandler and enter the code in Listing 14.14 into the general declaration section of the module.
Option Explicit ` ` define dialog data type Public Type errDialog Message As String Buttons As Variant Title As String HelpFile As String HelpID As Long Return As Long End Type ` Public udtErrDialog As errDialog ` ` declare winHelp API Declare Function WinHelp Lib "user32" Alias "WinHelpA" (ByVal hwnd As Long, _ ByVal lpHelpFile As String, _ ByVal wCommand As Long, _ ByVal dwData As Long) As Long ` Public Const HELP_CONTEXT = &H1 Public Const HELP_QUIT = &H2 |
That's all that you need to add to the modErrHandler. Next, you need to add code
to the Initialize event of the errHandler Class module. Add the code from Listing
14.15 to the Class_Initialize event.
Private Sub Class_Initialize() ` ` set starting values errDefRtn = errExit errDefType = errItem ` udtErrDialog.Buttons = "" udtErrDialog.HelpFile = "" udtErrDialog.HelpID = -1 udtErrDialog.Message = "" udtErrDialog.Return = -1 udtErrDialog.Title = "" ` End Sub |
This errHandler class has two Public properties--the DefaultAction and DefaultType
properties. These defaults were set in the Initialize event and can be overridden
by setting the properties at runtime. Create the DefaultAction property (using the Tools |
Add Procedure menu option) and add the code from Listing 14.16 into the Property Let
and Property Get routines.
Public Property Get DefaultAction() As
errReturn ` ` return default ` DefaultAction = errDefRtn ` End Property Public Property Let DefaultAction(ByVal vNewValue As errReturn) ` ` verify parm and store ` If vNewValue >= errExit Or vNewValue <= errselect Then errDefRtn = vNewValue End If ` End Property |
Note that the data type for the property is errReturn. This is one of the enumerated types
defined in the declaration section of the class module. Next, create the Property Let
and Property Get routines for the DefaultType property, and enter the code from
Listing 14.17 into the project.
Public Property Get DefaultType() As
errType ` DefaultType = errDefType ` End Property Public Property Let DefaultType(ByVal vNewValue As errType) ` If vNewValue >= errcoll Or vNewValue <= erritem Then errDefType = vNewValue End If ` End Property |
Finally, you're ready to write the main error handler method. This method can be called
from any VBA-compliant program. You pass the Visual Basic Err object (or database Errors
collection) along with a few optional parameters that can control the behavior of the
message dialog box. After the method has been completed, a value is returned. This value
can be used to control program flow and error recovery.
Create a new function method in your class module called errHandler and enter the code from Listing 14.18.
Public Function errHandler(objErrColl
As Variant, Optional intType As errType, ÂOptional errOption As errReturn, Optional
errRefName As String) As errReturn ` -------------------------------------------------------------------------- ` produce msg and prompt for response ` ` inputs: ` objErrColl - DAO collection -OR- VBA Err object ` intType - errType enum that describes objErrColl (coll or item) ` errOption - errReturn enum sets dialog behavior (exit, resume, next, Âselect) ` errRefName - string to reference caller ` ` returns: ` errExit - end program ` errResume - try again ` errNext - skip to next line ` -------------------------------------------------------------------------- ` Dim strMsg As String Dim strTitle As String Dim rtnValue As errReturn ` ` retrieve action option If IsMissing(errOption) Then errOption = errDefRtn End If ` ` retrieve reference name If IsMissing(errRefName) Then errRefName = "" Else errRefName = " from " & errRefName End If ` ` build full message strMsg = errMsg(objErrColl, intType) ` ` write it out, if allowed If errLogFlag = True Then LogError strMsg End If ` ` evaluate things Select Case errOption Case errExit udtErrDialog.Title = "Exiting Program" udtErrDialog.Message = strMsg udtErrDialog.Buttons = Array("&Exit") frmErrDialog.Show vbModal rtnValue = errExit ` Case errresume, errNext udtErrDialog.Title = "Error Message" & errRefName udtErrDialog.Message = strMsg udtErrDialog.Buttons = Array("&OK") frmErrDialog.Show vbModal rtnValue = errOption ` Case Else udtErrDialog.Title = "Error Message" & errRefName udtErrDialog.Message = strMsg udtErrDialog.Buttons = Array("&Cancel", "&Retry", "&Ignore") frmErrDialog.Show vbModal rtnValue = udtErrDialog.Return End Select ` ` give it back errHandler = rtnValue ` End Function |
The code in Listing 14.18 calls a support function (errMsg) and a dialog box
(frmErrDialog). You need to code these two remaining objects before you can test your
error handler. Create a new function (errMsg) in the class module and enter the
code from Listing 14.19 into the project.
Public Function errMsg(objErrColl As
Variant, intType As errType) As String ` ` build and return complete error msg ` Dim strMsg As String Dim objItem As Error ` strMsg = "" ` If intType = errcoll Then For Each objItem In objErrColl strMsg = strMsg & "<" & CStr(objItem.Number) & "> " strMsg = strMsg & objItem.Description strMsg = strMsg & " (in " & objItem.Source & ")." & vbCrLf Next Else ` intType= errItem strMsg = "<" & objErrColl.Number & "> " strMsg = strMsg & objErrColl.Description strMsg = strMsg & " (in " & objErrColl.Source & ")" ` udtErrDialog.HelpFile = objErrColl.HelpFile udtErrDialog.HelpID = objErrColl.HelpContext End If ` errMsg = strMsg ` End Function |
The main job of the errMsg routine is to build a complete error message for
display to the user. In order to do this, errMsg needs to know if the object that
was passed was a single VBA Err object or the DAO Errors collection. That is why the errType
parameter is included in the call. Also note that the errMsg method has been
declared as a Public method. You can call this method from your Visual Basic 5
programs, too. That way, even if you don't want to perform all of the error-handing
operations, you can use errMsg to get an improved error message for your use.
The last main piece of the errHandler class is a custom dialog box to display the error message and get input from the user. Often, you want to do more than just display the message and let the user press "OK." You may ask them to retry the same process, or ask if they want to ignore the error and continue. This dialog box not only displays the message and gives you an opportunity to get a response from the user, it also allows you to provide an optional "Help" button to give the user greater support in discovering how to resolve the error.
NOTE: Creating Help files for your Visual Basic project is covered in Appendix B of this guide.
With the prjErrHandler project still open, add a
form to the project. Use Table 14.4 and Figure 14.16 as guides in laying out the form.
Table 14.4. Control
table for the frmErrDialog form.
Control | Property | Setting |
VB.Form | Name | frmErrDialog |
BorderStyle | 3 `Fixed Dialog | |
Caption | "Error Report" | |
ClientHeight | 1755 | |
ClientLeft | 45 | |
ClientTop | 330 | |
ClientWidth | 5460 | |
ControlBox | 0 `False | |
MaxButton | 0 `False | |
MinButton | 0 `False | |
ShowInTaskbar | 0 `False | |
StartUpPosition | 2 `CenterScreen | |
VB.TextBox | Name | txtErrMsg |
BackColor | &H80000000& | |
Height | 1035 | |
Left | 900 | |
Locked | -1 `True | |
MultiLine | -1 `True | |
ScrollBars | 2 `Vertical | |
Top | 120 | |
Width | 4395 | |
VB.CommandButton | Name | cmdBtn |
Caption | "&Help" | |
Height | 315 | |
Index | 3 | |
Left | 4080 | |
Top | 1320 | |
Visible | 0 `False | |
Width | 1215 | |
VB.CommandButton | Name | cmdBtn |
Caption | "&Ignore" | |
Height | 315 | |
Index | 2 | |
Left | 2760 | |
Top | 1320 | |
Visible | 0 `False | |
Width | 1215 | |
VB.CommandButton | Name | cmdBtn |
Caption | "&Retry" | |
Height | 315 | |
Index | 1 | |
Left | 1440 | |
Top | 1320 | |
Visible | 0 `False | |
Width | 1215 | |
VB.CommandButton | Name | cmdBtn |
Caption | "&OK" | |
Height | 315 | |
Index | 0 | |
Left | 120 | |
Top | 1320 | |
Visible | 0 `False | |
Width | 1215 | |
VB.Image | Name | Image1 |
Height | 600 | |
Left | 120 | |
Picture | C:\TYSDBVB5\SOURCE\CHAP14\ | |
ERRHANDLER\INTL_NO.BMP | ||
Stretch | -1 `True | |
Top | 120 | |
Width | 600 |
Be sure you build the command buttons as a control array and that you set their Visible
property to False. You write code to arrange and enable these buttons as needed at
runtime.
Figure 14.16. Laying out
the frmErrDialog form.
Only two events need to be coded for this form. The Form_Load event handles most
of the dirty work. Listing 14.20 shows the code that you should add to the Form_Load
event.
Private Sub Form_Load() ` Dim intBtns As Integer Dim intLoop As Integer ` txtErrMsg = udtErrDialog.Message Me.Caption = udtErrDialog.Title ` intBtns = UBound(udtErrDialog.Buttons) For intLoop = 0 To intBtns cmdBtn(intLoop).Caption = udtErrDialog.Buttons(intLoop) cmdBtn(intLoop).Visible = True cmdBtn(intLoop).Top = Me.ScaleHeight - 420 cmdBtn(intLoop).Left = 120 + (1300 * intLoop) Next ` ` check for help file If udtErrDialog.HelpFile <> "" Then cmdBtn(3).Visible = True cmdBtn(3).Top = Me.ScaleHeight - 420 cmdBtn(3).Left = 120 + (1300 * 3) End If ` End Sub |
The code in Listing 14.20 first sets the dialog caption and message box. Then, based on
the properties of the udtErrDialog type, the buttons are arranged on the form. The only
other code that needs to be added to this form is the code that goes behind the command
button array. Place the code from Listing 14.21 in the cmdBtn_Click event of the
form.
Private Sub cmdBtn_Click(Index As
Integer) ` ` return user selection ` Dim lngReturn As Long ` Select Case Index Case 0 udtErrDialog.Return = errExit lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_QUIT, &H0) Unload Me Case 1 udtErrDialog.Return = errresume lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_QUIT, &H0) Unload Me Case 2 udtErrDialog.Return = errNext lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_QUIT, &H0) Unload Me Case 3 lngReturn = WinHelp(Me.hwnd, udtErrDialog.HelpFile, HELP_CONTEXT, udtErrDialog.HelpID) End Select ` End Sub |
The code in Listing 14.21 allows the user to select the command button appropriate for the
moment. If the user presses the Help button, the properties from the udtErrDialog type are
used to fill in the parameters of the WinHelp API call.
That's all the code you need to create your errHandler ActiveX DLL. Save the project and then compile it (File | Make prjErrHandler.dll). If it compiles without error, you're all set for a quick test!
Start a new Visual Basic 5 Standard EXE project. Set
the form name to frmTest and the project name to prjTest. Add a data control and a command
button to the form. Refer to Figure 14.17 while laying out the form.
Figure 14.17. Laying out
the frmTest form.
Next you need to add a reference to the error handler object library to your project.
Select Project | References and locate and add the prjErrHandler DLL (see Figure 14.18).
Figure 14.18. Adding the
error handler object library to the project.
Now you can add a bit of code to the form to set up the error handler, and then cause an
error to be handled. First, add the following line to the general declaration section of
the form. This declares the object that contains the error handler.
Next, add the code from Listing 14.22 to the Form_Load event of the project.
This code creates the new error handler object and then sets up the data control with a
bogus database name. Now add the code from Listing 14.23 to the Data1_Error
event. This code intercepts the database error and displays the new custom dialog box.
Private Sub Data1_Error(DataErr As
Integer, Response As Integer) ` Dim rtn As Long Response = 0 rtn = objErr.errHandler(Errors, errcoll) ` End Sub |
Now save the form (FRMTEST.FRM) and the project (PRJTEST.VBP) and run
the code. You should see your new object library error dialog telling you about the
database error (see Figure 14.19).
Now add some code behind the Command1_click
event to create a divide-by-zero error. The code in Listing 14.24 does just that.
Figure 14.19. The new
error object library in action.
Private Sub Command1_Click() ` On Error GoTo Localerr Dim rtn as Long ` Print 6 / 0 ` Exit Sub ` Localerr: rtn = objErr.errHandler(Err, erritem, errresume, "prjTest. ÂForm1.Command1_Click") Resume Next ` End Sub |
When you save and run this code, press the command button to see the new error report. You
should see that the Help button is active. Press the Help button to display Visual Basic 5
help for dealing with the divide-by-zero error (see Figure 14.20).
NOTE: Check out Appendix B for information on how you can create your own help files for your Visual Basic 5 projects.
Figure 14.20.Viewing
help on the divide-by-zero error.
Now let's add an option that creates an error report file whenever the error handler is
activated.
When errors occur, users often do not remember details that appear in the error messages. It's much more useful to create an error log on disk whenever errors occur. This enables programmers or system administrators to review the logs and see the error messages without having to be right next to the user when the error occurs.
To build error-logging features into the existing errhandler class, you need to declare two new properties (LogFileName and WriteLogFlag), create a LogError method to write the errors to a disk file, add some code to the general declarations area, the Class_Initialize and Class_Terminate events, and add a few lines to the errHandler method to call the LogError method.
First, restart the errhandler DLL project and add the code below to the general declaration section of the class module.
` ` logging storage Private errLogFileName As String Private errLogFlag As BooleanThese two lines of code appear at the end of the section. They declare local storage space for the new properties. Now use the Tools | Add Procedure menu option to add two new Public properties to the class: LogFileName and WriteLogFlag. Listing 14.25 shows the code you need to add to the Property Let and Property Get statements for these two new properties.
Public Property Get LogFileName() As
String ` LogFileName = errLogFileName ` End Property Public Property Let LogFileName(ByVal vNewValue As String) ` errLogFileName = vNewValue ` End Property Public Property Get WriteLogFlag() As Boolean ` WriteLogFlag = errLogFlag ` End Property Public Property Let WriteLogFlag(ByVal vNewValue As Boolean) ` errLogFlag = vNewValue |
End Property The LogFileName property is used to hold the name of the disk file that holds the log records. The LogFlag property controls the status of the error logging. If the LogFlag property is set to True, log records are created. Now add the following code to the end of the Class_Initialize event. This sets the default values for the two new properties.
errLogFileName = App.EXEName & ".err" errLogFlag = False `Now create a new Private Sub method called LogError and enter the code from Listing 14.26 into the routine. This is the code that actually creates the log entries.
Private Sub LogError(strErrMsg As
String) ` ` write error to disk file On Error GoTo LocalErr ` Dim intChFile As Integer ` intChFile = FreeFile Open errLogFileName For Append As intChFile Print #intChFile, Format(Now, "general date") Print #intChFile, strErrMsg Print #intChFile, "" Close intChFile ` Exit Sub ` LocalErr: ` trouble with file stuff! Err.Raise vbObjectError + 1, "errHandler.LogError", "Can't write log file [" & errLogFileName & "]" ` End Sub |
Notice that you added an error handler in this routine. Because you are about to perform
disk operations, you need to be ready for errors here, too! Notice also that the internal
error is not displayed in a message box. Instead the Raise method of the Err
object is used to generate a unique error number and description. This is sent back to the
calling application for handling.
TIP: The Visual Basic FreeFile() function is used to return a number that represents the first available file channel that Visual Basic uses to open the data file. Using FreeFile() guarantees that you do not select a file channel that Visual Basic is already using for another file.
Now all you need to do is add a call to the LogError method from within the Public errHandler method. Listing 14.27 shows the code you need to add in the routine. Make sure you add these lines of code right after the call to the errMsg function and just before the start of the Select Case statement.
` build full message strMsg = errMsg(objErrColl, intType) ` ` write it out, if allowed <<< new code If errLogFlag = True Then <<< new code LogError strMsg <<< new code End If <<< new code ` ` evaluate things Select Case errOption |
That's the end of the code to add logging to the error handler. Save the project and
compile the ActiveX DLL. Once the DLL has been successfully compiled, close this project
and open the test project you built earlier.
Open the Form_Load event of the frmTest form and add two lines to set the LogFileName and WriteLogFlag properties of the errHandler object. Listing 14.28 shows how to modify the code.
Private Sub Form_Load() ` Data1.DatabaseName = "junk" Set objErr = New errHandler ` objErr.WriteLogFlag = True objErr.LogFileName = App.Path & "\" & App.EXEName & ".log" ` End Sub |
Now, when you run the project, each error is logged to a file with the same name as the
application in the same folder as the application. In the preceding example, a file called
errTest.log was created in the C:\TYSDBVB5\SOURCE\CHAP14\ folder.
Listing 14.29 shows the contents of this error log file.
05-Feb-97 5:27:01 AM <3024> Couldn't find file `junk'. (in DAO.Workspace). 05-Feb-97 5:27:08 AM <11> Division by zero (in prjTest) |
You can easily modify the layout and the contents of the log reports. You need only change
a few lines of code in the LogError method.
The final touch to add to your error handler library is the option to keep track of and print a module trace. A module trace keeps track of all the modules that have been called and the order in which they were invoked. This can be very valuable when you're debugging programs. Often, a routine works just fine when it is called from one module, but it reports errors if called from another module. When errors occur, it's handy to have a module trace to look through to help find the source of your problems.
We implement our module trace routines as a new objclass object in the prjErrHandler project. Reload the ActiveX DLL project and add a new class module to the project. Set its Name property to TraceObject and keep its Instancing property set to the default, 5 - MultiUse.
You need two new properties for this object (TraceFileName and TraceFlag) and a handful of new Public methods:
First, add the code in Listing 14.30 to the general declarations area of the class module.
Option Explicit ` ` local property storage Private trcFileName As String Private trcFlag As Boolean ` ` internal variables Private trcStack() As String Private trcPointer As Long |
Next, create the two new Public properties, TraceFile and TraceFlag, and enter the code
from Listing 14.31 into the Property Let and Property Get statements for
these two new properties.
Public Property Get TraceFileName() As
String ` TraceFileName = trcFileName ` End Property Public Property Let TraceFileName(ByVal vNewValue As String) ` trcFileName = vNewValue ` End Property Public Property Get TraceFlag() As Boolean ` TraceFlag = trcFlag ` End Property Public Property Let TraceFlag(ByVal vNewValue As Boolean) ` trcFlag = vNewValue ` End Property |
Now add the code from Listing 14.32 to the Class_Initialize event. This code sets
the default values for the two Public properties.
Now it's time to code the various methods you need to manage call tracing in Visual Basic
5. First, create the Public Sub methods Push and Pop. These two
routines handle the details of keeping track of each function or sub as it is executed.
Listing 14.33 shows the code for these two Public methods.
Public Sub Push(ProcName As String) ` ` push a proc onto the stack trcPointer = trcPointer + 1 ReDim Preserve trcStack(trcPointer) trcStack(trcPointer) = ProcName ` End Sub Public Sub Pop() ` ` pop a proc off the stack If trcPointer <> 0 Then trcPointer = trcPointer - 1 ReDim Preserve trcStack(trcPointer) End If ` End Sub |
Now create another Public Sub method (Clear) and a Public Function
method (List). Add the code from Listing 14.34 to the class.
Public Function List() As Variant ` ` return an array of the trace log List = trcStack ` End Function Public Sub Clear() ` ` clear off the stack trcPointer = 0 ReDim Preserve trcStack(0) ` End Sub |
TIP: Notice the use of the Variant data type to return an array of items. This is a very efficient way to pass array data among Visual Basic methods.
Now create a new Public Sub called Dump. This writes the trace list to a disk file. Fill in the method with the code from Listing 14.35.
Public Sub Dump() ` ` write trace log to file Dim intFile As Integer Dim intLoop As Integer ` intFile = FreeFile Open trcFileName For Append As intFile Print #intFile, "***TRACE STACK DUMP***" Print #intFile, "***DATE: " & Format(Now(), "general date") Print #intFile, "" ` For intLoop = trcPointer To 1 Step -1 Print #intFile, vbTab & Format(intLoop, "000") & ": " & ÂtrcStack(intLoop) Next ` Print #intFile, "" Print #intFile, "***EOF" Close #intFile ` Exit Sub ` LocalErr: Err.Raise vbObjectError + 3, "Trace.Dump", "Can't write trace file [" & ÂtrcFileName & "]" ` End Sub |
Finally, create the Public Sub method called Show and enter the code
from Listing 14.36.
Public Sub Show() ` ` show trace log in dialog ` Dim intLoop As Integer Dim strMsg As String ` strMsg = "" For intLoop = trcPointer To 1 Step -1 strMsg = strMsg & Format(intLoop, "000") strMsg = strMsg & ": " strMsg = strMsg & Trim(trcStack(intLoop)) strMsg = strMsg & vbCrLf Next ` MsgBox strMsg, vbInformation, "Trace Stack" ` End Sub |
Notice that the code in Listing 14.36 prints the call array in reverse order. This is the
conventional way to print trace lists. The top-most entry shows the most recently executed
routine, and the bottom-most entry shows the first routine in this trace.
After adding this last code, save and compile the ActiveX DLL and then load your errTest project. After you load the frmTest form, add the following code to the general declarations area of the form.
Public objTrace As ObjectNext, update the Form_Load event as shown in Listing 14.37. This adds the use of the trace module to the project.
Private Sub Form_Load() ` Data1.DatabaseName = "junk" ` Set objErr = New errHandler Set objTrace = New TraceObject ` objTrace.Push "Form_Load" ` objErr.WriteLogFlag = True objErr.LogFileName = App.Path & "\" & App.EXEName & ".log" ` objTrace.Pop ` End Sub |
Note the use of objTrace.Push to add the name of the method onto the trace stack.
This should happen as early as possible in the method code. Note, also the objTrace.Pop
line at the very end of the method. This removes the name of the method from the stack
just as the method is complete.
Let's also add trace coding to the Command1_click event. Update your form's command1_Click event to match the one in Listing 14.38.
Private Sub Command1_Click() ` On Error GoTo Localerr Dim varList As Variant Dim rtn As Long ` objTrace.Push "Command1_Click" ` Print 6 / 0 ` Exit Sub ` Localerr: ` rtn = objErr.errHandler(Err, erritem, errresume, "prjTest.Form1.Command1_Click") ` objTrace.Show objTrace.Pop Resume Next ` End Sub |
Save this code and run the project. When you press the command button, you get a trace
report on the screen (see Figure 14.21).
Figure 14.21. Viewing the
trace message.
Notice that, in order to add module tracing to a project, you only need to add a .Push
line at the start of the routine and a .Pop line at the end of the routine. This
is all you need to do in order to update the procedure stack for the program. But, for
this to be really valuable, you have to do this for each routine that you want to track.
In a real application environment, you wouldn't want to show the procedure stack each time an error is reported. The best place for a stack dump is at exit time due to a fatal error. You should probably use the TraceFile option to write the stack to disk rather than displaying it to the user.
Now that you have the basics of error handling under your belt, you can continue to add features to the generic error handler. As you add these features, your programs take on a more professional look and feel. Also, using options such as error report logs and procedure stack logs makes it easier to debug and maintain your applications.
Additional features that you can add to your error handler include:
Today's lesson covered all the basics of creating your own error-handling routines for Visual Basic applications. You learned that an error handler has three basic parts:
You learned that an error handler has four possible exits:
You learned how to use the Err.Raise method to flag errors without resorting to modal dialog boxes.
You learned about the major types of errors that you are likely to encounter in your program:
You also learned that you can declare a global error handler or a local error handler. The advantage of the global error handler is that it allows you to create a single module that handles all expected errors. The disadvantage is that, due to the way Visual Basic keeps track of running routines, you are not able to resume processing at the point the error occurs once you arrive at the global error handler. The advantage of the local error handler is that you are always able to use Resume, Resume Next, or Resume label to continue processing at the point the error occurs. The disadvantage of the local error handler is that you need to add error-handling code to every routine in your program.
Finally, you learned how to create an error handler object library that combines local error trapping with global error messages and responses. The error handler object library also contains modules to keep track of the procedures currently running at the time of the error, a process for printing procedure stack dumps to the screen and to a file, and a process that creates an error log on file for later review.
1. Create a new project and
add code to a command button that opens the file C:\ABC.TXT. Include an error
handler that notifies the user that the file cannot be opened, and then terminates the
program.
2. Modify the project started in Exercise 1 by adding a new command button. Attach
code to this button that attempts to load a file named C:\ABC.TXT. Notify the
user that this file cannot be opened, and give the user the option and the dialog box to
search for the file. Exit the program when a selection has been made or if the user
chooses not to proceed.
Run this program and elect to find the file. Cancel out of any common dialogs that appear.
After this, create the file using Notepad and run the process again. Finally, move the
file to a location other than the C drive and run the program. Usethe common dialog to
search for and select the file.