Catching keystrokes in Free Pascal programs and applications.
This tutorial is about reacting, when the user strokes a key on the keyboard. How the program or application notices, when a key is pressed? How to proceed to get the corresponding character or code, in the case a special or control character was pressed? How to do it in a simple command line program and how to do it in a Lazarus GUI application?
Catching key strokes in Free Pascal command line programs.
If you run a command line program by double-clicking the executable, the Command Prompt window is automatically closed when the program terminates (this is how it works
on MS Windows, no idea if this applies to Linux, too). Thus, if you want to be sure to view the program's output, you'll have to pause the program before it reaches the
end of the code. The simplest way to do so, is to tell the user to push ENTER to exit; a simple Readln may be used in this case:
// Terminating a program by hitting the ENTER key
Write('Hit ENTER to terminate the program... ');
Readln;
If we want to terminate the program with any keystroke, we can use the Boolean function KeyPressed within a loop. This function is defined
in the Crt unit, thus we must specify this unit in the uses clause.
// Terminating a program by hitting any key
uses Crt;
...
Write('Hit any key to terminate the program... ');
repeat
until KeyPressed;
The last keyboard key pressed may be retrieved by using the function ReadKey (also defined in the Crt unit). The function result is a character. No problem for letters, numbers and symbols, but how to deal with special and command keys, like ENTER, ESC, TAB, or the arrow keys? Characters are internally stored as ASCII codes. ASCII code 0 corresponds to the NULL character, ASCII codes from 1 to 31 are control or special characters, printable characters are in the range from 32 to 126, 127 is the ESC key, characters above 127 give no output if the system codepage is actually 65001 (UTF8), otherwise, the output, you get, depends on the code page used. Knowing the ASCII code, you can convert it into a character, using the function Chr. And inversely, you can convert a character to the corresponding ASCII code using the function Ord. Note, that character constants may be written as the corresponding ASCII code, preceded by the number sign(#). For example: Chr(27) is equivalent to #27.
In fact, the ReadKey function is somewhat more complicated than described above. It seems obvious, that with 128 ASCII codes it's not
possible to describe all possible keyboard keys. Not only that there is a whole set of special keys (the function keys F1 – F12 alone need 12 codes), but there is
also the combination of a control key with other keys, as for example CTRL+C or ALT+ENTER. Most keystrokes, simple or combinations, may be retrieved correctly
by ReadKey thanks to a special feature of this function. For the keys of printable characters and the most common special keys, it returns
one single character (or speaking numeric, the ASCII code corresponding to the key pressed). For other keys, ReadKey returns 2 characters:
the character NULL + some other character. For example:
the TAB key corresponds to ASCII code 9, the rightwards arrow to 0 + 77
c → 99, C (SHIFT+c) → 67, CTRL+c → 3 (corresponding to special key BREAK), ALT+c → 0 + 46.
Note that multiple control keys are not handled by ReadKey. Only one of the control keys will be retained and this by considering the priority ALT > CTRL > SHIFT. Example:
CTRL+SHIFT+c → 3; ALT+CTRL+SHIFT+c → 0 + 46.
So, if you want to test if the user of your program has pressed some special key, you have to know if it is a one- or two-character key and what is the ASCII code
corresponding to the character returned by the ReadKey function. Long years ago, I wrote a small program, that in a loop (terminated by
the ESC key) waits for a keyboard key being pressed and for each key pressed, displays the corresponding ASCII code. Here is the source (use the following link to
download it, together with another small program, that displays the ASCII table for codes 32 to 126).
program keycodes;
uses Crt;
const
Key_NULL = #0;
Key_ESC = #27;
var
Key: Char;
begin
Writeln('Hit the key to display the code of or ESC to terminate'); Writeln;
repeat
Key := ReadKey; // retrieve key pressed (as character)
Write('ASCII code of key pressed is : ');
if Key = Key_NULL then begin
// NULL character indicates a two-characters key
Write('0 + ');
Key := ReadKey; // retrieve the second character
end;
Writeln(Ord(Key)); // display ASCII code
until Key = Key_ESC; // terminate the program if ESC has been pressed
end.
In a situation where your program is running, doing some iterative stuff by default, whereas given keyboard keys pressed change this default advancement, the
simplest way to proceed (I guess) is to set up a loop, containing the default action code, and the ReadKey statement(s) placed into
an if KeyPressed block. Here some code from my command line snake game (Key_Null is
here a Boolean variable and Key_ESC an integer constant equal to 27):
repeat
--- Move the snake ---
// Check if user pressed a key and do corresponding action
Key := ' ';
if KeyPressed then begin
Key := ReadKey;
if Ord(Key) = 0 then begin
Key_Null := True;
Key := ReadKey;
end;
if Ord(Key) = Key_ESC then
// ESC exits the game
EoG := True
else if Key_Null then begin
// Other keys (they are all 2-character keys) do some action, in particular change the snake's direction
case Ord(Key) of
- Action for the different keys -
end;
end;
end;
until (M.GoodLeft = 0) or EoG; // end the loop if all meats have been eaten or game is terminated
Catching key strokes in Lazarus GUI applications.
Lazarus forms support several keyboard handlers, the ones that are of interest here being:
OnKeyPress | Handler for a character entered by the user. The handler only receives characters, not control or other special key codes and can only be used with ANSI characters. The key may be retrieved in a variable of type Char. |
OnUTF8KeyPress | Handler for a character entered by the user. The handler only receives characters, not control or other special key codes. The handler receives the UTF-8 character code and so works correctly with all character keyboard keys. The key may be retrieved in a variable of type TUTF8Char, that is not an ordinal type as Char; in fact it is a string. |
OnKeyDown | Handler for a keyboard key pressed. The handler can filter keys, for special use in e.g. non-textual controls. It receives all keystrokes, including control and other non-visual keys. Keys are encoded as virtual keys, with separate active modifier keys. Text input instead should be checked in an On(UTF8)KeyPress handler. |
By default, the keystrokes are caught if the form is active and if the focus is at the form itself, not at any of the form's controls! To be sure, that the keystrokes will be caught independently of the actual focus within the form, set the form's property KeyPreview = True.
Catching character keys.
Here some code from my WGuess3 application. When the player pushes a (character) key on the keyboard, the routine
retrieves it and then checks if this character has already been entered (with the edit field edLettersEntered containing the characters entered so far).
procedure TfWGuess3.FormUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
var
S: string;
I: Byte;
NewLetter: Boolean;
begin
if bKeybEvent then begin
UTF8Key := UTF8UpperCase(UTF8Key);
S := edLettersEntered.Text;
// Check if the letter pressed hasn't already been entered
NewLetter := True;
for I := 1 to UTF8Length(S) do begin
if UTF8Copy(S, I, 1) = UTF8Key then
NewLetter := False;
end;
if NewLetter then begin
.....
end;
.....
end;
end;
This code also shows the best and simplest way to work with UTF8 characters. Defining S as a string (and not as an UTF8string, as I did in my WGuess2 application, you can use the same coding as you would use with ANSI characters, except that you'll have to use the UTF8 functions (UTF8Length, UTF8Copy, etc) instead of the "normal" ones.
If you look at the code above, you may wonder what the Boolean bKeybEvent is for. It's the simplest way, I found, to turn the keyboard event handler on and off. Set to False when a new game is started, hitting a key on the keyboard will have no effect. Setting it to True when the word to guess is generated and the player has to enter letters in order to find the word, these letters will be caught by the routine, as described above. And finally, resetting it to False if the word has been found (or if the user pushes the Solve button in order to enter the entire word), keystrokes will not trigger anymore the actions coded in the keyboard handler routine.
Catching control and special keys.
The OnKeyDown event of an object allows you to check what key the user has pressed on the keyboard. The key ("regular key") may be retrieved in a variable of type Word, the control character(s) pressed is (are) returned separately as a constant of type TShiftState, type that is declared within the LCL as a set of control keys.
The word retrieved for a given key pressed is the encoding for a so-called virtual key, that are constants declared in the LCLType unit. All virtual key constants begin with VK_. For the number and letter keys, just add the number resp. letter; examples: 5 → VK_5, J → VK_J. It similarly works for the function keys (defined from F1 to F24); example F6 → VK_F6. Here some further of these constants (use the following link to view a list of all available LCLType constants, including all virtual keys declarations).
Enter key | VK_RETURN | Escape key | VK_ESCAPE |
Delete key | VK_DELETE | Insert key | VK_INSERT |
Tab key | VK_TAB | Backspace key | VK_BACK |
Home key | VK_HOME | End key | VK_END |
Left arrow key | VK_LEFT | Right arrow key | VK_RIGHT |
Up arrow key | VK_UP | Down arrow key | VK_DOWN |
Page up key | VK_PRIOR | Page down key | VK_NEXT |
Unknown key | VK_UNKNOWN |
To test if a control key has been pressed, you must check if the actual TShiftState set contains the corresponding constant. Here the most important constants, defined as elements of the TShiftState set (use the following link to view the TShiftState type declaration, with all set elements available).
Shift key | ssShift | Ctrl key | ssCtrl |
Alt key | ssAlt | Alt-GR | ssAltGr |
Caps lock key | ssCaps | Num lock key | ssNum |
Scroll lock key | ssScroll |
if (Key = VK_F12) and (ssCtrl in Shift) then ...
Here the complete code of the keyboard handler in my Snake2 application. The snake is moving (controlled by the code
located in a timer routine) and the user can change this default action by pushing given keys on the keyboard, in particular the arrow keys in order to change the snake's
direction of displacement.
procedure TfGame.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if bKeybEvent then begin
// Do only if keyboard capture is enabled (i.e. when the snake is moving)
if Key = VK_LEFT then begin
// Key pressed = left arrow
if iDirLastX <> 1 then begin
// Prevent direct move in opposite direction
iDirX := -1; iDirY := 0; // set snake direction to leftwards
end;
end
else if Key = VK_RIGHT then begin
// Key pressed = right arrow
if iDirLastX <> -1 then begin
iDirX := 1; iDirY := 0; // set snake direction to rightwards
end;
end
else if Key = VK_UP then begin
// Key pressed = up arrow
if iDirLastY <> 1 then begin
iDirX := 0; iDirY := -1; // set snake direction to upwards
end;
end
else if Key = VK_DOWN then begin
// Key pressed = down arrow
if iDirLastY <> -1 then begin
iDirX := 0; iDirY := 1; // set snake direction to downwards
end;
end
else if (Key = VK_F12) and (ssCtrl in Shift) then begin
// Key pressed = CTRL+F12
rdMeals.GoodLeft := 0; // cheat key: allows to pass to next round without
having to eat the resting meals
end;
iDirLastX := iDirX; iDirLastY := iDirY; // Save the snake's actual position
end;
end;
Lazarus Wiki note: When a key is held down, the OnKeyDown event is re-triggered. The first re-triggering event is after approx 500 ms and the next ones cycle between 30 and 50 ms.
If you find this text helpful, please, support me and this website by signing my guestbook.