Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
ERROR()
, LINENO()
, MESSAGE()
, ON ERROR
, On(“Error”), ON ESCAPE
, On(“Escape”), ON READERROR
, On(“ReadError”), Sys(2018)These handy functions and commands provide some key debugging facilities to the Visual FoxPro development environment. ERROR()
, LINENO()
, MESSAGE()
and SYS(2018) can provide details about what went wrong, while you can use the ON ERROR
, ON ESCAPE
and ON READERROR
event handlers to control the flow of events and to determine what to do now. We think the ON techniques are mostly relics of bygone Xbase days, but have some suggestions on how to handle errors in Visual FoxPro. ERROR is a cool command that lets you test all the error-handling functions once you have them in place.
ERROR nNumber [, cMoreInfo ] | cCustomMessage
Parameter |
Value |
Meaning |
nNumber |
Integer |
The value of a valid error message—most seem to be in the range of 1 to 1999, although more than a few in that range are still open. Passing a bad error number results (surprise!) in an error—1941, to be precise: "Error code is not valid." Sheesh! |
cMoreInfo |
Character |
If the error message can provide specific information, you can pass that information to the error handler here. This is the equivalent of the information passed back by SYS(2018), and is displayed in the Help file listing as "<name>", as in "File <name> not found." |
cCustomMessage |
Character |
You know those advertisements and billboards that say "Your message could be here?" Well, never to be left out, Visual FoxPro provides this opportunity for you, too, to write error messages. All generate error 1098, the "User-defined error," but with your message. There is no way we can see to supply the equivalent of SYS(2018), however. |
This command allows you to do many things: You can pass on an error condition that you don’t want to handle locally, or you can create your own error. You can test your error-handling routines by generating errors 1 through 1999. Or you can use the built-in facilities to display your own error messages, using Error 1098, a custom user-defined error and message.
We'd like to be able to use ERROR in an object's ERROR method to invoke the global error handler. Unfortunately, it doesn't work. It calls the default Cancel/Suspend/Ignore dialog. See the entry for the Error method for our thoughts on this one. |
ERROR 1767, "its child"
* Produces the error message "Parent object will not
* allow this property setting for its child" - try to say
* that with a straight face.
ERROR "TILT!" && Generates error 1098
nError = ERROR()
cErrorMessage = MESSAGE()
cCode = MESSAGE(1)
cMoreInfo = SYS(2018)
The ERROR()
function returns the last error, if an ON ERROR
trap is in place. If there have been no errors, or the error handler is not in place, ERROR()
returns zero. ERROR()
is also reset by a RETURN or RETRY command, so the very act of finishing the ON ERROR
routine clears the error code. Preserve this value within your ON ERROR
routine, if needed. ERROR()
does not detect low-level or DDE errors; use FERROR()
or DDELastError()
for those sub-languages.
The two values of the MESSAGE()
function preserve the error message and the code of the offending line. The error message hangs around forever, until the next error overwrites it. MESSAGE(1) is a funny function. When you’re running the interactive version of VFP, sometimes it returns the original line of code even if the program file is specifically compiled with the encrypt option! Sometimes, rather than displaying the entire line of code, the error handler translates the tokenized FoxPro command into its original form, and displays it with an ellipsis if additional information was included in the command. The Help claims this occurs “when the source code is unavailable.” We’ve usually found that to mean “when the source file is not in the current directory or on the path.” Finally, in the runtime version, MESSAGE(1) returns the same error message that’s displayed with MESSAGE()
.
SYS(2018) returns the parameter or additional information supplied to a command that may have caused the error to occur. This might be the name of a file or memory variable that does not exist. This value is used internally by the error handler to display specific error messages, such as “File <name> does not exist.” SYS(2018) returns the <name> portion of the error, if one exists.
You may want to replace these calls with a single call to AERROR()
, which is a function that fills an array with information about the error, including the same information returned by ERROR()
, MESSAGE()
and SYS(2018). However, the array also contains information you can’t get from other functions, such as COM and ODBC error information. See AERROR()
for details on this very useful function.
Field validation errors got harder to process after VFP 3. In VFP 3, inserting data into a table that violated the field rule always generated error 1582, and populated the MESSAGE() function with "Field <field name> validation rule is violated" and the SYS(2018) value with the field name. That SYS(2018) value made it easy for us to know which field had the problem, perhaps to set focus back to that control. In VFP 5, SYS(2018) contains the field's RuleText if it's been filled in, or an all-uppercase version of MESSAGE(). There's no easy way to find the field name—you need to parse it out of the MESSAGE() text, which is localized differently for different languages, or scan the DBC for a field with a matching message. They had it right to begin with. Bogus. |
ON ERROR WAIT WINDOW ;
"Error " + LTRIM(STR(ERROR())) + CHR(13)+;
"Message() " + MESSAGE() + CHR(13) + ;
"Message(1) " + MESSAGE(1) + CHR(13) + ;
"SYS(2018) " + SYS(2018)
DO LUNCH
* File does not exist, and displays:
* ERROR() 1
* Message() File 'lunch.prg' does not exist.
* Message(1) "do lunch"
* SYS(2018) LUNCH.PRG
ON ERROR [ Command ]
ON ESCAPE [ Command ]
ON READERROR [ Command ]
cCommand = ON( "Error" )
cCommand = ON( "Escape" )
cCommand = ON( "ReadError" )
The ON ERROR
event handler still has a place within the workings of Visual FoxPro. ON ESCAPE
can have some applicability under some circumstances. ON READERROR
is old and should be allowed to expire quietly.
ON ERROR
is the last resort for errors that have stumped the local Error method, or occur in a place where those procedures do not have control. There are a huge number of errors, but most fall into a small number of categories: catastrophic, and worse. Our general feeling is that the error handler should record as much information about the error as possible (storing such values as LINENO()
, PROGRAM()
and lots of SYS() values to a text file or memo field) and then shut down the operation.
ON ESCAPE
can be better replaced in forms with KeyPress events, but it may still have a place in areas where the user input is limited and the event model weak, such as in the running of reports. If you create a UDF() within a report to pause and present a “Cancel/Resume” dialog to the user, you could trigger this with the ESCAPE key with an ON ESCAPE
trap, as in the example that follows. Trigger a variable to change values when the Escape key is pressed, and then test for that value change in a UDF within the report processing. To minimize the time consumed in firing and testing the UDF, you should probably place it in the group band or page bands of the report, so it doesn’t fire for every record.
The ON READERROR
command is one of the two or three in the language that your authors have never used. The intent of the command is to define a routine to be triggered when an invalid entry is made in an input control that’s part of a READ—an invalid date, a number outside an acceptable range, or an item that failed its VALID test. We were surprised to find it worked within the VFP event model at all. But, even in FoxPro 2.x, there are usually better ways to handle these cases. There is one case we know of, though, where ON READERROR
is worth its weight in gold. An “Invalid Date” error fires before any associated Valid code, and doesn’t let the user out of the entry until the date has been corrected. In the case of two-digit date entry, where the user may be entering a valid date for another century, an ON READERROR
routine can process the keystrokes entered and “fix” the date to a valid entry, solving the Year 2000 problem for some legacy applications. Credit for this idea goes to our friend Christof Lange.
* See the ON ERROR example above.
cOldEscape = ON("ESCAPE") && Preserve the old value.
lStopReport = .F.
ON ESCAPE lStopReport = .T.
REPORT FORM MyReport
* Within MyReport, call the UDF TestStop(), which follows:
* TestStop().PRG
* If Escape has been pressed, present an "OK/Cancel"
* dialog to the user. If they choose to cancel printing,
* force the report to end by jumping to the bottom of the
* table. Otherwise, reset the flag to show Escape had been
* pressed and resume.
IF lStopReport
IF MESSAGEBOX("Continue Printing?",17,"Report Paused");
= 2
GO BOTTOM
ELSE
lStopReport = .F.
ENDIF
ENDIF
RETURN
AError(), DDELastError(), Error Event, FError(), KeyPress, MessageBox(), On Key, Retry, Return, Set Reprocess