Chapter 15

Debugging and Error Trapping


CONTENTS

Control creation presents new challenges in the area of debugging. It can sometimes seem as if you're debugging two programs at once-the ActiveX control and the Visual Basic application in which it resides.

The key, as you've seen already from the previous examples in this book, is to keep your control project separate from your EXE project until you're fairly sure your control is ready for prime time. The fact that Visual Basic now enables you to keep multiple projects in the same development environment is a great help in this area; it enables you to keep a "throwaway" EXE project lying around any time you need it.

Planning ahead can also give you an edge when it comes to dealing with problems in your code. For example, if you start your project by making a list of your control's properties, their data types, and whether those properties are to be delegated or stored in variables, it's less likely that you're going to forget the name of an obscure variable or property.

Good planning leads to fewer bugs. But you knew that already. This chapter deals with what happens after you've planned and coded and adjusted your expectations and you discover that things are still messed up.

Debugging

Debugging is the practice of taking care of problems in your code after you've written it. Whether these problems are comprised of bugs (you committed a syntactical mistake in your code) or errors (your code doesn't come up with the correct result), the debugging features of Visual Basic can help you figure out what's wrong.

This section describes all of the debugging features of the Visual Basic IDE, highlighting the new features of Visual Basic 5.0 where appropriate. Because VB's debugging features have only a tangential bearing on control creation, I tried to keep the demonstrations succinct. This was so I could leave more room for the second half of this chapter, which covers error-handling, and which has more of a bearing on control creation.

The Immediate Window

In previous chapters, you used the Immediate window as a place to view the results of the Debug.Print method. But you can also issue commands to Visual Basic through the Immediate window. This is useful when you want to execute individual lines of code to see what they'll do at the point the program is currently at.

Typing code into the Immediate window works only when you've paused program execution. When a program has been paused as the result of a pre-programmed breakpoint or as a result of the programmer pressing Ctrl+Break, it is said to be in break mode. The following example shows how to enter break mode-pausing a program while it's running-by using Ctrl+Break.

To see how to issue commands to a running program through the Immediate window:

  1. Start a new EXE project.
  2. Add a command button to the form. In the form's Click event, enter the following code:
Private Sub Command1_Click()

For x = 1 To 1000000
    Debug.Print x
    y = 3.14159
    z = y * x

