+-----------------------------------------------+ | File: keysdet.txt | | Author: Mod | | Email: fer2roc@excite.com | | Date: 10-30-99 | | Description: Tutorial on Keys Detection | | for QBasic and QuickBasic | | Level: Beginners and medium-level programmers | +-----------------------------------------------+ This document may be freely distributed provided that I am given credit,and that is unchanged (including this message). KEYS DETECTION (FROM THE BEGINNING) =================================== +-------+ | Intro | +-------+ One of the more important points in programming is how to identify the different keys, through which the user will interact with the program. Although many controlling actions (such as to open menus, to make scrolling, to input data, to quit, etc) may be accomplished with the mouse, the program must usually allow to make the same actions through the keyboard; also the data introduction (specially texts) is exclusively keyboard depending. In this tutorial we'll see how to identify the different keys in order to assign to them the functions we want, that's to say, how to represent the keys in our code. QBasic allows to use different ways to define the keyboard, each one has advantages and disadvantages. We'll review first 3 different methods and then we'll see how to choose one method depending on the characteristics of our program. The methods are not excluding and may be combined in the same program. Before analyzing each method, we must review some generalities about the keyboard and about the characters thas it represents. +--------------+ | The keyboard | +--------------+ Today it's almost exclusively used the so called "extended keyboard", which has at least 103 keys. We'll see that some functions in QBasic are designed for older keyboards. Some keys are "controlling" keys, like Esc, Intro, Spc, arrow keys, etc. Other keys represent printable characters (most 2 but also 3). The keys pressing may be combined, we keep one key depressed and then we press another one. The Shifts, Ctrl and Alt keys make that and change the function of the second key. Other keys like CapsLock and NumLock act as "on/off" switches changing permanently the function of the other keys, until they are pressed again. It's important to say that keyboards vary in different countries. There are some different letters, punctuations marks and even the location of the more common letters may vary. This fact must be always considered by the programmer in order to code in a universal way, so that their programs may function well in any keyboard. There are keys that keep their position constant in different keyboards, other vary from country to country. +----------------+ | The characters | +----------------+ The ASCII code contains the 256 symbols that constitute the characters set. This number depends on the possibility of representing different data in a byte (in binary numbers, the 8 bits of a byte may go from 00000000 to 11111111, which is equivalent to decimal 255). Not all the ASCII characters are printable. Some represent actions, like beeping, carriage return, intro, etc. and each one has its own symbol, although it's neither is used in writing nor is drawn in the keyboard keys. The printable characters are the capital and lower-case letters, the numbers, the punctuation marks and a lot of symbols such as accented letters, foreign and old letters, and graphical figures that may be used for layouting. Each character may be identified through its ASCII number, from 0 to 255. QBasic manages the ASC function, which returns the ASCII number for each specified character. The opposite function is CHR$, which returns the character for each ASCII number: PRINT ASC("a") The result will be 97 PRINT CHR$(97) The result will be a The expression CHR$(number) is equivalent to the ASCII character that it represents. Is the same to say: IF a$ = "a" THEN.. than to say: IF a$ = CHR$(97) THEN.. Some function keys have a so called "extended code", which consist in 2 ASCII characters and therefore it's stored in 2 bytes. The first one is always the null character CHR$(0), the second one is another character from the set. The extended code is represented as the addition of both characters. For example, the H letter has the ASCII code #72. But the Up arrow key has the extended code CHR$(0) + "H" or CHR$(0) + CHR$(72). Some keys (like Ctrl, Alt, Shift) haven't an ASCII number if they are pressed alone. They haven't an own function, they just modify the function of the others. As keyboards, the character set is not exactly the same in different countries, but letters, numbers and function symbols keep constant. Why the things must be complicated? Why not to use a single and complete code, which include the symbols used in all countries? It exists: UNICODE. But it needs to store each symbol in 2 bytes. That enhances the number from 256 to 65536, but it isn't practical. Usually we don't need more then 256 symbols and we would be only duplicating the memory usage in order to have symbols which we would never use. As each language has its own necessities, it's more practical to introduce little variations in the character sets. +---------------------+ | International codes | +---------------------+ There are 24 different possibilities of keyboard and characters set varieties. he "keyb" command in DOS allows to know both the keyboard type and the haracter set that your system uses. In USA the "us" keyboard code and #437 character set are used. The complete keyboard codes and character sets list may be consulted in the DOS manuals. +----------------+ | Keys detection | +----------------+ Now we begin. The first thing that you have to know is, that there are 2 ways of proceeding: (1) We may recognize the pressed "key", that's to say a key that has a determined location in the keyboard, independently of the function that t represent or the character that it contains. In order to do that, xist the so called "Keyboard Scan Codes". (2) We may recognize the entered "character", independently of the location of the key through which it entered. In this case, we use the ASCII code. It's important not to confuse them, because they aren't equivalent. For example the Esc key hat the scan code #1 and the ASCII code #27. If we put them in the wrong place the program won't function. In this tutorial we'll study 3 key detection methods: (1) The character detection through the INKEY$ function (2) The key detection through the ON KEY statement (3) The key detection through the INP function +---------------------+ | The INKEY$ function | +---------------------+ The INKEY$ function reads the keyboard buffer and it returns a string which consists just in the entered character. It may be compared with INPUT, but there are important differences. INPUT stops the program and waits that the user enter data. It only "knows" about the data when the user press the Intro key and enters a string that may consist in several characters. INKEY$ doesn't wait. It detects a single or extended character as soon as it was entered, without waiting for Intro. Also INPUT "makes echo", it prints the character on the screen and INKEY$ doesn't it. INKEY$ is excellent in a menu: you press the selected key and the action goes at once. a$ = INKEY$ If we run this code, we'll see that the program just end. INKEY$ didn't wait for an input, it just returned a null character and the program continued. If we want that INKEY$ wait for a key pressing, we must code a loop like this: DO a$ = INKEY$ LOOP UNTIL a$ <> "" Here we indicate that the program must keep the loop until the string cease to be a null string. This will occur when some key (with ASCII character) has been pressed. As we know the returned ASCII number we'll be able to know which key has been pressed, using IF or SELECT CASE structures and then we may assign to them the actions we want. Examples: CLS PRINT "In this program you may detect: PRINT "B - b - Intro - Up arrow - Down arrow" PRINT "Esc for ending" DO DO: a$ = INKEY$ LOOP UNTIL a$ <> "" SELECT CASE a$ CASE "B": PRINT "B key pressed" CASE CHR$(98): PRINT "b key pressed" CASE CHR$(13): PRINT "Intro pressed" CASE CHR$(0) + "H": PRINT "Up arrow key pressed" CASE CHR$(0) + CHR$(80): PRINT "Down arrow key pressed" CASE CHR$(27): END END SELECT LOOP The function a$ = INPUT$(1) is nearly equivalent to the INKEY$'s loop. It doesn't need Intro pressing and doesn't makes echo on the screen, but since it returns only one character you won't be able to differentiate the 2 bytes extended characters. Nevertheless, it's useful in the inespecific controlled pause "Press any key..", since it saves code. +----------------------+ | The ON KEY statement | +----------------------+ I used to hate ON KEY, because I used to find it capricious. Sometimes it seemed to work perfectly, sometimes not. Then I saw that the problem was the CapsLock and NumLock keys. I understood that, if one or both of them are activated, the function of all the keys is changed and the keys whose detection I programmed are not the same that those that the keyboard has now. Since I learnt how to solve it, I love ON KEY, I found it very useful. Its main advantage is, that I may put it at the beginning of a program (for example, to end if Esc is pressed) and I won't worry any more: the Esc key will be detected, although the program be running in a SUB or making a loop.It saves considerable code. Without ON KEY we should have to code the end condition in many parts of the program. ON KEY detects "keys", not characters. Therefore it uses the scan codes. But the syntax of ON KEY demands that these codes must be written inside of CHR$( ) functions and they will look like ASCII codes and this may be confusing. Also the ON KEY syntax requires to use a subroutine (memories of old basic). If we code ON KEY(15) PRINT "Hello", we'll get an "Gosub expected" error message. We must define a label and to set there the actions that we want and the returning instructions. The ON KEY programming has several steps: (1) Keys defining. Some keys are QBasic predefined. Others may be defined by the programmer using the KEY statement. (2) For each key to detect we must set the ON KEY statement with the syntax ON KEY(n) GOSUB label. (3) Each key detection must be enabled using the KEY(n) ON (also may be disabled with the KEY(n) OFF statement, which it may be useful in some programs). (4) Coding of the subroutines, either one for each key or common, depending of our necessities. Do not confuse KEY, ON KEY(n) and KEY(n) ON, they are different statements. Only key defining may be somewhat complicated, and we'll see it in detail. The keys are identified through an order number, from 1 to 25 and 30 to 31 (these are KEY statement's arbitrary numbers, they are neither scan codes nor ASCII codes). The Qbasic predefined keys are: 1 - 10 Function keys F1 to F10 11 - 14 Arrow keys (up, left, right, down). These arrow keys are those in the numerical keyboard keys (memories of the past again). 30 - 31 Function keys F11 and F12 The keys from 15 to 25 may be defined by the programmer. Therefore we may define only 11 keys, and that it isn't too much, as we'll see. The syntax is: KEY number, CHR$(kbdFlag) + CHR$(scanCode) The keyboardFlag indicates if the key that we're defining is combined with another one. We must use the following values: 0 if the key is pressed alone 1 to 3 if any Shift and the key are combined 4 if Ctrl and the key are combined 8 if Alt and the key are combined 32 if NumLock is activated 64 if CapsLock is activated 128 if we are defining some extended key Also these values may be combined each other. If we want to detect a Ctrl+Alt+key combination, the keyboardFlag must be 4 + 8 = 12. Other examples: - to detect the Intro key pressed alone: KEY 15, CHR$(0) + CHR$(28) - to detect Ctrl+Esc: KEY 16, CHR$(4) + CHR$(1) - to detect the Alt key pressed alone: KEY 17, CHR$(0) + CHR$(56) Notice that these codes look like extended ASCII codes, specially when the first term is CHR$(0). But I insist: the Alt key hasn't ASCII code. If we try to detect it with INKEY$ using CHR$(0)+ CHR$(56) or CHR$(0) + "8", it on't work. Now we'll see a runnable example of Alt key detection: CLS KEY 15, CHR$(0) + CHR$(56) ON KEY(15) GOSUB AltKey KEY(15) ON FOR n = 1 to 50000 LOCATE 10, 36: PRINT n NEXT n END AltKey: LOCATE 12, 30: PRINT "Alt has been pressed" SLEEP 1 CLS RETURN If this program didn't work for you, look at your NumLock and CapsLock. The Alt key may be detected, even if the program is running a loop, but it requires that both Locks to be inactivated. This is the reason: if one of both Locks are activated, the meaning of the other keys changes, we must consider them as combined. If we press now a key, we're really pressing a combination: NumLock+key or CapsLock+key or NumLock+CapsLock+key. NumLock+Alt is not the same key than Alt. If we defined Alt, we had not to aspire that NumLock+Alt can be detected. If we want to detect a key in any circumstance, we must define the key 4 times, covering all the possibilities: - to detect the Alt key pressed alone: KEY 15, CHR$(0) + CHR$(56) - to detect it if Num Lock is on: KEY 16, CHR$(32) + CHR$(56) - to detect it if Caps Lock is on: KEY 17, CHR$(64) + CHR$(56) - to detect it if both Locks are on: KEY 18, CHR$(96) + CHR$(56) (32 + 64 = 96) Then we may drive all GOSUBs to the same label: ON KEY(15) GOSUB AltKey ON KEY(16) GOSUB AltKey ON KEY(17) GOSUB AltKey ON KEY(18) GOSUB AltKey And, of course, all key detections must be enabled: KEY(15) ON: KEY(16) ON: KEY(17) ON: KEY(18) ON Now the program will detect the Alt key always. But notice that, since we may define only 11 keys, we'll be able to make a complete programming of only 2 keys. Fortunately, the Qbasic's predefined keys (F1 trough F12) work perfectly in all cases. Now an example of defining extended keys. We've seen above that the predefined arrow keys are those of the numerical keybord. The "normal" arrow keys belong to the extended keyboard and won't work as predefined keys. If we would want to detect them, we should define them as following: (Up arrow key: Scan code #72) - to detect it alone: KEY 15, CHR$(128) + CHR$(72) - with Num Lock on: KEY 16, CHR$(160) + CHR$(72)'(128 + 32 = 160) - with Caps Lock on: KEY 17, CHR$(192) + CHR$(72)'(128 + 64 = 192) - with both Locks on: KEY 18, CHR$(224) + CHR$(72) '(128 + 32 + 64 = 224) This is only an example of how to define extended keys. We couldn't define the extended arrow keys by this method, because we would need 4 * 4 = 16 keys and we only dispose of 11. Indeed the "normal" arrow keys are detected well with INKEY$. +------------------+ | The INP function | +------------------+ INP gets information through the I/O ports of the computer. If we give to it the hexadecimal &H60 address, we'll read through the keyboard I/O port and we'll get a value (one byte) that is the scan code of the last pressed key. Moreover, this value will be different if the key is still pressed or if it has been released: If the key is still pressed: we get the key's scan code if the key has been released: we get the key's scan code + 128 So, we may establish a key recognizing routine using the obtained value: CLS PRINT "In this program you may detect Ctrl y Alt" PRINT "Esc for ending" DO k = INP(&H60) LOCATE 12, 30 SELECT CASE k CASE 1: END CASE 29: PRINT "The Ctrl key is pressed " CASE 56: PRINT "The Alt key is pressed " CASE 157: PRINT "The Ctrl key has been released" CASE 184: PRINT "The Alt key has been released " END SELECT LOOP Notice that we're detecting easily Ctrl and Alt, both keys haven't ASCII code and usually their detection is considered as difficult. Key detection with INP is very fast. Moreover the possibility of press/release detecting makes it very useful for games programming. As an example, we'll see how to transform the Intro key in a Accelerator: CLS PRINT "Press and release Intro" PRINT "Esc for ending" DO SOUND (50 + incr), .5 k = INP(&H60) SELECT CASE k CASE 1: END CASE 28 inc = inc + .5 IF inc > 120 THEN inc = 120 CASE 156 inc = inc - .5 IF inc < 0 THEN inc = 0 END SELECT DEF SEG = &H40 POKE &H1A, PEEK(&H1C) LOOP The 2 last lines inside the loop are a routine for discharging the keyboard buffer, avoiding saturation and beeping. +---------------------------------------+ | How to choose and combine the methods | +---------------------------------------+ Each method has their own advantages and disadvantages and therefore we couldn't say which is the best. We could say rather that a method will be the best in each specific case. You could have the following tips as a guide: (1) Use INKEY$ each time as you can. Detecting characters through INKEY$ is safer to get portability to our programs. If we ask the user for pressing the "A" key, our code will detect it, althought in some countries the "A" key may have another location in the keyboard. We have no limmits about how many keys to detect, but we only can detect the keys which have ASCII code. (2) Use ON KEY if you need keys that will be used along the whole program (for example Esc for end, F1 for Help). It may be useful also in games programming, if you need a repeating action along the whole program. Practically we count on the 12 predefined function keys (F1 through F12), since the numerical keyboard arrow keys are not to be used currently. If you can disable NumLocK and CapsLock, you should count on 11 keys to define, on the contrary only 2. (3) The keys which haven't ASCII code may be detected either through ON KEY or through INP, depending on their usage, frequent or ocasional, as it was stated in (2). Althought you are using the scan codes, the position of the main controlling keys is quite constant and you won't have portability problems. (4) If you want speed and press/release detection, as in games, use INP Finally, here is an example about how the different method may be combined in the same program. In the example, Intro is detected through ON KEY, Ctrl through INP and Esc through INKEY$: CLS PRINT "In this program you may detect: PRINT " - Ctrl (through INP) PRINT " - Intro (through GOSUB) PRINT " - Esc (through INKEY$) KEY 15, CHR$(0) + CHR$(28) KEY 16, CHR$(32) + CHR$(28) KEY 17, CHR$(64) + CHR$(28) KEY 18, CHR$(96) + CHR$(28) ON KEY(15) GOSUB IntroKey ON KEY(16) GOSUB IntroKey ON KEY(17) GOSUB IntroKey ON KEY(18) GOSUB IntroKey KEY(15) ON: KEY(16) ON: KEY(17) ON: KEY(18) ON DO DO: a$ = INKEY$ k = INP(&H60) IF k = 29 THEN LOCATE 10, 20: PRINT "Ctrl has been pressed" SLEEP 1 LOCATE 10, 20: PRINT STRING$(21, 32) END IF LOOP UNTIL a$ <> "" IF a$ = CHR$(27) THEN LOCATE 10, 22: PRINT "Esc has been pressed" LOCATE 11, 20: PRINT "and the program will end" SLEEP 1 END END IF LOOP IntroKey: LOCATE 10, 20: PRINT "Intro has been pressed" SLEEP 1 LOCATE 10, 20: PRINT STRING$(22, 32) RETURN +-----------------+ | There is more.. | +-----------------+ The methods reviewed in this tutorial aren't all we can do. We could also detect keys looking in the memory through PEEK. We could make bit decoding to detect, for example, if the left or the right Alt key has been pressed. But this goes beyond the scope of this article. +---------------------+ | The tables you need | +---------------------+ In the Help File of QBasic 1.1 and QuickBasic 4.5 you'll find: - The ASCII Character Codes List - The Keyboard Scan Codes List - I'll give you the list of the more common extended ASCII codes (it lacks those of combined keys) Up arrow CHR$(0) + "H" or CHR$(0) + CHR$(72) Down arrow CHR$(0) + "P" or CHR$(0) + CHR$(80) Left arrow CHR$(0) + "K" or CHR$(0) + CHR$(75) Right arrow CHR$(0) + "M" or CHR$(0) + CHR$(77) F1 CHR$(0) + ";" or CHR$(0) + CHR$(59) F2 CHR$(0) + "<" or CHR$(0) + CHR$(60) F3 CHR$(0) + "=" or CHR$(0) + CHR$(61) F4 CHR$(0) + ">" or CHR$(0) + CHR$(62) F5 CHR$(0) + "?" or CHR$(0) + CHR$(63) F6 CHR$(0) + "@" or CHR$(0) + CHR$(64) F7 CHR$(0) + "A" or CHR$(0) + CHR$(65) F8 CHR$(0) + "B" or CHR$(0) + CHR$(66) F9 CHR$(0) + "C" or CHR$(0) + CHR$(67) F10 CHR$(0) + "D" or CHR$(0) + CHR$(68) F11 CHR$(0) + "…" or CHR$(0) + CHR$(133) F12 CHR$(0) + "†" or CHR$(0) + CHR$(134) Ins CHR$(0) + "R" or CHR$(0) + CHR$(82) Del CHR$(0) + "S" or CHR$(0) + CHR$(83) Home CHR$(0) + "G" or CHR$(0) + CHR$(71) End CHR$(0) + "O" or CHR$(0) + CHR$(79) PgUp CHR$(0) + "I" or CHR$(0) + CHR$(73) PgDn CHR$(0) + "Q" or CHR$(0) + CHR$(81) Try to make a program that gives the simple or extended ASCII code of any key or keys combination you press (if it has one). +--------------+ | Contact Info | +--------------+ That's all for this tutorial. Hopefully it will help newbies to clear their knowledge about key detection. If you have questions or comments you may contact me via email at: fer2roc@excite.com +---------+ | Credits | +---------+ I want to thank Zip, from whom I got some of the information of above. Also for reviewing the article and of course for hosting my tutorials on his web site: http://angelfire.com/co/zippy15