Posted 16 years ago on 5/31/2008 and updated 10/16/2008
Take Away: Tracinging built-in events, setting breakpoints, compiler options, debugging without the debugger, types of errors, the error event, errorShow, and the try structure.
KB101145
The material in this article is from Appendix B of my Paradox 9 book titled, Paradox 9 Power Programming: The Official Guide.
Until now, it's been assumed that you are an excellent typist. Occasionally, you might have typed a routine incorrectly. In those cases, you had to spot the typo and debug the routine. This next section formally introduces the Debugger. It gives you the tools you need to debug your code with confidence.
ObjectPAL offers an advanced debugging tool called the ObjectPAL Debugger. The Debugger is a set of features built into the ObjectPAL Editor that helps you debug your application. With the debugger, you can inspect variables, list the events called, step through code, and monitor various elements of your application.
Using the Debugger
The debugger included in Paradox is very powerful. You can view your running application using the following debugger windows: Breakpoints, Call Stack, Watches, Tracer, and Debugger (see Figure 1).
Figure 1: The ObjectPAL debugger
Entering and Exiting the Debugger
Any time that you are in the ObjectPAL Editor, you can open any of the five ObjectPAL Debugger windows from the View menu or with the appropriate Toolbar icons. If your form or script is running, you can enter the Debugger in three ways:
If Compile with Debug is checked, add a debug() procedure to your code and run the form.
If Enable Ctrl+Break is checked, use Ctrl+Break when the form is running.
Set a breakpoint and run the form.
Placing a debug() statement in a method has the same effect as setting a breakpoint at a line. The advantage of debug() is that it is saved with the source code, so you don't have to keep resetting it as you would with a breakpoint. The setting for Compile with Debug is saved with the form and determines if debug() statements are ignored. When this option isn't checked, debug() statements are ignored. There is no need to uncheck this option before you deliver a form because the compiler strips out all debug() statements before it compiles. Using the debug() statement will not interrupt execution when the user runs the form.
To use the debug() procedure, follow these steps:
1. Place debug() in your code.
2. Make sure that Compile with Debug is checked.
3. Run the form.
One advantage of using the debug() procedure instead of setting a breakpoint is the ability to use it conditionally. For example:
1: if siCounter > 100 then
2: ;Execute code.
3: else
4: debug()
5: endIf
Note: When using a debug() statement in a library, the Compile with Debug option must be selected from within the library for it to take effect.
The Breakpoints Window
One of the most effective techniques for debugging an application is to set a breakpoint and then step through your code. A breakpoint is a flag that you can set in your code that stops a form during runtime and enters the Debugger. The Debugger enables you to inspect variables, step through your code, and much more. A common way of entering the Debugger is by setting a breakpoint. When you choose Program | Toggle Breakpoint, the visual minus sign appears next to the active line (see Figure 2). Go ahead and type the code that you see in Figure 2, put the cursor on the second msgStop(), and select Program | Toggle Breakpoint. In addition to selecting Toggle Breakpoint, you can double-click to the left of the line of code where you wish to place a breakpoint.
Figure 2: Setting a breakpoint
To see the currently set breakpoints, toggle the Breakpoints window open by selecting View | Breakpoints (see Figure 3). Right-click the Breakpoints window to gain access to its menu. When you run the form, execution stops right where you placed the breakpoint.
Figure 3: Open the Breakpoints window by selecting View | Breakpoints
When you are in debug mode, you have many options. For now, just select Program | Run or press F8 to continue execution. To use breakpoints, follow these steps:
1. Select the editor window you want to put a breakpoint--for example, pushButton, newValue, Var, cmMyCustomMethod, and so on.
2. Place the cursor on the line on which you want the breakpoint to occur.
3. Select Program | Toggle Breakpoint.
4. Run the form.
5. To view the current breakpoints, select View | Breakpoints to display the Breakpoints windows.
The Call Stack Window
The Call Stack window is used after execution stops at a breakpoint. Select View | Call Stack to toggle the Call Stack window open. The Call Stack window lists all the events, custom methods, and custom procedures called since the form started running. The most recently called routine and its caller are listed first. This process continues all the way back to the first method or procedure.
The Call Stack window is most useful when you want to know where you are. For example, if you've called several custom methods in your code and you want to verify that a certain custom method was called, use View | Call Stack. To use the Call Stack window, follow these steps:
1. Set a breakpoint at the place in your code from which you want to start viewing the stack.
2. Run the form.
3. When the breakpoint occurs, select View | Call Stack.A list of all the called methods appears. Right-click the Call Stack window to view its menu.
The Watch Window
The Watch window allows you to watch variables as your form executes. To toggle the Watches window open, select View | Watches. To add a watch, select Program | Add Watch, or right-click on the Watch window (see Figure 4).
Figure 4: The Watch window allows you to watch variables
The Tracer Window
ObjectPAL offers a powerful tracing utility that enables you to view the behind-the-scenes activity of your application. You can start the tracer before you run your form while in the Editor. A window that logs all the activity of your code and the events pops up.
The Properties | Tracer On option in the Tracer window toggles the tracer on. When the form is open, a window will display that traces the form, script, or report.
The Properties | Show Code option in the Tracer window toggles on and off whether the Tracer lists each line of code as it executes.
The Properties | Built-In Events option in the Tracer window allows you to select any events that you wish to trace. If you check the Properties | Trace On option and you haven't selected any events to trace, the ObjectPAL tracer opens a window and lists each line of code as it executes. Use this method to trace only the code that you write. This is a wonderful way to find the location of a problem. To trace your code, follow these steps:
1. Make sure that Program | Compile with Debug is checked in the main Paradox menu.
2. Select Properties | Trace On from the Tracer menu. Make sure that no events are checked.
3. Make sure Properties | Show Code is checked from the Tracer menu.
4. Run the form.
Note: The tracer requires that you have Program | Compile with Debug checked in order to trace your code. Compile with Debug has no effect on tracing built-in events.
Tracing Built-in Events
In addition to tracing only your code, you can trace your code and the events. If you check the View | Tracer option and select some events to trace, the ObjectPAL tracer opens a window and lists each line of code and each event as it executes.
Checking an event indicates that you want that event traced; unchecked events are not traced. It doesn't matter whether the event has code attached to it. If you check the event, it will be traced. Figure 5 shows the Select Built-In Events for Tracing dialog box with the action event select. The action event is perhaps the most important event to trace.
Figure 5: The Select Built-In Events for Tracing dialog box
When the box labeled Form Prefilter is checked, events are traced as they execute for the form and for the intended target object. Otherwise, events are traced only for the target object. Your settings for these options are saved with the form, so you don't have to check them every time you want to trace execution. When the Tracer is open, execution proceeds normally. ObjectPAL provides procedures for controlling the tracer. To trace your code and the events, follow these steps:
1. Select Properties | Built-In Events. Choose the built-in events that you want to trace (see Figure 5 shown previously). You can check any combination of events, but the fewer you check, the better. It will be easier to follow.
2. Make sure Properties | Trace On is checked.
3. Run the form.
You also can use the tracerOn() procedure in your ObjectPAL code, but you still have to manually select the combination of events that you want to trace.
The Debugger Window
You can get into and out of the Debugger, but what can you do with it? When execution stops at a breakpoint, you can inspect variables. Paradox has a built-in way to check a variable's value at a certain point in your code. To check a value, set a break at the point in your code at which you want to check a variable, and run the form. When your program breaks, select Program | Inspect to inspect as many different variables as you want.
There are three steps in inspecting a variable:
1. Set a breakpoint at the place in your code where you want to view a variable.
2. Run the form.
3. When the breakpoint occurs, select Program | Inspect from the Debug window and type the name of the variable that you want to see.
The options in the Debugger window fall into these four categories:
Entering and exiting the Debugger
Inspecting variables
Stepping through the application
Monitoring the application
The following paragraphs describe the more important options in the Debugger.
Program | Run: You can select this option before or after you set breakpoints or while you're in the Debugger. Using this option is equivalent to selecting Program | Run. If you haven't set any breakpoints, Run does nothing extra. After you set breakpoints, select Program | Run to run the form. Paradox saves all attached events, compiles the code, and runs the form. When Paradox encounters a breakpoint, execution halts and a Debugger window opens. In effect, this is how you enter the Debugger. When you're in a Debugger window, this option enables you to continue execution from the breakpoint.
Program | Step Over: Select Program | Step Over to step through your code line by line. You can use this option after execution stops at a breakpoint.
Program | Step Into: Select Program | Step Into to step through every line in a custom procedure. You can use this option after execution stops at a breakpoint.
Program | Stop Execution: Select Program | Stop Execution to exit the Debugger. This option halts execution and closes any Debugger windows. You can use this option after execution stops at a breakpoint.
Program | Inspect: Select Program | Inspect to display and change the value of a variable. You can use this option when execution stops at a breakpoint.
Program | Origin: Select Program | Origin to return to the event that contains the current breakpoint. The cursor will appear on the line that contains the breakpoint. You can use this convenient feature when execution suspends at a breakpoint and your screen becomes cluttered.
Program | Compile with Debug: Check Program | Compile with Debug to stop execution whenever the debug() statement is encountered. Placing a debug() statement in a event has the same effect as setting a breakpoint at that line. Unlike a breakpoint, the debug() procedure can be saved as part of your code. This option tells Paradox to provide more detailed error information. In most situations, I recommend that you leave Compile with Debug checked--even if you never use the debug() statement.
Note: When do you want to uncheck Compile with Debug? Breakpoints can be saved in code. Leaving "Compile with Debug" on during development is OK, but it is recommended that you turn it off if you plan to distribute non-delivered forms, libraries, or scripts. With Compile with Debug checked, your form, library, or script is about 1/3 bigger and runs slower. For best performance, turn off Compile with Debug if you distribute undelivered forms, libraries, or scripts.
Enable Ctrl+Break: If you check Enable Ctrl+Break, pressing Ctrl+Break in the ObjectPAL Preferences dialog box, execution suspends and opens a Debugger window that contains the active event, method, or procedure. This operation is similar to setting a breakpoint. If Enable Ctrl+Break to Debugger isn't checked, pressing Ctrl+Break still works, but it only halts execution. Although Ctrl+Break halts the execution of ObjectPAL methods and procedures, other operations, such as queries, are not affected.
It is also important to note that Enable Ctrl+Break will stop execution when Ctrl+Break is pressed, but you must also check Compile with Debug in order for Ctrl+Break to enter the Debugger.
Tip: Here is an undocumented tip. If you're like most programmers, the default error messages make sense most of the time, but not always. Select Program | Compile with Debug to get better error messages from the compiler. This applies even if you don't use the debug() procedure.
The Enable Ctrl+Break Option
To get into the Debugger easily, usetheEnable Ctrl+Break option and press Ctrl+Break. This technique suspends execution when your form is running. It enables you to decide on the fly whether--and where--you want to interrupt execution. Although this technique is less precise than setting a specific breakpoint or using the debug() procedure, you'll like its flexibility. To use Ctrl+Break, follow these steps:
1. Select the Edit | Developer Preferences and check the Enable Ctrl+Break option on the General tab.
2. Run the form.
3. Press Ctrl+Break when you want to move into the Debugger.
Stepping Through Your Application
Selecting Program | Step Over enables you to step through your code line by line. Selecting Program | Step Into enables you to step through every line in a event and through every line in a custom procedure that it calls.
To step through your code, follow these steps:
1. Set a breakpoint at the place in your code that you want to start stepping through.
2. Run the form.
3. When the breakpoint occurs, select Program | Step Into or Program | Step Over to execute the next piece of code.
If you want to try this, make sure that Compile with Debug is checked. Place the following code on a button and run the form:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: debug()
4:
5: message("1")
6: message("2")
7: message("3")
8: message("4")
9: endMethod
After you run the form, click the button. The Debugger window opens. Select Program | Step Into repeatedly to step through the messages.
Debugging without the Debugger
The Debugger is wonderful. Often, however, it's just as easy to debug code without the Debugger as it is to set a breakpoint and go into the Debugger. This section discusses general debugging techniques.
Debugging means locating the places where your application or routine doesn't work--but also the places where it does. If you're debugging, you're at a place where your application doesn't work, and you need to get to a place where it does work. One popular technique is to strip code and objects until something works, and then rebuild. ObjectPAL offers several ways to debug without the Debugger.
Types of Errors
A logic error is an error in thinking. Often, you try to code something that doesn't make sense because it's a logic error. Logic errors are among the most difficult types of errors to diagnose. If you knew that your thinking was wrong, you wouldn't try to implement the code in the first place! A runtime error is an error that occurs while a form is being run, even though the routine has passed a syntax check. A syntax error is an error that occurs because of an incorrectly expressed statement. Syntax errors usually occur when you mistype a command or when you attempt to use a command that doesn't exist.
Tip: If you want tighter, cleaner code, turn on the Compiler Warnings option. It gives you better control over your code. For example, not declaring variables slows down your code. The Compiler Warnings option catches undeclared variables and warns you. To turn on this option, go into the ObjectPAL Editor and select Properties | Compiler Warnings.
Warning and Critical Errors
In ObjectPAL, two levels of errors occur: warning errors and critical errors. Because warning errors aren't critical errors, they display nothing during runtime, or, at most, a message in the status bar. A key violation error is an example of this type of error. If you want to include a higher level of error trapping in your applications, use one of the following techniques:
If the method or procedure returns a Logical, use it in an if statement.
Use errorShow() to display the built-in error messages.
Use a try structure to trap for errors.
Warning errors do not stop execution, whereas critical errors do. To illustrate, type the following code into the pushButton event of a button.
12: msgInfo("", "After error") ;This message does appear.
13: endMethod
Now, raise the warning error to a critical error. Type the following:
1: method pushButton(var eventInfo Event)
2: var
3: tc TCursor
4: endVar
5:
6: errorTrapOnWarnings(Yes) ;Raise warning errors to
7: ;critical errors.
8:
9: msgInfo("", "Before error")
10: tc.open("123xyz") ;123xyz does not exist.
11: msgInfo("", "After error") ;Note that this message never appears.
12: endMethod
This is an important part of dealing with errors. Note that warning errors do not stop execution of code, whereas critical errors do.
Using the Built-In error event
So far, I haven't discussed the built-in error event. Despite what you might think, the built-in error event is not the preferred place to put code. The reason is that the built-in error event is always called after the error occurs. Most of the time, you are going to want to know before the error occurs so that you can take appropriate steps. Sometimes, however, you will want to simply add to or alter an error message.
Use an if Statement to See Whether a Routine Is Successful
A technique commonly used for error checking is to use an if statement. Many methods and procedures return a Logical, and display or do one thing when a routine succeeds and another when it fails. For example, type the following into the pushButton event of a button.
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: if isFile("AUTOEXEC.BAT") then ;If exists, then
4: message("File exists") ;display "File exists".
5: else ;If not,
6: message("File does not exist") ;dislplay "File does not".
7: endIf
8: endMethod
I used a variation of the preceding error-checking routine in the form open routines in an earlier chapter:
1: if f.attach("My Form") then ;MY FORM is a title.
2: f.moveTo()
3: else
4: f.open("MYFORM") ;MYFORM is a filename.
5: endIf
You can use an if structure for many methods and procedures to give the user a better user-oriented message than the programmer-oriented, built-in error messages. For example, type the following code into the pushButton event of a button:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: tbl Table
5: endVar
6:
7: if isTable("WORLD.DB") then
8: tbl.attach("WORLD.DB")
9: else
10: msgStop("Oops!", "Could not find WORLD.DB. Check your working directory")
11: endIf
12: endMethod
Table.attach() returns True if the Table variable is associated with the table name. It doesn't report whether it is a valid table, or even that the file exists!
Use errorShow() in an if Statement
With Paradox, you can utilize the built-in error stack. To do this, you use the errorShow() procedure. The errorShow() procedure displays the error dialog box with the current error information from the error stack. For example, type the following code into the pushButton event of a button:
;Button :: pushButton
method pushButton(var eventInfo Event)
var
tc TCursor
endVar
if not tc.open("xyz") then
errorShow("Table xyz is missing or corrupt", "Try rebuilding or reinstalling")
endIf
endMethod
If the open event fails for any reason, an error message is displayed. You won't see benefits of this type of error checking while you develop. Instead, the benefits come when users use your program. Without this extra code, you might get a telephone call from a user who says, "The program doesn't work. When I click this button, nothing happens." With the extra code, the user would get a specific message--for example, an error saying that a table doesn't exist (see Figure 6). The user could check whether the table exists on the disk, and you would be spared the telephone call.
Figure 6: Using the errorShow() procedure
The errorShow() procedure also can accept two optional string parameters that you can use to add text to the error box. When you use hundreds of errorShow() procedures in a large project, adding text to the error box can really help. The following is the complete syntax for errorShow():
You can use topHelp and bottomHelp for anything you wish, but here is a suggestion. Use the topHelp for the name of the object followed by the path to the code, and bottomHelp for extra information. For example:
1: if not tc.open("LINEITEM") then2: errorShow("Secrets.fsl :: button1 :: pushButton", "Open routine failed")3: endIf
Using the try Structure
To deal with errors yourself, you can place a try structure around your code. If an error is detected, you can use reTry, execute optional code, or display an error message that's better or more complete than the built-in error message. The following code uses a variation of this technique. It displays the built-in error messages, which normally wouldn't be triggered:
1: errorTrapOnWarnings(yes)
2: try
3: ;Your code here
4: onFail
5: msgStop( errorCode(), errorMessage() )
6: ;You could also use errorShow()
7: endTry
Use errorTrapOnWarnings(Yes) to raise warning errors to critical. If you wish to write really tight ObjectPAL code, do the following three things:
Use errorTrapOnWarnings(Yes) in the open event of every form.
Make sure that Properties | Show Compiler Warnings is checked for every form.
Make sure that Properties | Compile with Debug is checked for every form.
Sometimes, however, you will want to turn off errorTrapOnWarnings(). The most common time is when you are using the warning error in an if statement.
Another technique to raise warning errors to critical is with setReason(). Suppose that you wish to use setReason() to raise warning errors to critical. You might wish to use this technique when filtering out reasons. Add lines 2--4 to the error event of the form.
The errorTrapOnWarnings(Yes) makes warning errors critical errors. Specifically, it has the effect of the following:
The built-in error event will be called.
The standard error dialog box will be shown.
onFail will be called in a try structure.
The next line of code will not execute.
Use view() to View a Variable
Sometimes, it's convenient to use view() to view a variable in your code. view() also provides a stopping point that can help you narrow down a problem. For example, type the following into the pushButton event of a button.
This code doesn't shed light on any problems because it has no bugs. The code demonstrates, however, how view(), heavily used, breaks a large piece of code into smaller parts. If a problem existed, you might get a view box before the problem appeared. That would enable you to get closer to the problem. The closer you are to a problem, the easier it is to fix. You also can turn on the Compile with Debug option to receive better error messages.
Use the fullName Property
If you're having trouble with scope or the containership hierarchy and you need to know the full containership path of an object, use the following example. It assumes that an object named theBox is on a form:
Sometimes you'll be surprised about the full path of an object. Using the fullName property involves checking yourself. Some people call this type of debugging a reality check because you test your own perception of what's going on. If you're absolutely positive that an object has a certain path, do a reality check with fullName.
Use the Object Tree
Another way to check an object's path is to use the Object Tree inspector. The Object Tree is part of the Object Explorer and is a visual hierarchical display of a form and its containers. The Object Tree is most valuable when you come back to your own old code or analyze someone else's application. It's also a valuable everyday tool for opening many methods and for accessing stacked objects more easily. You can even print the Object Tree for a permanent printed record (see Figure 7).
Figure 7: The Object Tree allows you to see all the objects
Summary
Now that you have the basics of debugging ObjectPAL down, you can develop a style of your own. When problems arise, you'll be able to isolate them quickly. You'll be able to correct your mistakes, straighten out your logic, or find a solution that works around the problem.