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:
- Key input emulation using the
keybd_eventAPI - Creating a non‑activating window via
CreateParams - IME control (using
WM_IME_CONTROL,IMC_GETOPENSTATUS,IMC_SETOPENSTATUS) to toggle Japanese input ON/OFF - Handling key codes with
MapVirtualKeyanddwExtraInfo
Practical sample code is included to demonstrate how to build a fully functional on‑screen keyboard.
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.
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.
Configuring the Project Options
Open Project → Options.
In the upper‑left pane, select Application -> Manifest.
Set DPI Awareness to None, then click Save.
Select Run → Run to compile and execute the application.
