Delphi Programming / Object Pascal
[掲載 2005年01月24日] [更新 2017年04月21日] Delphi サンプルプログラム集 |
250_システム関係キーを無効化する |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE(UP1) Pro |
|
250_SystemKeyHook.zip [2,197 KB] 2017年04月21日版 (EXE 同梱) |
- 2010年08月21日
- メッセージを表示するサンプルを追加してコード整備
- 2010年08月24日
- DLL にしないサンプルコードを追加
- 2015年09月26日
- DLL を使用しないローレベルのキーフックのサンプルだけとした
- Windows 7 U64(SP1) + Delphi XE(UP1) Pro で動作確認
|
グローバルキーフックを使用して,システム関係の以下のキー操作を無効にするサンプルです.動作はシステム全体に有効となっていて,実行開始時に無効化するようになっています.
ローカルフックにすると自アプリだけの無効化となります.
- [ALT] + [TAB] タスクの切換え
- [VK_APPS] アプリケーションキー(メニューキー)
- [VK_LWIN] 左のウィンドウズ(スタートメニュー)キー
- [VK_RWIN] 右のウィンドウズ(スタートメニュー)キー
低レベルのキーボードフックである WH_KEYBOARD_LL を使用しています.このフックは DLL にしなくてもグローバルフックを実現できます.
システム関係のキー操作に,[Ctrl] + [Alt] + [Delete] と [Ctrl] + [Shift] + [ESC] がありますが,これらのキー操作はグローバルフックでも無効にはできません.
|
|
図1
設計時画面
|
リスト1
グローバルキーフックを使用して,システム関係キーを無効化 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TForm1 = class(TForm)
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//-----------------------------------------------------------------------------
// 低レベルキーフックのキー情報構造体と定数
//-----------------------------------------------------------------------------
const
LLKHF_EXTENDED = KF_EXTENDED shr 8;
LLKHF_INJECTED = $00000010;
LLKHF_ALTDOWN = KF_ALTDOWN shr 8;
LLKHF_UP = KF_UP shr 8;
LLMHF_INJECTED = $00000001;
type
LPKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;
tagKBDLLHOOKSTRUCT = record
vkCode : DWORD;
scanCode : DWORD;
flags : DWORD;
time : DWORD;
dwExtraInfo : ULONG_PTR;
end;
KBDLLHOOKSTRUCT = tagKBDLLHOOKSTRUCT;
TKbDllHookStruct = KBDLLHOOKSTRUCT;
PKbDllHookStruct = LPKBDLLHOOKSTRUCT;
var
KeyHookHandle : HHOOK=0;
//-----------------------------------------------------------------------------
// フックのコールバック関数
//
// [ALT]+[TAB] タスク切換え
// [VK_APPS] アプリケーションキー
// [VK_LWIN] 左側のウィンドウズ(メニュー)キー
// [VK_RWIN] 右側のウィンドウズ(メニュー)キー
//
// の本来動作を無効(Result := 1 0以外)にする
//-----------------------------------------------------------------------------
function LowLevelKeyProc(Code:Integer; wPar:wParam; lPar:LParam): LRESULT; stdcall;
var
Lkbdll : PKBDLLHOOKSTRUCT;
begin
Result := 0;
if Code < 0 then begin
Result := CallNextHookEx(KeyHookHandle, Code, wPar, lPar);
exit;
end;
Lkbdll := Pointer(lPar);
if Code = HC_ACTION then begin
//キーが[VK_TAB]+[ALT]の時はResult := 1にしてここで処理終了
if (Lkbdll.vkCode in [VK_TAB]) and
((Lkbdll.flags and (LLKHF_ALTDOWN) <> 0)) then begin
Result := 1;
end;
//[VK_XXX]のリストのキーの時はResult := 1にしてメッセージ処理終了
if (Lkbdll.vkCode in [VK_APPS, VK_LWIN, VK_RWIN]) then begin
Result := 1;
end;
end;
if Result = 0 then begin
Result := CallNextHookEx(KeyHookHandle, Code, wPar, lPar);
end;
end;
//=============================================================================
// フック開始
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1Click(nil);
end;
//=============================================================================
// 終了時にはキーフックを解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
Button2Click(nil);
end;
//=============================================================================
// キーフック開始.フック内で指定キーの本来動作を無効にする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
if KeyHookHandle = 0 then begin
KeyHookHandle := SetWindowsHookEx(WH_KEYBOARD_LL,
@LowLevelKeyProc,
HInstance,
0);
end;
end;
//=============================================================================
// キーフックを解除して通常の動作に戻す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
if KeyHookHandle <> 0 then begin
UnhookWindowsHookEx(KeyHookHandle);
KeyHookHandle := 0;
end;
end;
end.
02_システム関係キーを無効化 - メッセージを表示 |
上のサンプルでは,無効にしたキーを押すと,もちろん何も起こりません.これは通常のウィンドウズの動作と違っています.ユーザを惑わせないためには,情報表示のダイアログを表示するといい場合もあります.
このサンプルでは,指定した無効キーを押下すると,ダイアログを表示するようにしてみました.
ダイアログを表示すると,フォーカスを持つウインドウが,本サンプルのウィンドウになってしまいます.そこで,ダイアログを閉じたら直前に入力フォーカスがあったウィンドウを最前面に表示するようにしています.この処理に AttachThreadInput 関数を使用した,スレッドアタッチという手法を用いています.
|
|
図2
実行時の画面
- 無効化したキーを押すと,このダイアログを表示する
- [Stop KeyHook] で通常の動作に戻る
|
リスト2
グローバルキーフックを使用して,システム関係キーを無効化
無効化したキーを押下したらメッセージを表示 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TForm1 = class(TForm)
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
procedure WMAppMessage(var Message: TMessage); message WM_APP+100;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//-----------------------------------------------------------------------------
// 低レベルキーフックのキー情報構造体と定数
//-----------------------------------------------------------------------------
const
LLKHF_EXTENDED = KF_EXTENDED shr 8;
LLKHF_INJECTED = $00000010;
LLKHF_ALTDOWN = KF_ALTDOWN shr 8;
LLKHF_UP = KF_UP shr 8;
LLMHF_INJECTED = $00000001;
type
LPKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;
tagKBDLLHOOKSTRUCT = record
vkCode : DWORD;
scanCode : DWORD;
flags : DWORD;
time : DWORD;
dwExtraInfo : ULONG_PTR;
end;
KBDLLHOOKSTRUCT = tagKBDLLHOOKSTRUCT;
TKbDllHookStruct = KBDLLHOOKSTRUCT;
PKbDllHookStruct = LPKBDLLHOOKSTRUCT;
var
KeyHookHandle : HHOOK=0;
//-----------------------------------------------------------------------------
// フックのコールバック関数
//
// [ALT]+[TAB] タスク切換え
// [VK_APPS] アプリケーションキー
// [VK_LWIN] 左側のウィンドウズ(メニュー)キー
// [VK_RWIN] 右側のウィンドウズ(メニュー)キー
//
// の本来動作を無効(Result := 1 0以外)にする
//-----------------------------------------------------------------------------
function LowLevelKeyProc(Code:Integer; wPar:wParam; lPar:LParam): LRESULT; stdcall;
var
Lkbdll : PKBDLLHOOKSTRUCT;
begin
Result := 0;
if Code < 0 then begin
Result := CallNextHookEx(KeyHookHandle, Code, wPar, lPar);
exit;
end;
Lkbdll := Pointer(lPar);
if Code = HC_ACTION then begin
//キーが[VK_TAB]+[ALT]の時はResult := 1にしてここで処理終了
if (Lkbdll.vkCode in [VK_TAB]) and
((Lkbdll.flags and (LLKHF_ALTDOWN) <> 0)) then begin
Result := 1;
end;
//[VK_XXX]のリストのキーの時はResult := 1にしてメッセージ処理終了
if (Lkbdll.vkCode in [VK_APPS, VK_LWIN, VK_RWIN]) then begin
Result := 1;
end;
if Result = 1 then begin
PostMessage(Form1.Handle, WM_APP+100, wPar, lPar);
end;
end;
if Result = 0 then begin
Result := CallNextHookEx(KeyHookHandle, Code, wPar, lPar);
end;
end;
//=============================================================================
// フック開始
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1Click(nil);
end;
//=============================================================================
// 終了時にはキーフックを解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
Button2Click(nil);
end;
//=============================================================================
// キーフック開始.フック内で指定キーの本来動作を無効にする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
if KeyHookHandle = 0 then begin
KeyHookHandle := SetWindowsHookEx(WH_KEYBOARD_LL,
@LowLevelKeyProc,
HInstance,
0);
end;
end;
//=============================================================================
// キーフックを解除して通常の動作に戻す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
if KeyHookHandle <> 0 then begin
UnhookWindowsHookEx(KeyHookHandle);
KeyHookHandle := 0;
end;
end;
{$WRITEABLECONST ON}
//-----------------------------------------------------------------------------
// フックのコールバック関数から送られてきたメッセージの処理
//
// AttachThreadInputを使用しているのはグローバルホットキーを押下した時に
// フォアグランドだったウィンドウをフォアグランドにするため
//-----------------------------------------------------------------------------
procedure TForm1.WMAppMessage(var Message: TMessage);
const
LShowFlag : Boolean = False;
var
LForegroundWnd : HWND;
LThreadForeID : Cardinal;
LThreadSelfID : Cardinal;
LMsgText : String;
begin
//以下の処理実行中はフックからのメッセージ処理はしないためのフラグ
if LShowFlag = False then begin
LShowFlag := True;
//現在の前面ウィンドウのバンドル取得
LForegroundWnd := GetForegroundWindow;
if LForegroundWnd <> Handle then begin
//そのスレッドIDを取得
LThreadForeID := GetWindowThreadProcessId(LForegroundWnd);
//前面表示するウィンドウのスレッドIDを取得
LThreadSelfID := GetCurrentThreadId;
//スレッドのアタッチ
AttachThreadInput(LThreadSelfID, LThreadForeID, True);
//アタッチしたハンドルのウィドウを前面に
SetForegroundWindow(Handle);
//終了したらアタッチを切り離す
AttachThreadInput(LThreadSelfID, LThreadForeID, False);
end;
LMsgText := '申し訳ありませんが、'+ sLineBreak
+ 'このキーはご利用になれません.';
MessageBox(Handle, PChar(LMsgText), '情報', MB_TOPMOST or MB_ICONINFORMATION);
//直前のアプリにフォーカスを戻す
SetForegroundWindow(LForegroundWnd);
LShowFlag := False;
end;
end;
{$WRITEABLECONST OFF}
end.
03_[ PrintScreen ] と [ Alt ] + [ PrintScreen ] キーを無効にする |
スクリーンショットに使用する [PrintScreen] キーと [Alt] + [PrintScreen] キーであれば,グローバルフックを使用しなくても無効にすることができます.
これらのキーを無効にするには,これらのキーをグローバルホットキーとして登録します.そうすると新しいホットキーが有効になり,前のホットキーが無効となります.グローバルホットキーの登録を解除すれば元のホットキーが有効になります. |
|
図3
設計時の画面 |
|
図4
[PrintScreen] が無効状態で [PrintScreen] キーを押した場合 |
リスト3
[PrintScreen] と [Alt] + [PrintScreen] キーを無効にする |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, AppEvnts;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ApplicationEvents1: TApplicationEvents;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
Clipbrd;
{$R *.dfm}
const
FHotKeyID1 : Integer = $0001;
FHotKeyID2 : Integer = $0002;
//=============================================================================
// フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Button2Click(nil);
end;
//=============================================================================
// [PrintScreen]と[Alt]+[PriintScreen]をホットキーとして登録する
// このキーの押下のメッセージはこのアプリが受け取るようになる
//
// Application.Handleはこのフォームのハンドルでも動作する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
Clipboard.Clear;
RegisterHotKey(Application.Handle, FHotKeyID1, 0, VK_SNAPSHOT);
RegisterHotKey(Application.Handle, FHotKeyID2, MOD_ALT, VK_SNAPSHOT);
end;
//=============================================================================
// [PrintScreen]と[Alt]+[PriintScreen]のホットキー登録を解除する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
UnregisterHotKey(Application.Handle, FHotKeyID1);
UnregisterHotKey(Application.Handle, FHotKeyID2);
end;
//=============================================================================
// ApplicationEvents1コンポのOnMessageイベント処理
// [PrintScreen]と[Alt]+[PriintScreen]のメッセージを処理する
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
var Handled: Boolean);
var
LText : string;
begin
if (Msg.message = WM_HOTKEY) and (HiWord(Msg.lParam) = VK_SNAPSHOT) then begin
if (Msg.wParam = FHotKeyID1) or (Msg.wParam = FHotKeyID2) then begin
LText := 'スクリーンショットの機能は無効です';
MessageBox(Handle, PChar(LText), '情報', MB_TOPMOST or MB_ICONINFORMATION);
end;
end;
end;
end.
04_WM_HOTKEY メッセージでキーの押下を検出する場合 |
前項のサンプルでは TApplicationEvents.OnMessage でホットキーの押下を検出しています.
TApplicationEvents.OnMessage では,アプリケーションで発生するメッセージのほとんどのメッセージを補足することができますが,前項のサンプルで RegisterHotKey の登録元を自分のフォームにすれば専用のメッセージである WM_HOTKEY でホットキーの押下を検出できます.その例です.
本サンプルでは GlobalAddAtom を使用してグローバルホットキーの ID 値を取得しています. GlobalAddAtom を使用すると,グローバルホットキーの ID を自動的に生成してくれます.実際のアプリケーションでは GlobalAddAtom を使用した方がいいでしょう.
|
リスト4
[PrintScreen] と [Alt] + [PrintScreen] キーを無効にする
WM_HOTKEY メッセージでキーの押下を検出する例 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private 宣言 }
FHotKeyID1 : Integer;
FHotKeyID2 : Integer;
protected
procedure WMHotKey(var Message: TMessage); message WM_HOTKEY;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
Clipbrd;
{$R *.dfm}
//=============================================================================
// フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Button2Click(nil);
end;
//=============================================================================
// [PrintScreen]と[Alt]+[PriintScreen]をホットキーとして登録する
// このキーの押下のメッセージはこのフォームが受け取るようになる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
Clipboard.Clear;
//ホットキーをグローバルアトムのテーブルに追加
//文字列を識別する一意の値をホットキーIDとして取得
FHotKeyID1 := GlobalAddAtom('MySnapshotKey1');
FHotKeyID2 := GlobalAddAtom('MySnapshotKey2');
RegisterHotKey(Handle, FHotKeyID1, 0, VK_SNAPSHOT);
RegisterHotKey(Handle, FHotKeyID2, MOD_ALT, VK_SNAPSHOT);
end;
//=============================================================================
// [PrintScreen]と[Alt]+[PriintScreen]のホットキー登録を解除する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
UnregisterHotKey(Handle, FHotKeyID1);
UnregisterHotKey(Handle, FHotKeyID2);
//アトムに関連付けられている文字列を、アトムテーブルから削除
GlobalDeleteAtom(FHotKeyID1);
GlobalDeleteAtom(FHotKeyID2);
end;
//-----------------------------------------------------------------------------
// WM_HOTKEYメッセージ処理
//
// Messageの型はデフォルトではTWMHotKeyであるがTMessageに変更している
// TMessageにするとWindows APIの説明がそのまま使用可能
//-----------------------------------------------------------------------------
procedure TForm1.WMHotKey(var Message: TMessage);
var
LText : string;
begin
if (Message.WParam = FHotKeyID1) or (Message.WParam = FHotKeyID2) then begin
LText := 'スクリーンショットの機能は無効です';
MessageBox(Handle, PChar(LText), '情報', MB_TOPMOST or MB_ICONINFORMATION);
end;
end;
end.
|