ホットキーとホットキーコントロール

 便利な機能なのに使い方がよく分からない、そういう機能のひとつが「ホットキー」です。今回はその使い方を。

ホットキーってなんやねん?
 「ホットキー」とは、特定のキーとウィンドウを登録すると、どのウィンドウにフォーカスがあるのかは関係なく、そのキーの入力がキャンセルされ、代わりに登録したウィンドウへとWM_HOTKEYが送られてくるというものです。

 この機能を簡単に見る方法、そしてこの単語に関して混乱する理由が、「ショートカット」です。ショートカットのプロパティには「ショートカットキー」という欄があり、ここにキー入力をすると、いついかなる状況でもそのキーを入力したときにショートカットが実行されます。
 ところがこの「ショートカットキー」という単語が、「個々のアプリケーションでの短縮コマンド」としても使われているため、同じようなものだと勘違いしてしまいます。この「ショートカットキー」と「ホットキー」は別物だと考えてください。

 ホットキーはあくまでグローバルなものです。そのため他のアプリケーションのキー入力を邪魔する可能性があるというデメリットはあるものの、サブクラス化やシステムフックといった機能で実現するよりもずっと安全なものなので、まずは憶えておきましょう。

ホットキーの登録
 ホットキーの登録にはRegisterHotKey()というAPIを使用します。


void CMainFrame::RegHotKey()
{
	UINT	uiID = VK_F10;
	UINT	uiMod = MOD_CONTROL | MOD_SHIFT;
	::RegisterHotKey( m_hWnd, (int)MAKEWORD( uiID, uiMod ), uiMod, uiID );
}
	

 第1引数には、登録したキーが押されたときに送られてくるWM_HOTKEYを受け取るためのウィンドウのハンドルを渡します。
 第2引数は「ホットキーのインデックス」です。どうやらこのインデックスについての規定がしっかりしていないみたいで、一応、0x0000〜0xBFFFの値(DLLは0xC000〜0xFFFF)という範囲であればどうでもいいようです。この部分はあとで説明します。
 第3引数は特殊キーのコードです。特殊キーはALTキー(98ならGRPHキー)、CTRLキー、SHIFTキーのみっつで、このみっつのフラグを組み合わせて使うことができます。
 第4引数にはキーコードが入ります。例えば「CTRL+A」であれば、「A」をここで指定します。キーコードの一覧は、APIリファレンスの付録の「Virtual-Key Codes」というページにあるのでそちらを参照してください。
 上の例では「CTRLキーとSHIFTキーを押しながらファンクションキーのF10を押したとき」にメッセージが送られてきます。

 さて、インデックスについての説明です。このホットキーの登録は、ウィンドウハンドルとインデックスによって識別されているようです。そのため、ひとつのウィンドウの中でのインデックスを考えればいいわけです。ホットキーはグローバルなものですが、このインデックスが他のアプリケーションとかち合うんじゃないかなとか考える必要はありません。
 インデックスが同一の状態で、キーコードを変えて登録すると、登録しなおした方だけ有効になります。前のキーコードは削除されるので、ひとつのインデックスに重複して複数のキーが登録されるということはありません。このインデックスのシステムは「タスクトレイ上のアイコン」と同じものなのでしょう。このAPIは登録と更新を兼ねているというわけです。
 上の例ではキーコードそのものをインデックスとして使っていますが、別に0とか1とかでも全然構わないでしょう。

 注意することは、「他のアプリケーションとキーコードが重ならない」ようにすることです。重なった場合、関数は0を返してきます。この場合にはキーコードを変更する必要があります。

ホットキーの解除
 ホットキーはグローバルなものなので、アプリケーションが終了するときには解除しなければなりません。解除にはUnregisterHotKey()というAPIを使います。


void CMainFrame::UnregHotKey()
{
	UINT	uiID = VK_F10;
	UINT	uiMod = MOD_CONTROL | MOD_SHIFT;
	::UnregisterHotKey( m_hWnd, (int)MAKEWORD( uiID, uiMod ) );
}
	

 第1引数と第2引数は::RegisterHotKey()と同じです。ってゆーか同じウィンドウハンドルとインデックスのキーを解除します。

