Živě.cz o počítačích a internetu

Tipy a triky v Delphi, díl 138. - klávesnice

Jan Šindelář - 12.5.2004

Nejprve malé seznámení s problémem. Představte si běžný formulář, který je plný objektů uživatelského rozhraní. Z těchto prvků je vždy jeden "aktivní", má tzv. focus. Obvykle to bývá nějak jemně naznačeno ve vizuální podobě prvku (ohraničení apod). Pokud má prvek focus, je připraven přijímat podněty od uživatele prostřednictvím klávesnice. Stisknete-li tedy v daný okamžik klávesu, bude příslušná událost vyvolána pro ten objekt, který má focus. Tím se pak lze řídit a například podle konkrétní stisknuté klávesy (která se předá jako parametr události) příslušně reagovat.

Věc má ovšem jeden malý háček. Ne všechny komponenty totiž focus mohou mít a tím pádem reagovat na podněty z klávesnice. Asi jedním z nejpoužívanějších prvků s tímto omezením je Label. Ačkoliv se jedná ve své podstatě o stejný vizuální objekt jako všechny ostatní, na formuláři si s ním moc legrace neužijete a pomocí klávesnice s ním prostě nic nepořídíte. Stejně tak Panel, PaintBox či Image mají toto omezení. Slouží prostě primárně k zobrazování údajů a není potřeba, aby reagovaly na vstupy z klávesnice.

V dnešním příkladu si ukážeme, jak toto omezení překonat. Budeme konkrétně ovládat komponentu Image a na její plochu kreslit pomocí kurzorových šipek. Pomůže nám k tomu opět Windows API.

Zjednodušeně řečeno si vytvoříme speciální funkci, tzv. hook, která bude chytat jednotlivé podněty z klávesnice v podobě zpráv systému ještě předtím, než budou běžným způsobem zpracovány aplikací. Vytvořit tento klávesový hook nám pomůže API funkce SetWindowsHookEx a samotná výkonná funkce pro kreslení pak bude KeyboardHookProc. Na konci pak musíme zase hook zrušit funkcí UnhookWindowsHookEx.

Teď již tedy k naší dnešní aplikaci. Na prázdný formulář umístíme nejprve komponentu Image. Zarovnání (Align) pak nastavíme na hodnotu alClient. To je vše po vizuální stránce a teď již následuje zdrojový kód:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;

var
  Form1: TForm1;
  KBHook: HHook;
  cx, cy: integer;

implementation

{$R *.dfm}

function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt;
begin
  case WordParam of
    vk_Space:
      with Form1.Image1.Canvas do
      begin
        Brush.Color := clWhite;
        Brush.Style := bsSolid;
        Fillrect(Form1.Image1.ClientRect);
      end;
    vk_Right: cx := cx + 1;
    vk_Left: cx := cx - 1;
    vk_Up: cy := cy - 1;
    vk_Down: cy := cy + 1;
  end;
  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
    begin
      Pen.Color := clRed;
      Brush.Color := clYellow;
      TextOut(0,0,Format(`%d, %d`,[cx,cy]));
      Rectangle(cx - 2, cy - 2, cx + 2, cy + 2);
    end;
  Result := 0;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  KBHook := SetWindowsHookEx(WH_KEYBOARD, @KeyboardHookProc, HInstance, GetCurrentThreadId());
  cx := Image1.ClientWidth div 2;
  cy := Image1.ClientHeight div 2;
  Image1.Canvas.PenPos := Point(cx, cy);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnHookWindowsHookEx(KBHook);
end;

end.

Po spuštění aplikace pak můžete velmi primitivním způsobem kreslit čáru po ploše formuláře (resp. komponenty Image) pomocí kurzorových šipek klávesnice. Po stisknutí mezerníku bude plocha vymazána.

Těm zvědavým z vás ještě doporučím malý pokus. Nejprve zrušte zarovnání komponenty Image tak, aby nevyplňovala celou plochu formuláře. Nyní si na formulář do volného prostoru umístěte několik komponent, které mohou mít focus (Edit, CheckBox atd...). Když budete nyní po spuštění zkoušet mačkat kurzorové šipky, uvidíte, že se nejen příslušným způsobem chovají jednotlivé komponenty (přeskakuje focus, v Editu se pohybuje kurzor apod.), ale zároveň se na Image kreslí. Z toho vidíme, jak naše hook funkce funguje zcela nezávisle, vždy si odchytí příslušnou zprávu z klávesnice a pak ji dále pošle na zpracování běžným způsobem aplikaci. Tento řetězec ale můžeme snadno přetrhnout tím, že jako výstupní návratovou hodnotu (Result) u funkce KeyboardHookProc nastavíme nenulovou hodnotu. Pak ovšem veškeré vstupy z klávesnice bude chytat pouze Image a ostatní objekty nepůjdou klávesnicí ovládat. Samozřejmě možnost ovládat ostatní prvky myší zůstává, můžete dále mačkat tlačítka či přepínat RadioButtony a CheckBoxy, ale do Editu se vám z klávesnice nepodaří vložit ani písmenko.