特集:正規表現によるテキストファイルパースVisual Studio Magazine(4/8 ページ)

» 2004年12月07日 14時05分 公開
[Francesco Balena, Marco Bellinaso,FTPOnline]

グローバルなホットキーの検出

 .NET開発者は、Windowsフォームアプリケーションが入力フォーカスを持っていない時であっても、与えられたキーの組み合わせが押されたかどうかを調べたいことがよくある。

 そのような場合、キーが押されたかどうかを検出するために、2つの基本的な方法がある。どちらもWindows APIの呼び出しを必要とする。もっとも簡単な方法は、GetAsyncKeyState API関数を用いて、キーボードをポーリングする方法だ。

 GetAsyncKeyState関数の宣言は、次のようになる。

' VB.NET
Private Declare Function _
GetAsyncKeyState Lib "user32" _
Alias "GetAsyncKeyState" ( _
ByVal vKey As Keys) As Short

// C#
using System.Runtime.InteropServices;
// ...
[DllImport("user32")]
static extern short
GetAsyncKeyState(Keys vKey);

 GetAsyncKeyState関数は、32ビットの引数をとる。値を取得したり変換して保存したりするときには、この引数に、Keys列挙体を利用できる。

 GetAsyncKeyState関数を使うことは、かなり簡単だ。たとえば次のコードは、エンドユーザーが「Ctrl」+「A」キーの組み合わせでタイプしたかどうかを調査する。

' VB.NET
If GetAsyncKeyState(Keys.A) < 0 And _
GetAsyncKeyState(Keys.ControlKey) _
< 0 Then
' Ctrl+Aキーが押された
End If

// C#
if ( GetAsyncKeyState(Keys.A) < 0 &&
GetAsyncKeyState(Keys.ControlKey)
< 0 )
{
// Ctrl+Aキーが押された
}

 このコードを、Intervalプロパティの値を十分に小さくした(たとえば200ミリ秒)TimerコントロールのTickイベントで動かせば、興味あるすべてのホットキーをトラップできるようになる。

 しかしこのテクニックでは、Timerコントロールのインターバル間隔が短いと、アプリケーションにより多くのオーバーヘッドを加えてしまう。さらに、GetAsyncKeyStateのドキュメントによれば、Windows NT、2000、XPの時に、(1)カレントデスクトップがアクティブなデスクトップではない場合、(2)デスクトップの設定により、エンドユーザーが押したキーがバックグラウンドアプリケーションに知られないように保護されている。そして、アプリケーションがフォアグラウンド状態でない場合には「0」を返す可能性があると示されている。

 そこで、オーバーヘッドを回避するため、RegisterHotKey API関数を使って、グローバルなホットキーを登録する方法をとろう。

' VB.NET
Declare Function RegisterHotKey Lib _
"user32" (ByVal hwnd As IntPtr, _
ByVal id As Integer, _
ByVal fsModifiers As Integer, _
ByVal vk As Keys) As Integer

// C#
[DllImport("user32")]
static extern int RegisterHotKey(
IntPtr hwnd, int id,
int fsModifiers, Keys vk);

 hwndは、最後の2つの引数によって指定されたホットキーがエンドユーザーによって押されたとき、WM_HOTKEYメッセージを受け取るウィンドウのハンドルだ。

 引数idは、ホットキーを区別する識別子であり、システムに登録されたそれぞれのグローバルキーと違ったものでなければならない。アプリケーションがシャットダウンする時には、登録したグローバルホットキー情報を解除するため、UnregisterHotKey API関数を呼び出す必要がある。

 アプリケーションのメインフォームが読み込まれた場合にホットキーを登録し、サブクラスでホットキーをトラップし、フォームが閉じられた際にホットキーを解放するプログラムは、リスト2のようになる。

 クラスのそれぞれのインスタンスに唯一無二のIDを作成するには、マイクロソフトのドキュメントで推奨されているように、GlobalAddAtom API関数を用いる。

 幾つかのAPIを呼び、また、フォームのWndProcメソッドをオーバーライドすることで、入力フォーカスをもっていない時に、エンドユーザーが与えられたキーの組み合わせの通知を受けられるようにする。以下のコードをWindowsフォームクラスに追加しよう。

