Intercepting Keyboard Input With Delphi

Delphi Keyboard Hook for TImage
Delphi Keyboard Hook for TImage.

Consider for a moment creation of some fast arcade game. All the graphics are displayed, let's say, in a TPainBox. TPaintBox is unable to receive the input focus — no events are fired when the user presses a key; we cannot intercept cursor keys to move our battleship. Delphi help!

Intercept Keyboard Input

Most Delphi applications typically handle user input through specific event handlers, those that enable us to capture user keystrokes and process mouse movement.

We know that focus is the ability to receive user input through the mouse or keyboard. Only the object that has the focus can receive a keyboard event. Some controls, such as TImage, TPaintBox, TPanel, and TLabel cannot receive focus. The primary purpose of most graphic controls is to display text or graphics.

If we want to intercept keyboard input for controls that cannot receive the input focus we'll have to deal with Windows API, hooks, callbacks and messages.

Windows Hooks

Technically, a "hook" function is a callback function that can be inserted in the Windows message system so an application can access the message stream before other processing of the message takes place. Among many types of windows hooks, a keyboard hook is called whenever the application calls the GetMessage() or PeekMessage() function and there is a WM_KEYUP or WM_KEYDOWN keyboard message to process.

To create a keyboard hook that intercepts all keyboard input directed to a given thread, we need to call SetWindowsHookEx API function. The routines that receive the keyboard events are application-defined callback functions called hook functions (KeyboardHookProc). Windows calls your hook function for each keystroke message (key up and key down) before the message is placed in the application's message queue. The hook function can process, change or discard keystrokes. Hooks can be local or global.

The return value of SetWindowsHookEx is a handle to the hook just installed. Before terminating, an application must call the UnhookWindowsHookEx function to free system resources associated with the hook.

Keyboard Hook Example

As a demonstration of keyboard hooks, we'll create a project with graphical control that can receive key presses. TImage is derived from TGraphicControl, it can be used as a drawing surface for our hypothetical battle game. Since TImage is unable to receive keyboard presses through standard keyboard events we'll create a hook function that intercepts all keyboard input directed to our drawing surface.

TImage Processing Keyboard Events

Start new Delphi Project and place one Image component on a form. Set Image1.Align property to alClient. That's it for the visual part, now we have to do some coding. First, we'll need some global variables:

  Form1: TForm1;
  KBHook: HHook; {this intercepts keyboard input}
  cx, cy : integer; {track battle ship's position}
  {callback's declaration}
  function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;

To install a hook, we call SetWindowsHookEx in the OnCreate event of a form.

 procedure TForm1.FormCreate(Sender: TObject) ;
 {Set the keyboard hook so we  can intercept keyboard input}
           {callback >} @KeyboardHookProc,
                          GetCurrentThreadId()) ;
 {place the battle ship in  the middle of the screen}
 cx := Image1.ClientWidth div 2;
 cy := Image1.ClientHeight div 2;
 Image1.Canvas.PenPos := Point(cx,cy) ;

To free system resources associated with the hook, we must call the UnhookWindowsHookEx function in the OnDestroy event:

 procedure TForm1.FormDestroy(Sender: TObject) ;
  {unhook the keyboard interception}
  UnHookWindowsHookEx(KBHook) ;

The most important part of this project is the KeyboardHookProc callback procedure used to process keystrokes.

 function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt) : LongInt;
 case WordParam of
  vk_Space: {erase battle ship's path}
    with Form1.Image1.Canvas do
     Brush.Color := clWhite;
     Brush.Style := bsSolid;
     Fillrect(Form1.Image1.ClientRect) ;
  vk_Right: cx := cx+1;
  vk_Left: cx := cx-1;
  vk_Up: cy := cy-1;
  vk_Down: cy := cy+1;
 end; {case}
 If cx < 2 then cx := Form1.Image1.ClientWidth-2;
 If cx > Form1.Image1.ClientWidth -2 then cx := 2;
 If cy < 2 then cy := Form1.Image1.ClientHeight -2 ;
 If cy > Form1.Image1.ClientHeight-2 then cy := 2;
 with Form1.Image1.Canvas do
  Pen.Color := clRed;
  Brush.Color := clYellow;
  TextOut(0,0,Format('%d, %d',[cx,cy])) ;
  Rectangle(cx-2, cy-2, cx+2,cy+2) ;
 {To prevent Windows from passing the keystrokes  to the target window, the Result value must  be a nonzero value.}

That's it. We now have the ultimate keyboard processing code.

Note just one thing: this code is in no way restricted to be used only with TImage.

The KeyboardHookProc function serves as a general KeyPreview & KeyProcess mechanism.