Delphiでスクリーン・タッチキーボードを自作する方法|keybd_event・IME制御も対応
WindowsタブレットやKIOSK端末などで、標準のタッチキーボードがうまく表示されないことがあります。
そんなとき、Delphiで独自のスクリーンキーボード(ソフトウェアキーボード)を作成することで、柔軟なUIと制御が可能になります。
このページでは、以下の技術を使ってDelphiでタッチキーボードを自作する方法を解説します:
- `keybd_event` APIによるキー入力のエミュレーション
- `CreateParams`でアクティブにならないウィンドウの作成
- IME制御(`WM_IME_CONTROL`, `IMC_GETOPENSTATUS`, `IMC_SETOPENSTATUS`)による日本語入力のON/OFF切り替え
- `MapVirtualKey`や`dwExtraInfo`の活用によるキーコード処理
サンプルコード付きで、実用的なスクリーンキーボードの構築手順を紹介します。
タッチキーボードを作成するにはCreateParamsをオーバーライドしてウィンドウがフォーカスを持たないようにする必要があります。
また、使用するコントロールもフォーカスを持たないもの(TSpeedButtonや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 宣言 }
protected
procedure CreateParams(var Params: TCreateParams); override;
public
{ Public 宣言 }
end;
implementation
{$R *.dfm}
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle :=
(Params.ExStyle
or WS_EX_NOACTIVATE //アクティブにならないウィンドウ
or WS_EX_TOPMOST //常に手前に配置
)
// and (not WS_EX_APPWINDOW) //タスクバーに配置しない
;
end;
キーボードのキーを押すには「keybd_event」API関数を使用します。
procedure keybd_event(bVk: Byte; bScan: Byte; dwFlags: DWORD; dwExtraInfo: UIntPtr); stdcall;
プロジェクトの作成と画面設計
[ファイル]⇒[新規作成]⇒[VCL フォーム アプリケーション -Delphi]をクリックし、新規プロジェクトを作成します。
画面設計は完了です。
ソースコードの記述
キーボードから「F12」キーを押してコードエディタに切り替えます。
以下ソースコードをコピー&ペーストして貼り付けます。
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 宣言 }
IsShift:Boolean;
procedure BtnClick(Sender: TObject);
procedure SetKeyBoard();
protected
procedure CreateParams(var Params: TCreateParams); override;
public
{ Public 宣言 }
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),
(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
// 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 //アクティブにならないウィンドウ
or WS_EX_TOPMOST //常に手前に配置
)
// and (not WS_EX_APPWINDOW) //タスクバーに配置しない
;
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
//フォアグラウンドウィンドウを取得
h:=GetForegroundWindow();
//IMEクラスの既定のウィンドウハンドルを取得
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.
イベントプロパティの設定
キーボードから「F12」キーを押してデザインエディタに切り替えます。
左上ペイン「構造」から「Form1」をクリックします。
左下ペイン「オブジェクト インスペクタ」から「イベント」タブをクリックし「OnCreate」イベントプロパティに「FormCreate」を設定します。
プロジェクト オプションの設定
「プロジェクト」⇒「オプション」をクリックします。
左上ペイン「アプリケーション」⇒「マニフェスト」を選択します。
「DPIの認識」の設定を「なし」に設定し「保存」ボタンをクリックします。
「実行」⇒「実行」をクリックすると、コンパイルして実行されます。