リスト2■VB.NETとC#において、ホットキーをトラップする
' VB.NET
' Windows API functions and constants
Private Declare Function RegisterHotKey _
Lib "user32" (ByVal hwnd As IntPtr, _
ByVal id As Integer, ByVal fsModifiers _
As Integer, ByVal vk As Keys) As Integer
Private Declare Function UnregisterHotKey _
Lib "user32" (ByVal hwnd As IntPtr, _
ByVal id As Integer) As Integer
Private Declare Function GlobalAddAtom _
Lib "kernel32" Alias "GlobalAddAtomA" _
(ByVal lpString As String) As Short
Private Declare Function GlobalDeleteAtom _
Lib "kernel32" (ByVal nAtom As Short) As Short
Private Const MOD_ALT As Integer = &H1
Private Const MOD_CONTROL As Integer = &H2
Private Const MOD_SHIFT As Integer = &H4
Private Const MOD_WIN As Integer = &H8
Private Const WM_HOTKEY As Integer = &H312

Dim hotkeyID As Short

Sub Form1_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
' ホットキーのために唯一無二のIDを生成する
hotkeyID = GlobalAddAtom("GlobalHotKey " & _
Me.GetHashCode().ToString())
' Ctrl+Aキーを登録する
RegisterHotKey(Me.Handle, hotkeyID, _
MOD_CONTROL, Keys.A)
End Sub

Sub Form1_Closing(ByVal sender As Object, ByVal _
e As System.ComponentModel.CancelEventArgs) _
Handles MyBase.Closing
' ホットキーの登録を解除し、アトムを削除する
UnregisterHotKey(Me.Handle, hotkeyID)
GlobalDeleteAtom(hotkeyID)
End Sub

Protected Overrides Sub WndProc( _
ByRef m As Message)
MyBase.WndProc(m)
If m.Msg = WM_HOTKEY Then
' ホットキーが押されたときの処理
Debug.WriteLine("WndProc: CTRL+A")
End If
End Sub

// C#
// Windows API functions and constants
[DllImport("user32")]
static extern int RegisterHotKey(IntPtr hwnd,
int id, int fsModifiers, Keys vk) ;
[DllImport("user32")]
static extern int UnregisterHotKey(IntPtr hwnd,
int id);
[DllImport("kernel32",
EntryPoint="GlobalAddAtomA")]
static extern short GlobalAddAtom(
string lpString);
[DllImport("kernel32")]
static extern short GlobalDeleteAtom
(short nAtom);

private const int MOD_ALT = 0x01;
private const int MOD_CONTROL = 0x02;
private const int MOD_SHIFT = 0x04;
private const int MOD_WIN = 0x08;
private const int WM_HOTKEY = 0x312;

short hotkeyID;

void Form1_Load(object sender, EventArgs e)
{
// ホットキーのために唯一無二のIDを生成する
hotkeyID = GlobalAddAtom("GlobalHotKey " +
this.GetHashCode().ToString());
// Ctrl+Aキーを登録する
RegisterHotKey(this.Handle, hotkeyID,
MOD_CONTROL, Keys.A);
}

void Form1_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
// ホットキーの登録を解除し、アトムを削除する
UnregisterHotKey(this.Handle, hotkeyID);
GlobalDeleteAtom(hotkeyID);
}

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if ( m.Msg == WM_HOTKEY )
{
// ホットキーが押されたときの処理
Debug.WriteLine("Ctrl+A");
}
}

 上記、リスト2に示したコードには、フォームのクラス内部から呼び出された場合のみの動作という制限がある。

 幾つかの状況では、コンポーネントのような非ビジュアルなクラス内からグローバルホットキーをトラップする必要があるかもしれない。このような用途のために、筆者はフォーム外からもトラップ可能な、GlobalHotKeyというスタンドアロンのクラスを作成した。

© Copyright 2001-2005 Fawcette Technical Publications

注目のテーマ