メッセージの受け取り
 登録したキーを押したとき、ウィンドウにWM_HOTKEYが送られてきます。ところが、MFCクラスウィザードは、なぜかこのメッセージに対するハンドラ関数を作製してくれません。なぜかMFCはこのメッセージをサポートしていないのです。
 そこでCWnd::WindowProc()を使用して、このメッセージを拾います。送られてくるウィンドウのクラスに、クラスウィザードを使って仮想関数のWindowProc()を追加します。そして、その中でWM_HOTKEYを拾うようにします。



LRESULT CMainFrame::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
	switch( message )
	{
	case WM_HOTKEY:	//ホットキーを押しました。
		TRACE( "%d, %d, %d\n", wParam, LOWORD( lParam ), HIWORD( lParam ) );
		return 1;
	}	

	return CFrameWnd::WindowProc(message, wParam, lParam);
}
	

 このメッセージが送られてきたとき、 wParamにはホットキーのインデックスが、lParamの下位WORDには特殊キーのコードが、lParamの上位WORDにはキーコードが入っています。複数のホットキーを同時に使用する場合には、送られてくるインデックスで動作を決定することになります。

「ホットキー」というユーザーインターフェイス
 さて、ここまでは「ホットキーそのもの」について見てきました。単純にホットキーを機能させるにはここまでで十分です。しかし、実際にはまだ不十分と言えるでしょう。

 ホットキーはグローバルなものですから、どのような状況で呼び出されるのかアプリケーション側では知ることができません。そして、アプリケーション側で決めつけてはいけません。アプリケーション側は「CTRL+H」で機能するようにしていても、ユーザーは十中八九変えたがります。他の多くのアプリケーションがその中でしか使わないショートカットキーを自由に変更できるのであれば、そういった多くのアプリケーションの設定を邪魔するようなホットキーは嫌われること請け合いです。
 そこで、ホットキーはユーザーで好きなキーを設定できるようにする必要があります。設定したいキーを受け取り、そのキーをホットキーとして登録するわけです。
 この「指定されたキー」を受け取るためのコントロールが「ホットキーコントロール」です。

ホットキーコントロール
 ダイアログに貼り付けられるコントロールのひとつに「ホットキーコントロール」があります。エディットボックスに似たこのコントロールは、押したキーを表示するという珍しいコントロールです。例えばCTRLキーとAを同時に押したときには、ホットキーコントロールには「CTRL + A」と表示されます。
 これはまさにホットキーに使うしかないコントロールです。このコントロールを使用してホットキーを取得すれば、ユーザーの好きなキーで設定できるというわけです。

コントロールへのキーのセット&取得
 まず最初にひとつ。
 ダイアログを表示する前に、すでに登録してあるホットキーを削除しておきましょう。しておかないとどうなるのか? そのキーをコントロールに入力しようとすると、ホットキーが優先して働いてしまうのです(笑)。これを忘れないように。

 さて、ホットキーコントロールは「変数」としては扱えないので、CHotKeyCtrl型のメンバ変数として操作することになります。クラスウィザードを使用して、コントロールに関連づけたメンバ変数を作製してください。
 コントロールにキーをセットするにはCHotKeyCtrl::SetHotKey()を使用します。この関数は、他のコントロールのメンバ関数と同じくコントロールのウィンドウにメッセージを送るので、コントロールがすでに作製されていなければなりません。そのため、この関数を使うのはWM_INITDIALOGのメッセージハンドラCDialog::OnInitDialog()内で行う必要があります。
 ということは、まずダイアログ側にキーコードの一時保管のためのメンバ変数(メンバ関数の引数を考えると、WORD型がいいでしょう)を作製しておいて、親ウィンドウ側からすでに登録されているキーコードをこの変数に格納し、それをダイアログが表示される直前CDialog::OnInitDialog()内でコントロールに登録するという手順を踏む必要があります。

 ここでさらに問題が出てきます。CHotKeyCtrl::SetHotKey()の第1引数には通常のキーコード、第2引数には特殊キーコードを入れます。通常のキーコードはRegisterHotKey()と同じなのですが、特殊キーコードはまるっきり違います。キーフラグを判別して、値を変換しなければなりません。
 この変換は次のような関数で行えます。