Next x

    This code simulates the code you'd normally see in a real program.

  1. Run the code by using the function key F5.
  2. Click the command button on the form. You should be able to see the Immediate window fill up with all kinds of meaningless numbers.
  3. Press Ctrl+Break on your keyboard. The program stops. The currently active line is highlighted, as illustrated in Figure 15.1.
    Figure 15.1 : Break mode.

  4. Position your mouse pointer on top of one of the variables in the code (but don't click). After you hold the mouse pointer over a variable for a second or so, Visual Basic displays the value of that variable, as displayed in Figure 15.2.
    Figure 15.2 : Auto Data Tip in Break mode.

  5. Now click in the Immediate window. Type the code:
x = 999999

    and press Enter.

  1. Run the program by pressing the function key F5. You should be able to see that the program picks up from where it left off, but because you increased the value of the counter x to 999,999, it only goes through the loop once more before exiting the Click event procedure.

TIP
To exit break mode and resume running your code, press F5.

Using the Immediate window is helpful when you're trying to track down the source of a bug. But using Ctrl+Break can be a bit tricky when you're trying to break the program at a particular line of code. It's doubly so when you're trying to get at your control project's running code from the EXE project. That's where breakpoints come in.

Breakpoints

A breakpoint halts program execution when your application comes to a certain line of code. You can set as many breakpoints in your project as you want, except for lines of code that aren't executed-such as variable declarations, comments, and blank lines.

You can use breakpoints in control projects just as you do in normal Visual Basic code. To place a breakpoint, you must have a code window open and have the insertion point on the line of code where you want your breakpoint to be. There are four ways to place a breakpoint in a line of code:

TIP
By far, the mouse shortcut is the easiest way to set a breakpoint.

To resume running your code after you're done examining it in break mode, press F5. Since you can have as many breakpoints as you want in your code, the code will run until it reaches another breakpoint, or otherwise pauses or terminates.

The Stop Statement

Inserting a Stop statement in your code is the same as placing a breakpoint there. In some ways, a Stop statement is more convenient, since you can type it without having to fool around with the mouse, or a function key, or any of that other hoo-hah. To insert a Stop statement in your code, simply type the statement

Stop

on a line by itself. When your code runs, Visual Basic will stop execution as if there were a breakpoint there.

Stepping through Code in Break Mode

Once your program is in Break mode, you can step through the lines of code that follow the breakpoint, one line at a time. Stepping through the code enables you to see the effect of each line of code as it is executed. While you're stepping through each line of code, you can use the Immediate window to execute additional lines of code, or use Auto Data Tips to inspect the values of variables at any point (as described in the preceding section).

The easiest way to step through code is to use the function key F8, although you can also use the menu command Debug, Step Into. To see how to step through code, do the following:

  1. Open the code snippet from the previous demonstration. Using your favorite method from the list above, set a breakpoint on the next-to-last line of code, so that the code looks like Figure 15.4.
    Figure 15.4 : Breakpoint in code.

  2. Run the EXE project and click on the command button. The code runs, pausing at the line where you set a breakpoint, as illustrated in Figure 15.5.
    Figure 15.5 : Code in Break mode.

  3. Press the function key F8 a few times. You should be able to see the code execute one line at a time.

Stepping over Code

While you're stepping through code, you can step over code that you know doesn't need to be scrutinized. For example, say you're debugging a subroutine that contains code of its own, as well as calls to other subroutines. If you've already verified that the other subroutines are bug-free, you'd probably want to step over them rather than stepping through them for the hundredth time. You might also want to step over a line of code to see what happens when it is omitted.

You step over code in Break mode by using the keystroke shortcut Shift+F8 or by choosing the menu command Debug, Step Over. This ignores the current line of execution; stepping over a subroutine or function in this manner prevents the subroutine or function from executing.

TIP
You can step over only one line at a time. To step over multiple lines at a time, use the Set Next Statement command described later in this chapter.

Running to Cursor

Run to Cursor is another new feature of Visual Basic 5.0 used in Break mode. The command instructs Visual Basic to start running code up to the point where you've placed the cursor. When execution hits the line where the cursor is at, VB returns the program to Break mode. In a sense, the Run to Cursor command is sort of like a temporary breakpoint, because it doesn't persist after you've run the command. To see how run to cursor works:

  1. Run the EXE project code. Execution stops at the break point.
  2. Click on the line that contains the Debug.Print statement.
  3. Select the menu command Debug, Run to Cursor (or use the keystroke shortcut Ctrl+F8). The code runs up to the point where your cursor is, then goes back into Break mode.

Stepping Out

You use the Step Out command to tell Visual Basic to finish executing a particular subroutine, then return to Break mode. This is another Break mode command that is new to Visual Basic 5.0.

For example, say you're stepping through code and you inadvertently step into a subroutine you've already debugged. You can use the Step Out command to whiz through the remainder of the already debugged subroutine and return to stepping through the original code. To see how this works:

  1. Modify the example code so it contains a subroutine. The code should now look like this:
Private Sub Command1_Click()

For x = 1 To 1000000
    Debug.Print x
    Spoon
    y = 3.14159
    z = y * x

Next x

End Sub

Private Sub Spoon()
    For a = 1 To 10
        Debug.Print "Spoon!"
    Next a
End Sub
  1. Run the code. Execution is paused at the break point.
  2. Step through the code one step at a time until execution moves to the Spoon subroutine, as illustrated in Figure 15.6.
    Figure 15.6 : Stepping out of the Spoon subroutin.

  3. Choose the menu command Debug, Step Out. The remainder of the Spoon subroutine is executed and control returns to the Click event procedure.

Setting the Next Statement to Be Executed

In Break mode, you can designate the next line of code to be executed. This is helpful when you want to skip a number of lines or when you want to execute a number of lines again.

You set the next statement to be executed by following these steps:

  1. In Break mode, click on the line of code you want to be executed next.
  2. Select the menu command Debug, Set Next Statement, or use the function key shortcut Ctrl+F9, then restart execution by choosing the command Run, Continue (or use the function key F5). The program immediately resumes running at the point you set.

In Visual Basic 5.0, there is a new and easier way to set the next statement to be executed. In Break mode, you can click-drag the margin indicator that designates the next line to be executed. To see how this works:

  1. While in Break mode, use the mouse to click-drag on the yellow arrow in the margin; this margin indicator shows which line of code will execute next. Drag the indicator to the last line of code, as illustrated in Figure 15.7.
    Figure 15.7: Dragging the margin indicator.

  2. Continue execution by choosing the menu command Run, Continue, or by pressing the function key F5. You should be able to see that you've broken out of the loop by moving the next line of execution to the last line of the subroutine.

NOTE
You can clear all the breakpoints in your project at any time by choosing the menu command Debug, Clear All Breakpoints, or by using the keystroke shortcut Ctrl+Shift+F9.

Using Option Explicit

When you want to use a variable in Visual Basic, you simply make a reference to it. There are very few restrictions on what variables you can use or how many or how they need to be declared.

This is a good thing if you're new to Visual Basic or if you're brainstorming. (See the note at the end of this section for my thoughts on that.) But if your program has a lot of variables, or if you change the names of variables around quite a bit, you'll sooner or later run into a situation where you make reference to an uninitialized variable. In Visual Basic, an uninitialized variable is equivalent to zero, so this represents a problem if you thought you were referring to a variable that contained some meaningful value.

The Option Explicit statement forces you to declare all the variables you use. When you use explicit variable declaration, the program warns you when you make reference to a variable you haven't explicitly declared. To turn explicit variable declaration on, you type the code:

Option Explicit

in the declarations section of a code module. All of the procedures in that module will then be subject to explicit variable declaration.

For example, consider the following code:

Private Sub cmdCalcVolume_Click()
    theVolume = intHeight * intWidth * intDepth
    MsgBox "The volume is" & theVolume
End Sub

The assumption here is that the variables intHeight, intWidth, and intDepth were declared and initialized elsewhere (possibly at the module level). But if you forgot to declare these variables, or if you named them something other than intHeight, intWidth, and intDepth, they'll be uninitialized when this code runs. The volume calculation will always be zero.

If you use explicit variable declaration, Visual Basic spots this problem as soon as you attempt to run the application. The undeclared variable is highlighted and the error message in Figure 15.8 appears.

Figure 15.8 : Variable not defined error.

To resolve this problem using Option Explicit, you'd modify the code as follows:

' Declarations
Option Explicit

Private Sub cmdCalcVolume_Click()
    theVolume = intHeight * intWidth * intDepth
    MsgBox "The volume is" & theVolume
End Sub

The Locals Window

The Locals window, a new feature to Visual Basic 5.0, lets you see the state of all of the variables and properties in your project.

To see how the Locals window works, select the menu command View, Locals from an application that's in Break mode. The Locals window appears, as illustrated in Figure 15.9

Figure 15.9 : Locals window.

As you can see, in addition to showing the value of each variable, the Locals window also shows you the data type of each variable, including the Variant type, if applicable.

The Locals window also shows you the current value of all the properties in the active form. To see this information, click on the plus to the left of the Me item in the Locals window (Me always refers to the active form). The list of properties expands into an outline, as illustrated in Figure 15.10.

Figure 15.10: Expanded Locals outline.

Should You Be Explicit?
Although every book on professional Visual Basic programming ever written emphatically espouses the use of Option Explicit in every code module you ever write, I'm here to say that I don't use Option Explicit when I first start writing code. This is because I go through a brainstorming phase (right after I scrupulously plan out my project's feature set on a sheet of graph paper). In this phase, I add and remove variables willy-nilly.
When I'm at this point in the project, I don't like to be slowed down by having to return to the top of the procedure to declare a new variable when I need one. When my procedure is close to doing what it needs to do, I throw an Option Explicit into the code module and start debugging. This method may not work for you; it's one man's approach, submitted for your consideration.

You can also use the Locals window to inspect the relationship between your UserControl and its container. If you place a UserControl on an EXE project form and open the Locals window in Break mode, you'll see an entry in the outline for your UserControl. Click on the plus to the left of the UserControl in the list to expand the outline, and you'll see something like Figure 15.11.

Figure 15.11: Ambient properties in Locals window.

The Locals window also enables you to change the value of a variable or property while in Break mode. To see how this works:

  1. Run an EXE project, then put it in Break mode by pressing Ctrl+Break.
  2. Open the Locals window by choosing the menu command View, Locals.
  3. Click on the plus sign to the left of Me. The outline expands to display a list of properties for the current form.
  4. Click in the Value column to the right of the BackColor property.
  5. Change the BackColor value to 255. You should be able to see that the form's background color has turned red.

Monitoring Expressions Using Watches

You can use watches to monitor the state of a variable or expression. You can also use a watch to pause program execution when a variable expression changes.

When you set a watch, it appears in the Watches window. From there, you can monitor the state of the variable or expression. To see how this works:

  1. Click-drag to select the variable x in your example code. (This code exists in the Spoon project on the CD-ROM that accompanies this book; the project group filename is Spoon.vbg.)
  2. Choose the menu command Debug, Add Watch. The Watches window appears, displaying the watch you've just set, as illustrated in Figure 15.12
    Figure 15.12: Watches window.

  3. Remove any breakpoints you may have set up in the code (by using the menu command Debug, Remove All Breakpoints or the keystroke shortcut Ctrl+Shift+F9).
  4. Run the code, then use Ctrl+Break to pause it. The Watches window displays the current value of x, as illustrated in Figure 15.13.
    Figure 15.13: Watches window in Break mode.

Just as you can change the value of a property or variable in the Locals window, Visual Basic 5.0 now lets you change the value of a variable in the Watches window. To see how this works:

  1. In Break mode, click on the value of x in the Value column of the Watches window.
  2. Type in the value 999999.
  3. Continue program execution by using the menu command Run, Continue (or by pressing the function key F5). The code should iterate once more, then complete.

To delete a watch, right-click on the watch you want to delete in the Watches window, then from the pop-up menu, choose Delete Watch.

Quick Watch

The Quick Watch feature is a holdover from the Instant Watch feature of previous versions of Visual Basic. You use a quick watch to inspect the value of a variable or expression in Break mode.

This feature is essentially supplanted by the Auto Data Tips feature, which enables you to see the value of a variable or expression by simply positioning the mouse pointer over your code in Break mode. But the Quick Watch feature is helpful if you think you might attach a formal watch to the variable you're inspecting. To see how this works:

  1. Run your code example, then put it in Break mode.
  2. Click-drag to select any instance of the variable y in your code.
  3. Select the menu command Debug, Quick Watch, or use the keystroke shortcut Shift+F9. The Quick Watch dialog box appears, displaying the current value of the variable, as illustrated in Figure 15.14.
    Figure 15.14: Quick Watch dialog box.

  4. Click on Add. Notice that the Watches window appears and your quick watch has been converted into a formal watch.

Using Assertions

You can use the Assert method of the Debug object to test whether an operation succeeded or not. If the test is False (that is, the operation was unsuccessful or the expression returned zero or the value False), execution is paused on the line that contains the assertion. So in a sense, an assertion is like a conditional breakpoint.

Let's say you've encountered a bug in the sample application. You've determined that if the variable z goes above a value of 32,000, the bug is triggered. But you're not sure where in your code z exceeds 32,000, or why that causes an error to occur. You can use an assertion to find out. To do this:

  1. Change the code of the Click event in the Spoon project as follows:
Private Sub Command1_Click()
Dim x As Long, y As Single, z As Single

For x = 1 To 1000000
    Debug.Assert (z < 32000)
    y = 3.14159
    z = y * x
Next x

End Sub
  1. Run the code. The code will run, then break when the value of z equals 32,000.

NOTE
You can also use a watch to accomplish the same thing as the assertion described in this example. However, watches can only be set to break when an expression is True, as opposed to assertions, which always break when an expression is False. Additionally, watches are not saved along with your code; when you close a project and re-open it, the watches you've set are lost. Assertions, in contrast, stay with your code and are saved along with it.

Error-Handling

Error-handling, sometimes referred to as error-trapping, is a vital component of any programming project; it's particularly important in an ActiveX control. It refers to the process whereby a program attempts to deal with unexpected problems-anything from a mismatched variable data type to a full hard drive.

In a conventional Visual Basic project, when you fail to include an error-handler in a procedure, any error your program encounters causes the program to stop executing. When your ActiveX control generates an unhandled error, the error is passed along to the host application.

Writing Error-Handlers

You use the On Error statement to handle errors in procedures you write. When you initiate error-handling through the use of an On Error statement, that error-handler remains in effect for the duration of the procedure, unless it is superseded by another error-handler you enable or it is shut off by you. There are three forms of the On Error statement:

On Error GoTo line jumps program execution to another point in the procedure (by convention, at the end of the procedure). On Error Resume Next tells the procedure to ignore any errors generated by the procedure and proceed as if nothing has happened; the implication is that your code is either going to ignore the error or handle it on the line immediately following the error. On Error GoTo 0 disables the current procedure's error-handler. It is used very infrequently; in fact, the only situation in which I'd use it is in an error-handler, in order to prevent an endless loop. (An endless loop is where the procedure encounters an error and hands control to an error-handler, which then encounters an error and then attempts to hand control to an error-handler, ad infinitum.)

Here's an example of the various incarnations of the On Error statement. In previous examples in this book dealing with controls designed for use in Web pages, we included the minimal error-handler

On Error Resume Next

in the control's InitProperties event. We did this to avoid problems with the Extender object, since you're not supposed to rely on the Extender object's availability.

On Error Resume Next used in this way isn't really an error-handler at all, since it tells the procedure to eat any error it may encounter, no matter how heinous. (In the SoundButton example in Chapter 13, for example, we used this as a cheap way to get around the fact that a control in a Web page can't get the same Extender properties as a control in a VB project.)

Now that you know all about error-handlers, you can write more adept error-handlers for your controls. Consider the following modification to the InitProperties event of the SoundButton control:

Private Sub UserControl_InitProperties()
    On Error GoTo InitPropErr

    Caption = Extender.Name

Exit Sub

InitPropErr:
    MsgBox "InitProperties: " & Err & " - " & Error$
    Resume Next

End Sub

This is an example of an extremely rudimentary placeholder error trap I use when I'm in the process of figuring out what errors my procedure is likely to encounter. The code you see here is better than On Error Resume Next, but not much better. (You can see that as a result of the Resume Next in the error-handler, any error encountered by the procedure is ignored anyway.)

Also, the MsgBox statement in the error-handler violates the guideline that your control raise errors rather than display message boxes. But this is just a placeholder, so humor me for a few minutes while I get through this demonstration.

The most likely error this procedure will encounter has to do with the nefarious Extender object. For example, Access 95 can't deal with the concept of Extender.Name; if you place the control in a Microsoft Access form, the control displays the error message illustrated in Figure 15.15. Access then proceeds to insert the control anyway, even though your control encountered an error. Go figure.

Figure 15.15: SoundButton error message.

Instead of just generating an error message, it's better if your control attempts to do something intelligent to resolve the error condition. Because you inserted a rudimentary error trap in the control's InitProperties event, you now know in which procedure the error took place, as well as what error number was triggered.

Because the whole point of the code in the InitProperties event is to give the SoundButton control a default caption, the resolution of the problem is simple. When the control encounters error 438 in its InitProperties event, supply some generic default caption (like SoundButton, rather than SoundButton1).

But what happens when a totally unexpected error happens, something you couldn't have planned for? In a Visual Basic EXE project, you'd write code to gracefully abort out of the subroutine, or perhaps even shut down the application if the error took place in a critical spot. But in an ActiveX control, the appropriate response to an unexpected error is to raise it to the level of the host application. You raise an error to the host application by using the Raise method of the Err object, as explained in the next section.

Error-Handling Options

You have the ability to control the conditions under which Visual Basic will halt your application in situations where the application encounters an error. You do this in the General tab of the Tools, Options dialog box, as illustrated in Figure 15.16.

Figure 15.16: Options dialog box.

Here is what the different levels of error-handling mean:

NOTE
Class modules are covered in Chapter 16. For the purposes of this setting, your ActiveX control projects are not considered to be class modules, even though they have a number of attributes in common with class modules.

ERR Statements versus Objects
In the previous examples of error-handling in this chapter, you saw that you could get the number of the error by using Err. Once upon a time, Err was a Visual Basic statement, but now that Err is an object, when you refer to Err, you're really making a reference to the default property of the Err object. The default property of Err is Number, so references to Err.Number and Err are really the same thing.
Similarly, references to Error$ and Err.Description really refer to the same thing. It's probably better (from the viewpoint of clarity, correctness, and all that) if you use the new, improved, object-style references to Err properties, but the old style works, and old habits die hard, and Err is easier to type than Err.Number. It's obviously up to you which style you use.

NOTE
Be aware that when you change the level of error-handling, the change becomes a part of the Visual Basic IDE, not part of the current project, so the change you make will be reflected in all the projects you work with until you change the setting back.

The Err Object

In Visual Basic, information about errors is handled through the Err object. Err is a system-provided object that contains information about the last error that took place in your code. In addition, you can use the Raise method of the Err object to cause your application (or, more likely, your control project) to signal that an error condition has arisen.

When your control raises an error, it's sending a message to the host application. This is distinguished from error-handling, which attempts to deal with an error condition. Raising an error means that your control has encountered an error and is passing that information on to the host application. Table 15.1 shows a complete listing of the properties of the Err object, and Table 15.2 covers its methods.

Raising Errors

Although you're probably accustomed to seeing ActiveX controls display message boxes when they encounter error conditions, your control shouldn't display such message boxes with the MsgBox statement. Instead, your control should raise an error, sending a message back to the host application that an error has taken place and enabling the host application to deal with the error.

Table 15.1 Properties of the Err Object

PropertyDescription
DescriptionThe textual description of the error
HelpContextThe Help Context ID of a help topic that describes this error
HelpFileThe Windows Help file that contains a help topic that describes this error
LastDLLErrorThe error code returned from a DLL call. (This property is only relevant if the error was raised as the result of a call to a DLL.)
NumberThe number of the error. If no error exists, Number equals zero.
SourceThe object or process that generated the error.

Table 15.2 Methods of the Err Object

ClearClears the properties of the Err object
RaiseRaises an error to the host application

When your control raises an error, one of two scenarios takes place:

You can modify your control's error-handler so it will react more intelligently to this error condition, raising an error instead of displaying a message box. Here's a basic example of a procedure that does this:

Private Sub UserControl_InitProperties()

    On Error GoTo InitPropErr

    Caption = Extender.Name
    
Exit Sub
    
InitPropErr:

    Select Case Err
        Case 438  ' Obj doesn't support this property
        Caption = "SoundButton"
        
        Case Else"
        Err.Raise Err, "SoundButton", Err.Description
        
    End Select

End Sub

TIP
Notice that the Select Case in the error-handler uses the error number (438), rather than the textual description of the error ("Object doesn't support this property"). This is because error numbers are guaranteed not to change from one version of Visual Basic to the next, while descriptions can change.
You may want to consider declaring and using constants for error numbers so you don't have to sit around wondering what "438" means. Using a constant like ERR_OBJECT_DOESNT_SUPPORT can make much more sense, depending on your programming style. I like using the numbers and commenting them, as in the above example, so I don't have to remember what I named my constants.

Having a single error-handler for a procedure is nice because all the error-handling code is in the same place. However, this may not give you the granularity you need to handle the many things a particular procedure may be called upon to do. As an alternative, you can handle potential errors line-by-line, in this way:

Private Sub UserControl_InitProperties()

    On Error Resume Next

    Caption = Extender.Name

    If Err Then
        Select Case Err
            Case 438  ' Obj doesn't support this property
            Caption = "SoundButton"

            Case Else
            Err.Raise Err, "SoundButton", Err.Description

        End Select
    End If

End Sub

In this case, there is no On Error Goto; instead, the value of Err is inspected after any line that might cause problems. If Err is nonzero, the error is handled immediately.

Handling errors line-by-line requires more work on your part, but it's vital if you have a procedure that may need to handle the same error in several different ways depending on where in the procedure the error comes up.

Summary

In this chapter we talked about debugging and error handling, particularly with respect to control creation. We covered error-handling as well as raising errors encountered by your control project to the level of the host application.

In the next chapter we'll take a look at object-oriented programming concepts in Visual Basic, and how to put them to work in your control projects.