トップへ(mam-mam.net/)

How to Create a Custom On‑Screen Touch Keyboard in Delphi keybd_event and IME Control Supported

English

How to Create a Custom On‑Screen Touch Keyboard in Delphi keybd_event and IME Control Supported

On Windows tablets and KIOSK terminals, the default touch keyboard may sometimes fail to appear correctly.
In such cases, creating your own on‑screen (software) keyboard in Delphi allows for flexible UI design and precise control.

This page explains how to build a custom touch keyboard in Delphi using the following techniques:

Practical sample code is included to demonstrate how to build a fully functional on‑screen keyboard.

Creating a touch keyboard in Delphi

To create a touch keyboard, you need to override CreateParams so that the keyboard window does not receive focus.
You must also use controls that do not take focus, such as TSpeedButton or TBitBtn.

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs , Vcl.Buttons;

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle :=
    (Params.ExStyle
    or WS_EX_NOACTIVATE          //window does not become active
    or WS_EX_TOPMOST             //always stays on top
    )
    // and (not WS_EX_APPWINDOW) //do not show in the taskbar
    ;
end;

To simulate key presses, use the keybd_event API function.
procedure keybd_event(bVk: Byte; bScan: Byte; dwFlags: DWORD; dwExtraInfo: UIntPtr); stdcall;

Creating the Project and Designing the Form

Create a new project by selecting File → New → VCL Forms Application – Delphi.

Creating a touch keyboard in Delphi

Writing the Source Code

Press F12 to switch to the code editor.
Copy and paste the following source code into the editor.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs , Vcl.Buttons;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    IsShift:Boolean;
    procedure BtnClick(Sender: TObject);
    procedure SetKeyBoard();
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  public
    { Public declarations }
  end;

  TVKeybd=record
    C,S:String;
    K:Byte;
    W:Integer;
    SB:TSpeedButton;
  end;

