Computing: Free Pascal Programming

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 keyVK_RETURN Escape keyVK_ESCAPE
Delete keyVK_DELETE Insert keyVK_INSERT
Tab keyVK_TAB Backspace keyVK_BACK
Home keyVK_HOME End keyVK_END
Left arrow keyVK_LEFT Right arrow keyVK_RIGHT
Up arrow keyVK_UP Down arrow keyVK_DOWN
Page up keyVK_PRIOR Page down keyVK_NEXT
Unknown keyVK_UNKNOWN  
As the virtual keys are of type Word, they may not only be tested with an If ... else clause, but also within a Case statement.

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 keyssShift Ctrl keyssCtrl
Alt keyssAlt Alt-GRssAltGr
Caps lock keyssCaps Num lock keyssNum
Scroll lock keyssScroll  
The test, if a given control key has been pressed, can be done using the in operator. Example: To check if CTRL+F12 has been pressed (with Key being the Word, containing the virtual key value and Shift being the TShiftState set with the control keys), you can use the following code:
    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.