////////////////////////////////////////////////////////////////////
// キーコードを入れ替えます。
// WORDをCHotKeyCtrl::GetHotKey()で、
// UINTを::RegisterHotKey()で使用します。

DllExport void KCL_API KFKeyChange( WORD &p_rwKey, UINT p_uiKey )
{
	p_rwKey = 0;

	if( p_uiKey & MOD_SHIFT )	//シフトキーが押されてるか。
		p_rwKey |= HOTKEYF_SHIFT;

	if( p_uiKey & MOD_CONTROL )	//コントロールキーが押されてるか。
		p_rwKey |= HOTKEYF_CONTROL;

	if( p_uiKey & MOD_ALT )	//グラフキーが押されてるか。
		p_rwKey |= HOTKEYF_ALT;
}
	

 見ての通り、この関数はKCL9542に含める予定のものですが、まだ次期リリース時期が決まっていないので、コードを書き換えて自分のアプリケーションで使っちゃってください。当然変更される可能性もあります(ホットキーを扱うクラスの作製も考えていますし)。
 それと、これは「ホットキー>ホットキーコントロール」への変換なので、取得するときには逆の変換をしなければならないということでそっちの関数も。


DllExport void KCL_API KFKeyChange( UINT &p_ruiKey, WORD p_wKey )
{
	p_ruiKey = 0;

	if( p_wKey & HOTKEYF_SHIFT )	//シフトキーが押されてるか。
		p_ruiKey |= MOD_SHIFT;

	if( p_wKey & HOTKEYF_CONTROL )	//コントロールキーが押されてるか。
		p_ruiKey |= MOD_CONTROL;

	if( p_wKey & HOTKEYF_ALT )	//グラフキーが押されてるか。
		p_ruiKey |= MOD_ALT;
}
	

 あ、こういう関数の設計方法はいい例じゃないかもしれない……。
 まぁそれはともかく、そのホットキーコントロールからの値の取得にはCHotKeyCtrl::GetHotKey()を使用します。ほとんどCHotKeyCtrl::SetHotKey()と同じなので細かい説明はいいでしょう。あ、こちらも、ダイアログが削除されちゃうとコントロールそのものがなくなってデータが取得できないので、WM_DESTROYのメッセージハンドラCWnd::OnDestroy()でこの関数を使用してください。

 あと、ついでにCHotKeyCtrl::SetRules()についても説明しておきましょう。これは次のように使用します。


	m_cHKCtrl.SetRules( HKCOMB_NONE, HOTKEYF_CONTROL );
	

 第1引数は「無効なキー」を指定します。これは何かというと、例えばHKCOMB_Cを指定すると、CTRLキーを押しながらのキーは、コントロールに表示されなくなり、代わりに第2引数に指定したキーが同時に押されたように表示されます。上の例では「何も特殊キーを押してない状態」を無効状態としています。
 第2引数は今言ったように「代わりに表示する特殊キー」で、上の例ではCTRLキーが同時に押されたかのように表示されます。
 いい例が「ショートカット」の設定で、ただ普通のキーを入力すると、CTRLキーとALTキーが同時に押されたときのように表示されます。このように入力して欲しいキーに制限を加えたいときにこの関数を使用してください。

まとめ
 ホットキーはシステムグローバルな機能のため、やはり諸刃の剣と言えるでしょう。とはいえ、迷惑を掛けてもキーのひとつやふたつです、それほど気にする必要もなく、効果的に使えばアプリケーションの操作性が大きく増します。
 ただ問題なのは、ホットキーについてのドキュメントや例が少ないこと、なぜかホットキーコントロールとの連携がよくないことでしょう。今回の解説はちょっと例が少なかったと思いますが、少しは役に立ちましたか?

 
(C)KAB-studio 1998 ALL RIGHTS RESERVED.