var
  Form1: TForm1;
  kbd : array[0..5] of array[0..14] of TVKeybd = (
    (
      (C:'ESC'; S:'ESC'; K:VK_ESCAPE; W:60),
      (C:'F1'; S:'F1'; K:VK_F1; W:60),
      (C:'F2'; S:'F2'; K:VK_F2; W:60),
      (C:'F3'; S:'F3'; K:VK_F3; W:60),
      (C:'F4'; S:'F4'; K:VK_F4; W:60),
      (C:'F5'; S:'F5'; K:VK_F5; W:60),
      (C:'F6'; S:'F6'; K:VK_F6; W:60),
      (C:'F7'; S:'F7'; K:VK_F7; W:60),
      (C:'F8'; S:'F8'; K:VK_F8; W:60),
      (C:'F9'; S:'F9'; K:VK_F9; W:60),
      (C:'F10'; S:'F10'; K:VK_F10; W:60),
      (C:'F11'; S:'F11'; K:VK_F11; W:60),
      (C:'F12'; S:'F12'; K:VK_F12; W:60),
      (C:'Prt'; S:'Prt'; K:VK_SNAPSHOT; W:60),
      (C:''; S:''; K:0; W:0)
    ),(
      (C:'漢字'; S:'漢字'; K:255; W:60),  // "Kanji" key label
      (C:'1'; S:'!'; K:Ord('1'); W:60),
      (C:'2'; S:'"'; K:Ord('2'); W:60),
      (C:'3'; S:'#'; K:Ord('3'); W:60),
      (C:'4'; S:'$'; K:Ord('4'); W:60),
      (C:'5'; S:'%'; K:Ord('5'); W:60),
      (C:'6'; S:'&&'; K:Ord('6'); W:60),
      (C:'7'; S:''''; K:Ord('7'); W:60),
      (C:'8'; S:'('; K:Ord('8'); W:60),
      (C:'9'; S:')'; K:Ord('9'); W:60),
      (C:'0'; S:''; K:Ord('0'); W:60),
      (C:'-'; S:'='; K:VK_OEM_MINUS; W:60),
      (C:'^'; S:'~'; K:VK_OEM_7; W:60),
      (C:'\'; S:'|'; K:VK_OEM_5; W:60),
      (C:'BS'; S:'BS'; K:VK_BACK; W:60)
    ),(
      (C:'Tab'; S:'Tab'; K:VK_TAB; W:60),
      (C:'q'; S:'Q'; K:Ord('Q'); W:60),
      (C:'w'; S:'W'; K:Ord('W'); W:60),
      (C:'e'; S:'E'; K:Ord('E'); W:60),
      (C:'r'; S:'R'; K:Ord('R'); W:60),
      (C:'t'; S:'T'; K:Ord('T'); W:60),
      (C:'y'; S:'Y'; K:Ord('Y'); W:60),
      (C:'u'; S:'U'; K:Ord('U'); W:60),
      (C:'i'; S:'I'; K:Ord('I'); W:60),
      (C:'o'; S:'O'; K:Ord('O'); W:60),
      (C:'p'; S:'P'; K:Ord('P'); W:60),
      (C:'@'; S:'`'; K:VK_OEM_3; W:60),
      (C:'['; S:'{'; K:VK_OEM_4; W:60),
      (C:'Ent'; S:'Ent'; K:VK_RETURN; W:120),
      (C:''; S:''; K:0; W:0)
    ),(
      (C:'Cap'; S:'Cap'; K:VK_CAPITAL; W:60),
      (C:'a'; S:'A'; K:Ord('A'); W:60),
      (C:'s'; S:'S'; K:Ord('S'); W:60),
      (C:'d'; S:'D'; K:Ord('D'); W:60),
      (C:'f'; S:'F'; K:Ord('F'); W:60),
      (C:'g'; S:'G'; K:Ord('G'); W:60),
      (C:'h'; S:'H'; K:Ord('H'); W:60),
      (C:'j'; S:'J'; K:Ord('J'); W:60),
      (C:'k'; S:'K'; K:Ord('K'); W:60),
      (C:'l'; S:'L'; K:Ord('L'); W:60),
      (C:';'; S:'+'; K:VK_OEM_PLUS; W:60),
      (C:':'; S:'*'; K:VK_OEM_1; W:60),
      (C:']'; S:'}'; K:VK_OEM_6; W:60),
      (C:''; S:''; K:0; W:0),
      (C:''; S:''; K:0; W:0)
    ),(
      (C:'Shift'; S:'Shift'; K:VK_SHIFT; W:60),
      (C:'z'; S:'Z'; K:Ord('Z'); W:60),
      (C:'x'; S:'X'; K:Ord('X'); W:60),
      (C:'c'; S:'C'; K:Ord('C'); W:60),
      (C:'v'; S:'V'; K:Ord('V'); W:60),
      (C:'b'; S:'B'; K:Ord('B'); W:60),
      (C:'n'; S:'N'; K:Ord('N'); W:60),
      (C:'m'; S:'M'; K:Ord('M'); W:60),
      (C:','; S:'<'; K:VK_OEM_COMMA; W:60),
      (C:'.'; S:'>'; K:VK_OEM_PERIOD; W:60),
      (C:'/'; S:'?'; K:VK_OEM_2; W:60),
      (C:'\'; S:'_'; K:VK_OEM_102; W:60),
      (C:''; S:''; K:0; W:0),
      (C:''; S:''; K:0; W:0),
      (C:''; S:''; K:0; W:0)
    ),(
      (C:'Del'; S:'Del'; K:VK_DELETE; W:60),
      (C:'Ins'; S:'Ins'; K:VK_INSERT; W:60),
      (C:'Space'; S:'Space'; K:VK_SPACE; W:180),
      (C:'←'; S:'←'; K:VK_LEFT; W:60),
      (C:'↑'; S:'↑'; K:VK_UP; W:60),
      (C:'↓'; S:'↓'; K:VK_DOWN; W:60),
      (C:'→'; S:'→'; K:VK_RIGHT; W:60),
      (C:'Home'; S:'Home'; K:VK_HOME; W:60),
      (C:'End'; S:'End'; K:VK_END; W:60),
      (C:'Up'; S:'Up'; K:VK_PRIOR; W:60),
      (C:'Dn'; S:'Dn'; K:VK_NEXT; W:60),
      (C:''; S:''; K:0; W:0),
      (C:''; S:''; K:0; W:0),
      (C:''; S:''; K:0; W:0),
      (C:''; S:''; K:0; W:0)
    )
  );

Const
  IMC_GETOPENSTATUS=$5;
  IMC_SETOPENSTATUS=$6;

implementation

{$R *.dfm}

uses Winapi.Imm;

{ TForm1 }

procedure TForm1.SetKeyBoard;
var x,y:Integer;
    CapsLockState:SmallInt;
begin
  // Get the current state of CAPS LOCK
  CapsLockState := (GetKeyState(VK_CAPITAL) AND 1);
  for y := Low(kbd) to High(kbd) do
  begin
    for x := Low(kbd[y]) to High(kbd[y]) do
    begin
      if kbd[y][x].C='Shift' then
      begin
        if IsShift then        
          kbd[y][x].SB.Font.Color:=clRed
        else
          kbd[y][x].SB.Font.Color:=clBlack;
      end
      else if kbd[y][x].C='Cap' then
      begin
        if CapsLockState=1 then
          kbd[y][x].SB.Font.Color:=clRed
        else
          kbd[y][x].SB.Font.Color:=clBlack;
      end;

      if IsShift then
      begin
        if (CapsLockState=1) and (Length(kbd[y][x].S)=1) then
          kbd[y][x].SB.Caption:=LowerCase(kbd[y][x].S)
        else
          kbd[y][x].SB.Caption:=kbd[y][x].S;
      end
      else
      begin
        if (CapsLockState=1) and (Length(kbd[y][x].S)=1) then
          kbd[y][x].SB.Caption:=UpperCase(kbd[y][x].C)
        else
          kbd[y][x].SB.Caption:=kbd[y][x].C;
      end;
    end;
  end;
end;

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle :=
    (Params.ExStyle
    or WS_EX_NOACTIVATE          //window does not become active
    or WS_EX_TOPMOST             //always stays on top
    ) 
    // and (not WS_EX_APPWINDOW) //do not show in the taskbar
    ;
end;

procedure TForm1.FormCreate(Sender: TObject);
var x,y,w:Integer;
    sb:TSpeedButton;
begin
  Self.ClientWidth := 60*15;
  Self.ClientHeight := 60*6;
  IsShift:=False;
  for y := Low(kbd) to High(kbd) do
  begin
    w:=0;
    for x := Low(kbd[y]) to High(kbd[y]) do
    begin
      if kbd[y][x].C<>'' then
      begin
        sb:=TSpeedButton.Create(Self);
        sb.Parent:=Self;
        sb.Width:=kbd[y][x].W;
        if kbd[y][x].C='Ent' then
          sb.Height:=120
        else
          sb.Height:=60;
        sb.Font.Height:=-16;
        sb.Left:=w;
        sb.Top:=y*60;
        sb.Tag:=x+(y shl 8);
        sb.OnClick:=BtnClick;
        kbd[y][x].SB:=sb;
        inc(w, kbd[y][x].W);
      end;
    end;
  end;
  SetKeyBoard();
end;

procedure TForm1.BtnClick(Sender: TObject);
var sb:TSpeedButton;
    h,hIME:HWND;
    meStatus:NativeInt;
    Key:Byte;
    ScanCode:Cardinal;
    x,y:Byte;
begin
  sb:=TSpeedButton(Sender);
  y:=(sb.Tag shr 8);
  x:=(sb.Tag and $FF);

  if kbd[y][x].K=255 then
  begin
    //Get the foreground window
    h:=GetForegroundWindow();
    //Get the default IME window handle for that window
    hIME:=ImmGetDefaultIMEWnd(h);
    if hIME<>0 then
    begin
      meStatus := SendMessage(hIME, WM_IME_CONTROL, IMC_GETOPENSTATUS, 0);
      if meStatus=1 then
        SendMessage(hIME, WM_IME_CONTROL, IMC_SETOPENSTATUS, 0)
      else
        SendMessage(hIME, WM_IME_CONTROL, IMC_SETOPENSTATUS, 1);
    end;
  end
  else if kbd[y][x].K=VK_SHIFT then
  begin
    IsShift:=not IsShift;
    SetKeyBoard();
  end
  else
  begin
    if IsShift then
    begin
      ScanCode:=MapVirtualKey(VK_SHIFT, MAPVK_VK_TO_VSC);
      keybd_event(VK_SHIFT,ScanCode,0,0);
    end;
    Key:=kbd[y][x].K;
    ScanCode:=MapVirtualKey(Key, MAPVK_VK_TO_VSC);
    keybd_event(Key,ScanCode,0,0);
    keybd_event(Key,ScanCode,KEYEVENTF_KEYUP,0);
    if IsShift then
    begin
      ScanCode:=MapVirtualKey(VK_SHIFT, MAPVK_VK_TO_VSC);
      keybd_event(VK_SHIFT,ScanCode,KEYEVENTF_KEYUP,0);
    end;
    if Key=VK_CAPITAL then
      SetKeyBoard();
  end;
end;

end.

Setting the Event Properties

Press F12 to switch back to the form designer.
In the upper‑left "Structure" pane, select Form1.
In the lower‑left "Object Inspector", open the Events tab and set the OnCreate event to FormCreate.

Creating a touch keyboard in Delphi

Configuring the Project Options

Open Project → Options.
In the upper‑left pane, select Application -> Manifest.
Set DPI Awareness to None, then click Save.

Creating a touch keyboard in Delphi

Select Run → Run to compile and execute the application.