Home
Program List

Stephen Morgan's FoxPro Projects - Utility Program Details

Introduction

This area offers details of some major general-purpose programs used in some form by almost all the full application programs I wrote. Unless noted, all these programs were written in FoxPro 2.0, for DOS. Some early programs started out in dBASE III PLUS or FoxBase, but all were converted to FoxPro at some point.

Printer Menu

One of the first versions of the printer menu program was as a short list of available printers for the Technical Library program. The list included a high speed LAN printer. At that time, the Novell CAPTURE command was used to access the printer, but this caused memory problems if the command was used more than once or twice in a program session. Eventually, FoxPro added a command to access printers on a Novell network, and that command was substituted.

The printer menu eventually had four or more options, with the LAN printer being one of them. The printer codes were usually placed just before the report began and just after it ended, though some hand-coded reports had internally placed codes because of such things as expanded-print bold titles and different character spacing at different points in the reports. The printer codes were soon placed in variables instead of being stated directly, though extra steps were necessary in processing the LAN printer because of the requirement of connecting and disconnecting. Not all programs were given a printer menu, though the Engineering programs usually were. It became a problem to keep updating the code for all the programs, so I eventually wrote a separate program.

In the separate printer program, the printer names and printer codes were kept in tables instead of being hard coded. Three tables existed: one with the users' names and printer choices; and with the printer names and codes; and one with the actual menu options. The one with the menu options also tracked whether the printer was LAN or Local, and the server name was also recorded. Key numbers existed for the menu options and for the printer definitions.

Though the program worked well, it had a major deficiency in that it did not have the ability to edit anything. All editing had to be done directly from FoxPro. I had not wanted to get too deeply into writing a printer program because I was expecting FoxPro to offer a printer selection utility. However, the utility FoxPro offered was extremely slow in operation and required setting up predefined report definitions. This meant that I had to either set up a report definition for all necessary printers and continue offering the user my own menu, or rely on the user to set up the definitions (and to know what the definitions should be). For example, a user would have to know that a report required 17 characters per inch. Also, although a table of printers and codes was provided, altering anything required recompiling the utility. The utility could also be used only with FoxPro report forms, and not with hand-coded reports, which were sometimes absolutely required for Engineering printouts.

It became evident that FoxPro would not provide what I was looking for. Also, some programs on which I wished to implement the printer menu were going to be released to other locations, and it would not be right (or workable) to require the users at the other locations to set up the printer records directly from FoxPro. I then created the second major version of the separate printer menu program.

In the new version of the program, the menu options and printer definitions were set within the program. The users were not manually set within the program, as the program automatically detected new users and added user records as necessary. User names were placed in a variable in the DOS environment at the time of network login, and most of the programs accessed that variable for a user name, though Status Monitor had a partially separate system. All that was needed was a common variable name that the printer program could access, or at least a known range of possibilities. The programs did not in fact all have the same user variable name, so the printer program checked for all reasonable variations on the variable name before looking for a name in the DOS environment. (Some initial user variable name possibilities were User, MUser, UserName, and MUserName. This list was later expanded after variable type and scope prefixes were added, producing variations such as cUserName and gcUserName. Although I did not personally implement scope prefixes, I thought it best to check for them in order to handle possible future situations.)

What the printer program did was set public variables with the users' printer choice, and save key numbers recording the users' choice in a record. Normally, the printer program did not save a LAN printer choice, but could be configured to allow this.

When a report was to be printed, modules within the application program, not the printer program, accessed the variables, connected to the named file server if the choice was a LAN printer (or connected to the default server if a server was not explicitly named), and sent the appropriate printer codes (held in code variables such as cCPI10, cCPI17, cLPI6, and cLPI8) for that report. After printing, the printer codes to restore the normal printer state were sent, and if the printer was a LAN printer, a disconnect was sent by setting the printer to local.

This allowed both report forms and hand-coded reports with internal printer codes to be used.

Error Handler

Originally, none of the programs had an error handler.

I had never used an error handler, and none of the preexisting programs had one. I tried to write the programs to eliminate as many errors as I could. This was not enough to totally prevent errors, and so I eventually began to explore setting up an error handling system.

The error handler was originally centered on handling file-does-not-exist problems (if the requested file is on an unavailable file server, this can easily cause the error) and file-is-in-use-by-another errors, and had minimal processing for other errors. Except for printer-not-ready errors, most errors resulted in shutting down the program, since the consequences of allowing the program to continue with an unresolved error is great. Exceptions were allowed if the section of the program from whence the error originated indicated that it would be able to handle the problem.

For instance, if a table was unavailable and the user did not want to retry, and the section of the program where the error occurred had previously set variables indicating that the command was being checked, the error handler would return control to that portion of the program after setting a variable to an error value and the program would see that an error had occurred and cancel the operation.

What would happen if the program had simply continued, thinking the table had been opened? The user would eventually be presented with some other error. Perhaps a field would be referenced and a variable-not-found error would occur, or a string of such errors if the error handler kept returning control to the program. Eventually, a command would probably be encountered which would cause FoxPro to believe that a table should be open. At that point, FoxPro would present the user with a list of available tables to open. Even if the user knew which table to select, any associated indexes would not be opened and so the indexes would be corrupted if the table was updated. No guarantee exists that the table would be positioned on the correct record or that the correct table would be chosen. If the wrong table was chosen, the program might proceed without further activating the error handler, as some commands are sufficiently general as to work with any table, but the wrong table would be used. What if the command was to update a record, delete a record, or to delete ALL records?

