home *** CD-ROM | disk | FTP | other *** search
-
-
-
-
-
- A KEYBOARD INPUT UTILITY
-
-
- If you are like me, you often write programs that use "special"
- keys like the arrow keys, function keys, etc., but the way these
- keys are implemented seems frustrating; first you have to read a
- null character, then do another read, and you have keep track of
- what code means what. This detracts from the readability of the
- program and disrupts your flow of thought. Or maybe you'd like
- to read numbers from the keyboard but not have to worry about
- trapping errors in input to avoid a fatal "Invalid numeric
- format" error or check to see if the number typed is in the range
- you want it. Or maybe you'd like to have a better input routine
- than ReadLn to input a string. This unit tackles all those
- problems, and a few others to enable you to write programs that
- contain clean, good-looking, error-free input.
-
-
- SINGLE CHARACTER INPUT
-
-
- ReadKey is sometimes OK for character input, but when it comes to
- "special" keys that aren't normal ASCII characters, it's a real
- pain. That is where a function in this unit called GetKey comes
- in handy. GetKey's calling syntax is the same as ReadKey's, and
- it returns a character like ReadKey does. However, GetKey has
- some improvements over ReadKey:
-
- - The key pressed can be ENTIRELY determined from
- the character returned by the function. There is no
- need to call the function again for special keys.
- - Special keys can be determined by comparing them
- against either constants or simple expressions, rather
- than remembering meaningless codes.
- - If the user presses a special key when your
- program expects regular character input, the extra
- keystroke that ReadKey would normally hold after the
- null character is not there with GetKey, eliminating
- the danger of misinterpreting an extra keystroke, so
- you should use GetKey in place of ALL calls to ReadKey,
- unless you make special provisions to avoid
- misinterpreting the extra keystroke.
-
- If the character returned by GetKey is less than or equal to
- #127, it is not a special character and should be taken at face
- value. (Note: If GetKey returns a #0, it means the user entered
- a "null" character, not that there is another character waiting
- to be read. A null can be entered by pressing the Control key
- along with the "@" key.) However, if the character returned is
- #128 or greater, it means that the user pressed a special key
- and that the character should be interpreted as follows: For
- all special keys except Alt plus a letter, there are constants
- that you can match against the character returned to determine
- the key pressed. Here is a list of these constants, along with
- the keys that generate them:
-
-
-
-
-
-
- F1, F2, ... , F10 - one of the ten function keys.
- ShiftF1, ShiftF2, ... , ShiftF10 - one of the
- ten function keys together with the Shift key.
- CntlF1, CntlF2, ... , CntlF10 - one of the ten
- function keys together with the Control key.
- AltF1, AltF2, ... , AltF10 - one of the ten
- function keys together with the Alt key.
- Alt0, Alt1, ... , Alt9 - the Alt key together with
- one of the number keys from the top row of the
- keyboard.
- UpArrow, DownArrow, LeftArrow, RightArrow - one
- of the directional arrow keys.
- CntlLeftArrow, CntlRightArrow - the Control key
- together with the left or right arrow key.
- Normally, pressing the Control key together with the
- up or down arrow key does not generate a character.
- Home, End_, PgUp, PgDn, Ins, Del - the corresponding
- key from the numeric keypad. Note that the constant
- for the "End" key ends with an underscore to avoid
- conflict with the reserved word "end" in Turbo
- Pascal.
- CntlHome, CntlEnd, CntlPgUp, CntlPgDn - the
- Control key together with the Home, End, PgUp, or
- PgDn key. The Control key together with the Ins or
- Del may or may not produce a keystroke. If it does,
- it produces the same result as the Ins or Del key
- without the Control key.
- AltMinus, AltEqual - the Alt Key together with the
- "-" or "=" key from the center section of the
- keyboard. The key from the far right side of the
- keyboard will not produce a character when pressed
- together with the Alt key.
-
- NOTE: Support for the F11 and F12 keys is not included because
- reading these keys requires calling a special "Read extended
- keyboard" function, which can lock up or cause other problems
- with computers that do not have BIOS support for reading the
- extended keyboard.
-
- Here is a short program to demonstrate the GetKey function. It
- simply moves the cursor around on the screen as the user presses
- the arrow keys. The program will continue until you press the
- F10 key:
-
- Program MoveCursor;
-
- uses Keyboard;
-
- const
- XMax=80; {Change these if your screen}
- YMax=25; {size is different from 80x25}
-
- var
- X,Y:Integer;
-
-
-
-
-
- Ch:Char;
-
- begin
- X:=WhereX; Y:=WhereY; {Read current location}
- repeat
- Ch:=GetKey;
- case Ch of
- LeftArrow : if X>1 then dec(X); {Left arrow pressed}
- RightArrow : if X<Xmax then inc(X); {Right arrow pressed}
- UpArrow : if Y>1 then dec(Y); {Up arrow pressed}
- DownArrow : if Y<Ymax then inc(Y); {Down arrow pressed}
- end;
- GotoXY(X,Y); {Move to the new location}
- until Ch=F10; {Check for the F10 key}
- end.
-
- The comments are not necessary, since this program is self-
- documenting because of the named constants, where a program that
- worked with ReadKey and raw character codes would need extensive
- commenting to explain itself. Note that the routines WhereX,
- WhereY, and GotoXY normally require a "Uses CRT" statement
- because they reside in the CRT unit. However, the CRT unit can
- cause problems in modes where the screen width is greater than 80
- columns. Since this unit used these routines, they have been
- implemented with BIOS calls in order to keep the unit compatible
- with all video modes. Since they were already written, they have
- simply been put in the interface part of the unit so other
- programs can use them too. If you still want to use the routines
- in the CRT unit, you can either delete the {$DEFINE NOCRT} line
- from the interface part of the unit (if you have the source
- code), or you can use the fully qualified names (e.g.,
- crt.wherex, crt.gotoxy, etc.) in your program.
-
- A NOTE ON CRT COMPATIBILITY (the KeyPressed function): The only
- routine in the Keyboard unit that is known to have a somewhat
- different effect than the corresponding routine in the CRT unit
- is the KeyPressed function. This function, in addition to
- checking for keys pressed, also has the side effect that if it is
- called and a key is waiting and that key turns out to be a keypad
- '5' while in non-numeric mode, it will be silently removed from
- the buffer. This is a necessary compromise, because these keys
- continue to be put in the buffer regardless of the value of the
- nonnumeric variable. This would cause erroneous results, because
- getkey will ignore keypad '5' keys when in non-numeric mode, to
- be consistent with the way the keypad normally works, so
- keypressed would be returning TRUE when actually there was no key
- available. It could not simply ignore the keystrokes, because
- they would still be there next time it was called, and it would
- never be able to return TRUE. Eventually, the buffer could fill
- with 5's if they were not removed. The only situation this
- causes is that if you call this routine with nonnumeric equal to
- TRUE and there is a 5 in the buffer, then you set nonnumeric to
- FALSE and call it again, the 5 will not be there any more, even
- though now it should be able to be read. This seemed to be the
- best solution, although it is still somewhat imperfect.
-
-
-
-
-
-
- Another possibility is that the Alt key has been pressed along
- with one of the letters A-Z. Because the unit already has so
- many constants defined in it, it would be awkward to define
- constants for each key. Instead, GetKey returns a character
- equal to the ASCII value of the uppercase version of the key
- pressed plus a constant amount. The constant "Alt" has been
- defined for use in testing this condition. The character
- returned will be equal to Chr(Ord(ASCII_value)+Alt). To
- illustrate this, the following short program will continually
- read characters from the keyboard, and if the Alt key is pressed
- along with a letter, it will print the key that was pressed.
- Again, the program ends when you press the F10 key:
-
- program AltDemo;
-
- uses Keyboard;
-
- const
- A=ord('A');
- Z=ord('Z');
-
- var Ch:Char;
-
- begin
- repeat
- Ch:=GetKey;
- if (Ch>=chr(Alt+A)) and (Ch<=chr(Alt+Z)) then
- writeln('You pressed Alt-',chr(ord(Ch)-Alt));
- until Ch=F10;
- end.
-
- Since it is possible to enter characters by holding down the Alt
- key and typing in the ASCII value of the character on the numeric
- keypad, it is possible for a character greater than #127 to be
- entered from the keyboard. Since GetKey returns special
- characters as characters above #127, if your program does not
- detect this, it may misinterpret the character as a special
- character. To correct for this, GetKey sets a predefined
- variable called AltTyped, which will show whether the character
- was entered this way. If this variable is TRUE, the character
- was entered by typing it in on the numeric keypad. If it is
- FALSE, the character was entered by normal keyboard input. If
- you want to handle these situations differently, or just think
- your user might do something strange, you probably will want to
- test this variable before you process any special keys. Also,
- you are no doubt aware that the keys on the numeric keypad can be
- interpreted as either cursor control keys or as numbers,
- depending on the Shift and NumLock keys. You may want to treat
- these as cursor control keys all the time. For example, a
- drawing program in which the user draws by moving a cursor around
- on the screen probably would find it useless to treat these as
- number keys. You can specify whether to treat these keys
- specially by setting a predefined variable called "NonNumeric"
- before calling GetKey. When this variable is TRUE, GetKey treats
-
-
-
-
-
- the keys as cursor control keys and returns appropriate values
- regardless of the condition of the Shift and NumLock keys. When
- it is FALSE, GetKey treats these keys normally, returning numbers
- when the shift key is down or when NumLock is on. This variable
- is initially FALSE by default.
-
-
- NUMERIC/STRING INPUT
-
-
- This unit also contains powerful input routines that are a vast
- improvement over Read and ReadLn. Three of these input routines
- deal with numeric input, while two deal with string input. Here
- are the headings for these five routines:
-
- procedure ReadNo(var Number:Word; LoBound,
- HiBound:Word);
- procedure ReadInt(var Number:Integer; LoBound,
- Hibound:Integer);
- procedure ReadReal(var Number:Real; LoBound,
- HiBound:Real; Decimals:Byte);
- procedure ReadStr(var S:String; MaxLen:Byte;
- CharsToExclude:CharSet);
- procedure EditStr(var S:String; MaxLen:Byte;
- CharsToExclude:CharSet);
-
- CharSet is a type defined as set of Char. Unlike ReadLn, these
- procedures do not skip to the next line when the user hits return
- at the end. This is because sometimes you may want the cursor to
- stay on the same line after the input. You can always add a
- blank WriteLn statement if you want to skip to the next line.
- ReadNo, ReadInt, and ReadReal input numbers. ReadNo inputs
- positive (unsigned) integers, ReadInt inputs positive or negative
- (signed) integers, and ReadReal inputs real numbers. LoBound
- and HiBound are the minimum and maximum values for the number to
- be input. For ReadReal, the parameter Decimals is the minimum
- number of decimal places you want the user to be able to enter.
- The maximum width the user will be allowed to type in will be
- determined by the values passed for LoBound and HiBound (and
- Decimals, if appropriate). For example, if you call ReadNo with
- LoBound=0 and HiBound=500, the user will be given a maximum width
- of three to type in, since no more than three digits will be
- possible. If you call ReadReal with LoBound=0, HiBound=500, and
- Decimals=3, the user will be given a maximum width of 7 to type
- in (3 for the whole number part, 1 for the decimal point, and 3
- for the decimal part). In each case, the input will be
- restricted to allow only characters appropriate for the type of
- number you are reading. ReadNo allows only digits to be entered.
- ReadInt allows only digits and a possible negative sign as the
- first character. ReadReal allows only digits, a decimal point,
- and a possible negative sign as the first character. It will not
- allow more than one decimal point to be entered. All three
- procedures will exit immediately without doing anything if the
- HiBound is less than the LoBound. If the user does not enter
- anything and just hits return, the numeric variable is returned
-
-
-
-
-
- unchanged, so if you want there to be some default value for
- these procedures to return, you should set the variable to that
- value before calling the procedure. If the number entered is out
- of the range specified, the procedure will beep and wait for
- another number to be input. (One exception: if the default value
- passed is out of the range specified and the user hits return to
- accept the default value, it will allow it. This will be shown
- to be a useful feature later.) You should make the user aware of
- both the range expected and the default value. If, for some
- reason, you don't want there to be any default value, you can
- take advantage of the fact that the cursor stays on the same line
- after input by first setting your numeric variable to an out-of-
- range value and continually reading until the variable is in
- range. Suppose you wanted to enter a number between 1 and 500
- but there was no good default value you could use. The following
- two lines will make the user enter a value that is in range:
-
- Number:=1000; {Start with an out-of-range value}
- repeat ReadNo(Number,1,500) until Number<>1000;
-
- .cp 2
- If the user enters nothing and just hits return, the cursor will
- stay exactly where it is so you can immediately make another call
- to ReadNo. The program would detect that the user did not enter
- anything because the default value (1000) would be returned, and
- it would make the user enter something before continuing.
-
- ReadStr and EditStr input strings. In each case, MaxLen is the
- maximum length you want to allow the string to be, and
- CharsToExclude is a set parameter that should contain any
- characters that you do not want to allow the user to enter. For
- example, when prompting for a filename, you may want to exclude
- control characters (below #32), blank (' '--#32), and characters
- above a lowercase z. So, if you wanted to limit the name to 30
- characters and exclude the characters mentioned, you would call
- ReadStr or EditStr as follows:
-
- .cp 3
- ReadStr(StringVar,30,[#0..#32,#123..#255]);
- - or -
- EditStr(StringVar,30,[#0..#32,#123..#255]);
-
- If you have a large range of characters you want to disallow but
- a small range you want to allow, you can take advantage of the
- set difference operator and pass the invalid characters as the
- set of all characters minus the set of valid characters. For
- example, if you wanted to use ReadStr to read a single character
- command followed by a carriage return (i.e., a string of length
- 1), and your range of commands was a digit from '1' to '9' or 'Q'
- to quit, rather than figure all the characters that were invalid,
- you could make the following call:
-
- ReadStr(Command,1,[#0..#255]-['1'..'9','Q','q']);
-
- This says to disallow all characters except the digits 1-9 or a
-
-
-
-
-
- capital or lowercase 'Q'.
-
- ReadStr allows the user to start with a blank string and enter
- data. It is somewhat similar to ReadLn, except for the extra
- parameters for formatting. If the user enters a null (blank)
- string, the string variable passed is returned unchanged. So,
- before calling ReadStr, you should set your string variable to
- whatever default value you want it to have if the user enters a
- null string. If you don't want to set any default string in such
- a case but instead want a null string returned, you should set
- your string variable to a null string ('') before calling
- ReadStr. EditStr allows the user to edit an already existing
- string. You should set the string variable to whatever initial
- value you want it to have before you call EditStr. When EditStr
- is called, the initial value will be displayed, and the user will
- get a chance to edit it. The string will take on exactly
- whatever value the user gives it--if the user changes it to a
- null string, it will be returned as a null string, regardless of
- what value it had when EditStr was called. Both procedures allow
- the following editing features:
-
- ESC - Erase the entire string.
- Backspace - Delete the character to the left of the
- cursor and move the cursor left one space.
- Left Arrow - Move the cursor left one space.
- Right Arrow - Move the cursor right one space.
- Up Arrow - Move the cursor up one line.
- Down Arrow - Move the cursor down one line.
- Home - Move the cursor to the beginning of the string.
- End - Move the cursor to the end of the string.
- Ins - Toggle insert/typeover mode.
- Del - Delete the character under the cursor.
- Cntl-Home - Delete from the cursor to the beginning of
- the string.
- Cntl-End - Delete from the cursor to the end of the
- string.
-
- These keys will only function if the cursor is in an appropriate
- position; you cannot backspace if you are at the beginning of the
- string, you cannot go forward if you are at the end, you cannot
- move up or down if the string does not extend to the line below
- or above the cursor, etc. The variable parameters for these
- procedures call for strings of 255 characters. If you want to
- call one of them with a string variable of a different length,
- you will have to include a relaxed var-string directive ({$V-})
- before the call. This directive tells Turbo Pascal not to worry
- if the declared lengths of the formal and actual parameters
- differ when a string is used as a variable parameter, but it also
- means that if you try to put more characters into the string than
- will fit, you run the risk of overrunning the end of the string
- and overwriting data outside the string. To avoid this, never
- call ReadStr or EditStr with a MaxLen greater than the declared
- length of the string. If you follow this rule, you will never
- have anything to worry about.
-
-
-
-
-
-
-
- OTHER PROCEDURES IN THIS UNIT
-
-
- There are three procedures in this unit that can be used to
- affect the state of the CapsLock, NumLock, and ScrollLock flags.
- They are SetCapsLock, SetNumLock, and SetScrollLock, and each
- takes a single parameter. The line SetCapsLock(On); will turn on
- CapsLock, while the line SetCapsLock(Off); will turn it off.
- "On" and "Off" are simple Boolean constants defined in the unit
- to be equal to TRUE and FALSE, respectively. To test the current
- states of the flags, there are three Boolean functions, called
- GetCapsLock, GetNumLock, and GetScrollLock. There is also a
- function to test the status of the Insert flag called GetInsert.
- You can test the status of the NumLock key with the following
- line:
-
- if GetNumLock=On then writeln('NumLock is on. ');
-
- or, more compactly:
-
- if GetNumLock then writeln('NumLock is on. ');
-
- Some programs simply set the status flags and do not return them
- to their previous states before the program ends. This can be
- annoying. It is always best to test the state of the flags
- before setting any of them, then return them to their previous
- states as soon as possible when you finish. Except on AT-type
- computers, setting these keys affects only the PC's internal
- record of the state of the keys but does not affect the status
- lights on the keyboard that show whether the flags are on or off.
- Therefore, if you do not return the flags to their previous state
- when you finish, the lights on the keyboard may become out of
- synch with the actual states of the flags within the computer.
- If you can avoid it, it is best not to change these settings
- because the user can change the states of the flags between the
- time you save them and the time you restore them, which would
- still leave the keyboard lights out of synch with the PC.
- However, this is somewhat less likely so if you do find it
- necessary to change the flags, this is the best way to do it.
-
- There are five Boolean functions to test the current states of
- the Shift, Control, and Alt keys: LeftShiftDown, RightShiftDown,
- ShiftDown, ControlDown, and AltDown. The names are self-
- explanatory, except it should be noted that LeftShiftDown and
- RightShiftDown each return the state of one of the shift keys,
- where ShiftDown returns true if either shift key is down.
-
- There is a procedure called FlushBuffer, which clears any typed-
- ahead characters from the input buffer. Calling this procedure
- immediately before doing input can eliminate mistakes caused by
- the user either making a mistake trying to type ahead or
- accidentally hitting two keys at the same time, causing the
- second key to be read later. DOS does this when you type "erase
- *.*" and you have to wait before typing "Y" or "N".
-
-
-
-
-
-
- .cp 2
- There is a byte-valued function called ScreenWidth, which will
- return the number of columns on the screen. This function was
- used by the input routines, so it was simply placed in the
- interface of the unit so other programs could use it also.
-
- Finally, there are two procedures dealing with the cursor called
- ChgCursor, which takes two byte-sized parameters to specify the
- starting and ending row (0 to 7) for the cursor; and GetCursor,
- which returns the current starting and ending row for the cursor.
- The typical thin cursor would be set by the line
-
- ChgCursor(6,7);
-
- which means to use the bottom two lines (6 and 7) in defining the
- cursor. A block cursor can be set by the line
-
- ChgCursor(0,7);
-
- which means to use all the lines (0 through 7) in defining the
- cursor. The cursor can be made invisible by the line
-
- ChgCursor($20,0);
- - or -
- ChgCursor(32,0);
-
- This procedure can be handy to vanish the cursor when waiting for
- single keypresses or to make "block" cursors in word processor-
- type programs to show that "insert mode" is on. By calling
- GetCursor before calling ChgCursor, you can return the cursor to
- the state it was in before you changed it.
-
-
- IN CONCLUSION
-
-
- The file KEYTEST.PAS demonstrates the use of the procedures
- contained in this unit. First, it contains a loop to print the
- states of the various toggle keys (Shift, Alt, Control, Caps
- Lock, Num Lock, and Scroll Lock), which continues until a key is
- pressed. Then, it requests a number of each type and displays
- the value returned. Next, it asks for a string to be input,
- displays the string, and allows you to edit the string. Finally,
- it turns on Num Lock and requests you to press a keypad key, to
- demonstrate the use of the NonNumeric variable. The interface
- section of this unit is contained in the file KEYBOARD.INT so
- that you can see the declarations of the constants, procedures,
- and functions in the unit at a glance.
-
- If you find this unit helpful in developing your own programs, a
- donation of any size would be appreciated; however, it is not
- required unless you will be using these procedures in a program
- that you will be releasing to the public; in this case, a minimum
- donation of $10 is requested.
-
-
-
-
-
-
- Please send any donations to:
-
- Tom Swingle
- Rt. 1 Box 292
- Waterford, OH 45786
-
- My college address, to reach me quicker (until June, 1992) is:
-
- Tom Swingle
- 114 Grosvenor St.
- Athens, OH 45701
-
- Whether or not you send a donation, any feedback you have on this
- unit is certainly welcome, and I will try to correct any bugs
- reported, although I think I have tested it thoroughly enough
- that almost all the bugs are gone (I have tested it quite
- extensively and used it in several programs of mine). More than
- anything, I would like to hear how this unit works with unusual
- setups, like computers with keystroke buffer extenders or other
- such things. I will gladly answer any questions as soon as I
- can. If you have access to electronic mail, you can reach me
- faster at
-
- tswingle@oucsace.cs.ohiou.edu or swingle@duce.cs.ohiou.edu.
-
- This unit is still undergoing rigorous testing, and bug reports
- (if any, hopefully there are none) will be coming in as time goes
- by, so contact me to see if there have been any updates, if this
- unit interests you!