Mr. Panattoni received a Bachelor of Science degree in Computer Information Systems from DeVry Institute of Technology in Los Angeles, CA. He has worked the last four years as a Programmer/Analyst for DataStar Corporation, a company that specializes in business and fruit accounting software. You may contact him at DataStar Corporation, 6 South 55th Avenue, Yakima, WA 98908, (509) 453-2455, or at his home number, (509) 453-2455.
Introduction
In school, they tell you that when writing the user interface section of a program, you should write it as if a monkey will be sitting at the keyboard. Based on my experience, I sometimes think that it would be easier to write code for a monkey than a real user. A monkey may bang at the keyboard, but a user often times will try to outwit the program and unknowingly enter incorrect information.Several years ago I was shown a data input function, called prompt, that not only provided a good method for entering data, but also did basic validation of the data being entered. The function was printed in a book titled Variations in C by Steve Schustack (MicroSoft Press, 1985). After several modifications and enhancements, this function has become a cornerstone for all of my data input routines, both in MS-DOS and UNIX.
In order to demonstrate prompt without requiring you to have a commercial windowing library, the version listed here has been stripped of all of its windowing library calls and replaced with Standard C I/O routines. To read a character from the keyboard, this version uses getchar. To display to the screen it uses printf. And to position the cursor, it uses a macro called MOVE_CUR. Since the MOVE_CUR macro uses ANSI codes, you need to include the ansi.sys driver in the file config.sys. Because of these changes, this function as it stands is written for MS-DOS, but it can easily be made to work with any MS-DOS or UNIX windowing library by changing just a few function calls.
prompt accepts input from the user and returns to the calling program an integer value that represents a terminating key. The terminating key is the key that is pressed by the user signaling either that some action may need to be performed by the calling program or that the user is done entering data in the field. The calling program will determine what needs to be done based on that terminating key. For example, If the terminating key is an F1, you may want to display a help screen or a pick list pertaining to the current field. If the terminating key is what I call a "moving" terminating key such as Up Arrow, Down Arrow, Home, End, Tab, or Enter the calling program will probably want to move to the next or previous field.
The keys.h header file included in prompt (see Listing 1) defines the values returned from getchar when the special keys, such as function and cursor keys, are pressed. This header file needs to be included not only with the file that defines prompt, but also with any program that calls it, if that calling program needs to know what terminating key caused prompt to return. When you implement a windowing library with prompt, this header file will probably need to be replaced by the windowing package's header file that defines its own return codes for the keyboard.
Besides the definitions of the special keys, I have included definitions for four other terminating keys which can be returned by prompt. Because there are times when a data field requires specific validation, I wanted to know whether the user changed the data. If no changes occur, there is no need to do any validating. Therefore I have defined a NO_CHANGE value for Up Arrow, Down Arrow, Tab, and Enter (and only these four keys). They are the most common keys that a user would press when done entering information for a field. If you find that you use other terminating keys to end data input, you may need to add NO_CHANGE values for those keys as well.
Calling the Prompting Function
The function prompt expects nine parameters:data is a character pointer that points to a buffer holding the default value of the data field to be entered by the user. It may be initialized to NULL, or it may have a preset value. When prompt returns to the calling program, the buffer that data points to will be changed if the user has entered any data.
match_char is a character code that represents what type of input will be accepted as being valid from the keyboard. These codes are setup and maintained in the match function.
min_len is an integer value depicting a minimum number of characters to be entered by the user for this data field. The prompt function will not return a "moving" terminating key unless the user has met this requirement.
max_len is an integer value depicting the maximum number of characters that can be entered for this data field.
row and col are two integers which represent the starting row and column where the user will begin entering data.
fyi is a character string that can be used either as the field's title, or as a "For Your Information" (FYI) line to give the user instructions about what is to be entered. If you decide not to use an FYI, you can pass a null string for this parameter.
fyi_row and fyi_col are two integers which represent the starting row and column for the FYI message display.
All of the row and column values refer to an entire screen of 12 rows and 80 columns. If you implement a windowing library with prompt, you will probably want to add another parameter for a Window pointer. Then all of the row and column numbers will be relative to the window being used.
When calling prompt, you can be as simple as you want, or you can analyze the return code in detail. In the sample program (See Listing 2) , I check to see what terminating key is returned by prompt to determine what field the user wants to go to next. If you have more than a couple of fields to call using prompt, I suggest making an array of "field" structures that will hold all of the values for prompt for each field. This has two immediate advantages. First, the parameters passed to prompt are more readable at a glance. And second, if the array of structures is used every time a field's data is displayed to the screen, when your client asks you to move screen fields around, you can move a field anywhere they want in one simple change.
Using the Prompting Function
prompt is a versatile function. It not only allows the user to enter in new data but gives the user the freedom to edit existing data without having to retype the entire field. When the program's cursor first arrives at a field, the user can overwrite its data by simply typing new information. prompt will wipe out the old data and replace it with the newly-entered information as long as the first key entered by the user is not Right Arrow. If Right Arrow is pressed as the first key, prompt will enter into an edit mode. Edit mode will allow the user to move through the field's data using Left Arrow, Right Arrow, Home, and End. The user also has the option of toggling between an Insert mode and a Typeover mode by pressing the Insert key. When in Typeover mode, the cursor appears as a block. When in Insert mode, the cursor appears as an underscore. Because this version of prompt has been stripped of a commercial windowing library in order to make it work with the Standard C library, I have added the change_cur function to change the cursor's appearance. If you implement a windowing library prompt, you will probably want to replace change_cur with a similar function from your windowing library.
Implementation
prompt is really quite basic when you look at it closely. (See Listing 3. ) When first called, it displays the data field and the For Your Information line. After that, the function goes into a loop that gets characters from the user until a terminating key is pressed. For each character entered by the user, prompt will verify that it is valid based on the match code that was passed in as a parameter. If the character entered is found to be valid, it is displayed to the screen and the user is prompted to enter another character. All editing keys such as Backspace, Left Arrow, Right Arrow, Home, and End can be entered by the user.When a terminating key is entered, prompt will do one of two things depending on what kind of terminating key is pressed. If the terminating key is not a "moving" key, prompt simply returns to the calling program the terminating key that was pressed. If the terminating key is a "moving" key, prompt will make sure that the length of the field's data meets or exceeds the field's minimum length requirement. If the minimum length has been met, prompt will return to the calling program. But before returning, prompt checks to see if the user has entered anything for this field. If not, then a NO_CHANGE value for the "moving" key is returned. Otherwise, the "moving" terminating key is returned to the calling program.
Real-Time Validation
match is a function that you, the programmer, set up to define a series of one-character codes that will represent sets of valid data. (See bottom of Listing 3. ) You can have as many sets as you like, and add them as often as is needed. This function is what I think makes prompt so great. Once the user has entered in the field's data, you can assume that the field is valid. prompt will not allow any characters to be entered that are not defined as being correct for a particular match code. There may be times however when you will still have to do some validating when prompt returns to the calling program. For example, while prompt can make sure that the user enters all of the correct characters that make up a date, it does not validate the date. But based on my experience with this function, prompt eliminates 50 to 60 percent of the user entry errors by keeping them from typing invalid keys from the beginning.