Even a variable-not-found error can be dangerous. If the variable not found was being used to initialize another variable, then the variable is not initialized and may itself not be found when the variable is later referenced. {If the variable has been declared in some fashion, the variable will exist, but may be of the wrong data type.) If either variable is used in another command, then that command will fail, whatever that command may be. For an extreme example, what if table work area numbers are kept in variables, and the program intends to switch work areas, delete all the records in a temporary table, and then return to the original work area, which holds a permanent table with 30,000 records? If the work area variable does not exist, an error will occur and the work area selected will remain that holding the permanent table. If the program is allowed to continue, and the next command is to delete all records, the 30,000 records in the permanent table will be deleted instead of the records in the temporary table.

It would be best if some means were provided to allow the user to safely continue. Eventually, I devised a system where variables held the name of the module to return to when an error occurred. If the error was a printer error, a special variable was referenced which was set to the name of the report menu module. If a table was being opened and if the operation was being checked, the error handler returned control to the program at the point where the error occurred. Otherwise, and for almost all other errors, the error handler attempted a return to the designated main module. If a special variable was set with the name of the main module, the program tree was checked to see if the name was present. If the name was not found, an alternate possible name, such as MainMenu was checked. If nothing was found, a RETURN TO MASTER command was given. Note that such a command will result in leaving the current program if the program was called from another program. In some cases, and for some errors, the situation was deemed sufficiently serious to directly quit FoxPro. Tables were always closed in situations where control was not returned to the area of the program where the error occurred. If the error occurred while the user was in the designated top module, the user might be returned to the place of the error. If the error occurred during initialization of the program, further errors might occur even (or maybe especially) if the user is returned directly to the top module. Though such situations are rare, I had the error handler place a counter in a public variable the error handler itself created, and if multiple errors of the same general type were occurring too close together, the error handler would decide that the situation couldn't be saved and simply quit FoxPro.

Almost all errors resulted in the user being given notification. Sometimes the error handler message was suppressed to allow the module where the error occurred to give a message, and sometimes custum messages where passed to the error handler. More often, in cases where a FoxPro type error occurred, the error handler presented the FoxPro error message and number.

Non-FoxPro errors and events could also occur, as the error handling portion could be bypassed and a call made directly to a section of the error handler that saved the errors. Sometimes, specific areas of programs were set to log unusual or disturbing events or activities, in which case the error would be saved as a custom error message and number (usually, but not always, zero), along with the user name, the name of the module (or a special name), and the date. These records would go into the same table as the FoxPro error records. Additionally, the error handler recorded the frequency of the error by looking backward through the file for other errors with the same message, number, user name, module name, and date, and then incrementing the error count and saving the result.

In the end, I did not record large numbers of errors, except in unusual situations.

Startup Error Handler

One type of error that disturbed me was an error that seemed unreachable. Some users loaded FoxPro from their file server and then received a file-does-not-exist error when FoxPro attempted to load the application, because the application was on a server to which they had failed to attach. This happened relatively frequently, as some users failed to provide the proper password when the attempt to attach was made. In some cases the subsequent attempt to map a drive also failed. In some cases, the user did not know the right password, because the user had changed the password on the default server and not on the other servers. This was all occurring in DOS, sometimes even on Windows computers running DOS windows (the programs were in DOS). When such an error occurred, the error handler was not activated because the application could not even be found. FoxPro presented its' own error message through its' native error handler. After acknowleging the message, the user was left in the command window. The user was frequently trapped in FoxPro, not knowing how to exit.

Eventually, I decided to do something about it. FoxPro had a startup program that installed the Run menu. I didn't use the Run menu. I substituted my own program for the startup menu. The new program ran a version of my error handler in a subdirectory I created off the FoxPro directory, and specified a table in that subdirectory when errors were logged. I gave users Novell write rights to that directory, to allow them to write to the table. The new system worked very well, enabling me to identify users with password problems and inform the LAN Administrator, frequently before the users themselves did (when the users did complain, they frequently blamed the program or some unknown problem, rather than the password).

Error Handler for Visual FoxPro

In converting the error handler to Visual FoxPro, I made two versions: one as a program and one as a class. Although a class was needed to fully handle errors occurring in forms and classes, a separate error handler was also needed for those cases where the class error handler was not available. The updates from the earlier error handler involved mainly adding error checks for new error types, saving class information in the tree of procedure names leading to the module in which the error occurred, modifying the file structure of the error log table, and using the Windows msgbox instead of coding message windows.

Note that error handling within classes is more complicated than simply installing a global error handler, as ideally the class should have some native error/exception handling mechanisms built-in, with the error passed to the global error handler only if it falls outside of what the class can handle. Since I wanted errors recorded, I made the error log portion available separately from the error handler, so the class error handler could log the errors without calling the global error handler.



Home
Program List
Utility Programs