「MFCの基礎 - キーボード」の版間の差分
編集の要約なし |
編集の要約なし |
||
| 1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
MFCアプリケーションにおけるキーボード処理は、Windowsのメッセージドリブン設計に基づいて実装される。<br> | |||
<br><br> | <br> | ||
キーボード入力は、WM_KEYDOWN、WM_KEYUP、WM_CHARなどのWindowsメッセージとして処理される。<br> | |||
MFCは、これらのメッセージを処理するための便利なメッセージハンドラ (OnKeyDown、OnKeyUp、OnChar等) を提供している。<br> | |||
<br> | |||
* キーボードメッセージの種類と処理フロー | |||
* 仮想キーコードと文字コードの違い | |||
* GetKeyState関数とGetAsyncKeyState関数によるキー状態の取得 | |||
* MFCのキーボードイベントハンドラ | |||
* アクセラレータキーとホットキーの登録 | |||
* キーボード入力のシミュレーション | |||
<br> | <br> | ||
仮想キーコードは、キーボードのキー自体を識別するためのコードであり、<code>VK_RETURN</code>、<code>VK_SHIFT</code> 等の定数で表現される。<br> | |||
一方、文字コードは、キー入力によって生成される文字を表すコードであり、<code>WM_CHAR</code> メッセージで取得できる。<br> | |||
<br> | <br> | ||
MFCのキーボード処理を理解することにより、以下に示す機能を実装できる。<br> | |||
<br> | <br> | ||
< | * ショートカットキーの実装 | ||
* ゲームや対話型アプリケーションのキー入力処理 | |||
* カスタムキーバインディング | |||
* キーボードマクロとオートメーション | |||
<br><br> | |||
== キーボードメッセージ == | |||
Windowsは、キーボード入力を複数のメッセージとして処理する。<br> | |||
<br> | <br> | ||
<center> | <center> | ||
{| class="wikitable | {| class="wikitable" | ||
|+ | |+ 主要なキーボードメッセージ | ||
|- | |- | ||
! | ! メッセージ !! 説明 | ||
! | |||
|- | |- | ||
| WM_KEYDOWN / WM_KEYUP || キーが押下された時と離された時に送信される。 | |||
|- | |- | ||
| | | WM_CHAR || 文字入力時に送信される。<br>(TranslateMessage関数による変換後) | ||
|- | |- | ||
| | | WM_SYSKEYDOWN / WM_SYSKEYUP || Altキーと組み合わせたシステムキー入力時に送信される。 | ||
|} | |||
</center> | |||
<br> | |||
キーボードメッセージの処理フローを理解することは、適切なキーボード処理の実装に不可欠である。<br> | |||
<br> | |||
==== WM_KEYDOWN / WM_KEYUP ==== | |||
* WM_KEYDOWNメッセージ | |||
*: キーが押下された時に送信される。 | |||
* WM_KEYUPメッセージ | |||
*: キーが離された時に送信される。 | |||
<br> | |||
<u>※注意</u><br> | |||
<u>WM_KEYDOWN と WM_KEYUP は、文字入力ではなく、キーの物理的な押下と解放を検出する場合に使用する。</u><br> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ メッセージパラメータ | |||
|- | |- | ||
! パラメータ !! 説明 | |||
|- | |- | ||
| | | wParam || 仮想キーコード (VK_RETURN、VK_ESCAPE等) | ||
|- | |- | ||
| | | lParam || キーの詳細情報 (リピートカウント、スキャンコード、拡張キーフラグ等) | ||
|} | |||
</center> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ lParamの構造 | |||
|- | |- | ||
! | ! ビット !! 説明 | ||
|- | |- | ||
| | | 0〜15 || リピートカウント | ||
|- | |- | ||
| | | 16〜23 || スキャンコード | ||
|- | |- | ||
| | | 24 || 拡張キーフラグ (1の場合、拡張キー) | ||
|- | |- | ||
| | | 29 || コンテキストコード (Altキーが押されている場合は1) | ||
|- | |- | ||
| | | 30 || 前のキー状態 (既に押されていた場合は1) | ||
|- | |- | ||
| | | 31 || 遷移状態 (WM_KEYUPの場合は1) | ||
|} | |||
</center> | |||
<br> | |||
==== WM_CHAR ==== | |||
* WM_CHARメッセージ | |||
*: 文字入力時に送信される。 | |||
<br> | |||
WM_CHARメッセージは、<code>TranslateMessage</code> 関数により <code>WM_KEYDOWN</code> メッセージから生成される。<br> | |||
<code>TranslateMessage</code> 関数は、仮想キーコードを文字コードに変換する。<br> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ メッセージパラメータ | |||
|- | |- | ||
! | ! パラメータ !! 説明 | ||
|- | |- | ||
| | | wParam || 文字コード (ASCII文字やUnicode文字) | ||
|- | |- | ||
| | | lParam || キーの詳細情報 (WM_KEYDOWNと同様) | ||
|} | |||
</center> | |||
<br> | |||
WM_CHARメッセージは、テキスト入力を処理する場合に使用する。<br> | |||
例えば、エディットコントロールやカスタムテキストエディタの実装に利用する。<br> | |||
<br> | |||
また、WM_CHARメッセージは、Shift、Ctrl、Altキーの状態を考慮した文字コードを提供する。<br> | |||
例えば、[Shift]キー + [A]キーを同時押下する時、wParamは"A"の文字コードとなる。<br> | |||
<br> | |||
==== WM_SYSKEYDOWN / WM_SYSKEYUP ==== | |||
WM_SYSKEYDOWNメッセージ と WM_SYSKEYUPメッセージは、[Alt]キーと組み合わせたキー入力時に送信される。<br> | |||
<br> | |||
これらのメッセージは、システムキーの処理に使用される。<br> | |||
システムキーには、[Alt] + [F4] (ウィンドウを閉じる)、[Alt] + [Tab] (タスク切り替え) 等が含まれる。<br> | |||
<br> | |||
メッセージパラメータは、WM_KEYDOWN と WM_KEYUPと同様である。<br> | |||
<br> | |||
WM_SYSKEYDOWN と WM_SYSKEYUPを処理する場合、通常は <code>DefWindowProc</code> 関数に渡して、デフォルトのシステム動作を維持する。<br> | |||
カスタム処理が必要な場合のみ、これらのメッセージを独自に処理する。<br> | |||
<br> | |||
==== メッセージの処理フロー ==== | |||
キーボードメッセージの処理フローを以下に示す。<br> | |||
<br> | |||
# ユーザがキーを押下する。 | |||
# Windowsは、WM_KEYDOWNメッセージを生成する。 | |||
# アプリケーションのメッセージループは、TranslateMessage関数を呼び出す。 | |||
# TranslateMessage関数は、WM_KEYDOWNからWM_CHARメッセージを生成する。(文字キーの場合) | |||
# DispatchMessage関数は、メッセージをウィンドウプロシージャに送信する。 | |||
# ウィンドウプロシージャは、メッセージハンドラを呼び出す。 | |||
<br> | |||
この処理フローを理解することにより、適切なメッセージハンドラを選択できる。<br> | |||
<br> | |||
文字入力を処理する場合は、WM_CHARメッセージを使用する。<br> | |||
キーの物理的な押下を検出する場合は、WM_KEYDOWNメッセージを使用する。<br> | |||
<br><br> | |||
== 仮想キーの取得 == | |||
<code>GetKeyState</code> 関数は、特定の仮想キーの状態を取得する関数である。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
SHORT GetKeyState(int nVirtKey); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) と 戻り値 | |||
|- | |- | ||
! 種別 !! 項目 !! 説明 | |||
|- | |- | ||
| | | 引数 (パラメータ) || nVirtKey || 仮想キーコード (VK_SHIFT、VK_CONTROL等) | ||
|- | |- | ||
| | | rowspan="2" | 戻り値 || 最上位ビット (ビット15) || キーが押されている場合は1、それ以外は0 | ||
|- | |- | ||
| | | 最下位ビット (ビット0) || キーのトグル状態 (CapsLockやNumLockの場合、ONの時は1) | ||
|} | |||
</center> | |||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
// Shiftキーが押されているか確認 | |||
if (GetKeyState(VK_SHIFT) & 0x8000) { | |||
// [Shift]キーが押下されている場合 | |||
} | |||
// CapsLockがONか確認 | |||
if (GetKeyState(VK_CAPITAL) & 0x0001) { | |||
// [CapsLock]がONの状態 | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
下表に、主要な仮想キー定数を示す。<br> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 主要な仮想キー定数 | |||
! 定数 !! 説明 | |||
|- | |- | ||
| VK_SHIFT || Shiftキー | |||
|- | |- | ||
| | | VK_CONTROL || Ctrlキー | ||
|- | |- | ||
| | | VK_MENU || Altキー | ||
|- | |- | ||
| | | VK_LSHIFT || 左Shiftキー | ||
|- | |- | ||
| | | VK_RSHIFT || 右Shiftキー | ||
|- | |- | ||
| | | VK_LCONTROL || 左Ctrlキー | ||
|- | |- | ||
| | | VK_RCONTROL || 右Ctrlキー | ||
|- | |- | ||
| | | VK_LMENU || 左Altキー | ||
|- | |- | ||
| | | VK_RMENU || 右Altキー | ||
|} | |||
</center> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 矢印キーと編集キー | |||
! 定数 !! 説明 | |||
|- | |- | ||
| | | VK_LEFT || 左矢印キー | ||
|- | |- | ||
| | | VK_UP || 上矢印キー | ||
|- | |- | ||
| VK_RIGHT || 右矢印キー | |||
|- | |- | ||
| | | VK_DOWN || 下矢印キー | ||
|- | |- | ||
| | | VK_INSERT || Insertキー | ||
|- | |- | ||
| | | VK_DELETE || Deleteキー | ||
|- | |- | ||
| | | VK_HOME || Homeキー | ||
|- | |- | ||
| | | VK_END || Endキー | ||
|- | |- | ||
| | | VK_PRIOR || Page Upキー | ||
|- | |- | ||
| | | VK_NEXT || Page Downキー | ||
|} | |||
</center> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 特殊キー | |||
! 定数 !! 説明 | |||
|- | |- | ||
| | | VK_RETURN || Enterキー | ||
|- | |- | ||
| | | VK_ESCAPE || Escapeキー | ||
|- | |- | ||
| | | VK_SPACE || Spaceキー | ||
|- | |- | ||
| | | VK_TAB || Tabキー | ||
|- | |- | ||
| | | VK_BACK || Backspaceキー | ||
|- | |- | ||
| | | VK_CAPITAL || CapsLockキー | ||
|- | |- | ||
| | | VK_NUMLOCK || NumLockキー | ||
|- | |- | ||
| | | VK_SCROLL || ScrollLockキー | ||
|} | |||
</center> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ ファンクションキー | |||
! 定数 !! 説明 | |||
|- | |- | ||
| | | VK_F1 || F1キー | ||
|- | |- | ||
| VK_F2 || F2キー | |||
|- | |- | ||
| | | VK_F3 || F3キー | ||
|- | |- | ||
| | | VK_F4 || F4キー | ||
|- | |- | ||
| | | VK_F5 || F5キー | ||
|- | |- | ||
| | | VK_F6 || F6キー | ||
|- | |- | ||
| | | VK_F7 || F7キー | ||
|- | |- | ||
| | | VK_F8 || F8キー | ||
|- | |- | ||
| | | VK_F9 || F9キー | ||
|- | |- | ||
| | | VK_F10 || F10キー | ||
|- | |- | ||
| | | VK_F11 || F11キー | ||
|- | |- | ||
| | | VK_F12 || F12キー | ||
|} | |||
</center> | |||
<br> | |||
<code>GetKeyState</code> 関数は、現在のスレッドのメッセージキュー内の最後のメッセージの時点でのキー状態を返す。<br> | |||
リアルタイムのキー状態を取得する場合は、<code>GetAsyncKeyState</code> 関数を使用する。<br> | |||
<br><br> | |||
== GetAsyncKeyState関数 == | |||
<code>GetAsyncKeyState</code> 関数は、リアルタイムのキー状態を取得する関数である。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
SHORT GetAsyncKeyState(int vKey); | |||
</syntaxhighlight> | |||
<br> | |||
パラメータと戻り値は、<code>GetKeyState</code> 関数と同様である。<br> | |||
<br> | |||
==== GetKeyState関数との違い ==== | |||
GetKeyState関数とGetAsyncKeyState関数の違いを以下に示す。<br> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ GetKeyState関数 と GetAsyncKeyState関数 の比較 | |||
! 項目 !! GetKeyState関数 !! GetAsyncKeyState関数 | |||
|- | |- | ||
| | | 取得タイミング || メッセージキュー内の最後のメッセージ時点 || 関数呼び出し時点 (リアルタイム) | ||
|- | |- | ||
| 用途 || メッセージハンドラ内での使用 || ゲームループや非同期処理での使用 | |||
|- | |- | ||
| | | 同期性 || 同期的 (メッセージキューと同期) || 非同期的 (現在の物理的なキー状態) | ||
|- | |- | ||
| | | マルチスレッド || スレッドごとに独立 || システム全体で共有 | ||
|} | |||
</center> | |||
<br> | |||
* GetKeyState関数 | |||
*: メッセージドリブンな処理に適している。 | |||
*: メッセージハンドラ内で使用することにより、メッセージが生成された時点のキー状態を正確に取得できる。 | |||
*: <br> | |||
* GetAsyncKeyState関数 | |||
*: リアルタイムなキー状態の検出に適している。 | |||
*: ゲームのメインループや、定期的なタイマー処理でキー状態をポーリングする場合に使用する。 | |||
<br> | |||
==== 使用例 ==== | |||
GetAsyncKeyState関数の使用例を以下に示す。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// ゲームループ内でのキー状態チェック | |||
void CGameView::OnTimer(UINT_PTR nIDEvent) | |||
{ | |||
// 矢印キーで移動制御 | |||
if (GetAsyncKeyState(VK_LEFT) & 0x8000) { | |||
m_playerX -= 5; // 左に移動 | |||
} | |||
if (GetAsyncKeyState(VK_RIGHT) & 0x8000) { | |||
m_playerX += 5; // 右に移動 | |||
} | |||
if (GetAsyncKeyState(VK_UP) & 0x8000) { | |||
m_playerY -= 5; // 上に移動 | |||
} | |||
if (GetAsyncKeyState(VK_DOWN) & 0x8000) { | |||
m_playerY += 5; // 下に移動 | |||
} | |||
// [Space]キーでジャンプ | |||
if (GetAsyncKeyState(VK_SPACE) & 0x8000) { | |||
Jump(); | |||
} | |||
Invalidate(); // 再描画 | |||
CView::OnTimer(nIDEvent); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
この例では、タイマーイベント内で定期的にキー状態をチェックし、プレイヤーの位置を更新している。<br> | |||
<br> | |||
GetAsyncKeyState関数は、複数のキーの同時押しを検出する場合にも便利である。<br> | |||
<syntaxhighlight lang="c++"> | |||
// [Ctrl] + [S]キーの同時押下を検出 | |||
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) && (GetAsyncKeyState('S') & 0x8000)) { | |||
SaveDocument(); | |||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== MFCでのキーボードイベントハンドラ == | |||
MFCは、キーボードメッセージを処理するための便利なメッセージハンドラを提供している。<br> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 主要なキーボードイベントハンドラ | |||
|- | |- | ||
! メソッド !! 説明 | |||
|- | |- | ||
| | | OnKeyDown / OnKeyUp || WM_KEYDOWN / WM_KEYUPメッセージの処理 | ||
|- | |- | ||
| | | OnChar || WM_CHARメッセージの処理 | ||
|- | |- | ||
| | | PreTranslateMessage || メッセージの前処理 (メッセージがDispatchされる前に介入) | ||
|} | |||
</center> | |||
<br> | |||
これらのハンドラを適切に使用することにより、柔軟なキーボード処理を実装できる。<br> | |||
<br> | |||
==== OnKeyDown / OnKeyUp ==== | |||
<code>OnKeyDown</code> イベントハンドラ と <code>OnKeyUp</code> イベントハンドラ は、キーの押下と解放を処理するハンドラである。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); | |||
afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) | |||
|- | |- | ||
! 項目 !! 説明 | |||
|- | |- | ||
| | | nChar || 仮想キーコード | ||
|- | |- | ||
| | | nRepCnt || リピートカウント (キーが押下され続けている場合の繰り返し回数) | ||
|- | |- | ||
| | | nFlags || キーの詳細情報 (拡張キーフラグ、コンテキストコード等) | ||
|} | |||
</center> | |||
<br> | |||
OnKeyDownハンドラを使用することにより、特定のキーに対する処理を簡潔に実装できる。<br> | |||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
void CMyView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) | |||
{ | |||
switch (nChar) | |||
{ | |||
case VK_LEFT: | |||
// [左矢印]キーが押下された場合 | |||
MoveLeft(); | |||
break; | |||
case VK_RIGHT: | |||
// [右矢印]キーが押下された場合 | |||
MoveRight(); | |||
break; | |||
case VK_ESCAPE: | |||
// [Esc]キーが押下された場合 | |||
CancelOperation(); | |||
break; | |||
case VK_F1: | |||
// [F1]キーが押下された場合 (ヘルプ表示) | |||
ShowHelp(); | |||
break; | |||
default: | |||
CView::OnKeyDown(nChar, nRepCnt, nFlags); | |||
break; | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== OnChar ==== | |||
<code>OnChar</code> イベントハンドラは、文字入力を処理するハンドラである。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ パラメータ | |||
|- | |- | ||
! 項目 !! 説明 | |||
|- | |- | ||
| | | nChar || 文字コード (ASCII文字やUnicode文字) | ||
|- | |- | ||
| | | nRepCnt || リピートカウント | ||
|- | |- | ||
| | | nFlags || キーの詳細情報 | ||
|} | |||
</center> | |||
<br> | |||
OnCharハンドラは、テキスト入力を処理する場合に使用する。<br> | |||
Shift、Ctrl、Altキーの状態を考慮した文字コードが提供されるため、文字入力処理が簡潔になる。<br> | |||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
void CMyView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) | |||
{ | |||
if (nChar == VK_RETURN) { | |||
// [Enter]キーが押下された場合 | |||
ProcessInput(); | |||
} | |||
else if (nChar == VK_ESCAPE) { | |||
// [Esc]キーが押下された場合 | |||
CancelInput(); | |||
} | |||
else if (nChar >= 32) { | |||
// 印字可能文字の場合 | |||
// 文字を入力バッファに追加 | |||
m_inputBuffer += (TCHAR)nChar; | |||
Invalidate(); | |||
} | |||
CView::OnChar(nChar, nRepCnt, nFlags); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== PreTranslateMessageメソッド ==== | |||
<code>PreTranslateMessage</code> メソッドは、メッセージがディスパッチされる前に呼び出されるメソッドである。<br> | |||
このメソッドをオーバーライドすることにより、メッセージの前処理やフィルタリングを実装できる。<br> | |||
<br> | |||
ディスパッチ (Dispatch) とは、メッセージを適切なウィンドウプロシージャに転送することを意味する。<br> | |||
Windowsのメッセージループでは、一般的に以下に示す流れで処理される。<br> | |||
# GetMessage関数 | |||
#: メッセージキューからメッセージを取得する。 | |||
# TranslateMessage関数 | |||
#: キーボードメッセージを文字メッセージに変換する。 | |||
# DispatchMessage関数 | |||
#: メッセージを対象ウィンドウのウィンドウプロシージャ (WndProc) に転送する。 | |||
<br> | |||
<u>PreTranslateMessageメソッドは、このDispatchMessageが呼ばれる前の段階で介入できるメソッドである。</u><br> | |||
<u>このメソッドがTRUEを返す場合、メッセージはウィンドウプロシージャに転送されずに、破棄または独自処理される。</u><br> | |||
<u>これにより、特定のキー入力を横取りしてショートカットキーとして処理する等の実装が可能になる。</u><br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
virtual BOOL PreTranslateMessage(MSG* pMsg); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) と 戻り値 | |||
|- | |- | ||
! 種別 !! 項目 !! 説明 | |||
|- | |- | ||
| | | パラメータ || pMsg || メッセージ構造体へのポインタ | ||
|- | |- | ||
| rowspan="2" | 戻り値 || TRUE || メッセージを処理済み (DispatchMessageに渡さない) | |||
|- | |- | ||
|VK_F1 || [ | | FALSE || メッセージを通常通り処理 (DispatchMessageに渡す) | ||
|} | |||
</center> | |||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
BOOL CMyDialog::PreTranslateMessage(MSG* pMsg) | |||
{ | |||
if (pMsg->message == WM_KEYDOWN) { | |||
// [Enter]キーの処理を無効化 (ダイアログを閉じないようにする) | |||
if (pMsg->wParam == VK_RETURN) { | |||
// カスタム処理 | |||
OnEnterKey(); | |||
return TRUE; // メッセージを消費 | |||
} | |||
// [Esc]キーの処理を無効化 | |||
if (pMsg->wParam == VK_ESCAPE) { | |||
// カスタム処理 | |||
OnEscapeKey(); | |||
return TRUE; // メッセージを消費 | |||
} | |||
// [Tab]キーで次のコントロールに移動 (カスタム順序) | |||
if (pMsg->wParam == VK_TAB) { | |||
FocusNextControl(); | |||
return TRUE; // メッセージを消費 | |||
} | |||
} | |||
return CDialog::PreTranslateMessage(pMsg); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<u>PreTranslateMessageメソッドは、ダイアログのデフォルト動作を変更する場合に特に有用である。</u><br> | |||
<br> | |||
例えば、ダイアログでは、[Enter]キーを押下するとOKボタンが押下され、[Esc]キーを押下するとキャンセルボタンが押下されるデフォルト動作がある。<br> | |||
PreTranslateMessageメソッドをオーバーライドすることにより、この動作をカスタマイズできる。<br> | |||
<br><br> | |||
== アクセラレータキー == | |||
アクセラレータキー (ショートカットキー) は、メニューコマンドをキーボードから直接実行する機能である。<br> | |||
<br> | |||
MFCでは、リソースを使用した実装と、動的な実装の2つの方法がある。<br> | |||
<br> | |||
==== リソースを使用した実装 ==== | |||
リソースエディタを使用してアクセラレータテーブルを定義する方法を以下に示す。<br> | |||
<br> | |||
===== ステップ 1 : リソースファイルの編集 ===== | |||
リソースファイル (.rc) にアクセラレータテーブルを追加する。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
IDR_MAINFRAME ACCELERATORS | |||
BEGIN | |||
"N", ID_FILE_NEW, VIRTKEY, CONTROL // Ctrl+N | |||
"O", ID_FILE_OPEN, VIRTKEY, CONTROL // Ctrl+O | |||
"S", ID_FILE_SAVE, VIRTKEY, CONTROL // Ctrl+S | |||
VK_F1, ID_HELP, VIRTKEY // F1 | |||
VK_F5, ID_VIEW_REFRESH, VIRTKEY // F5 | |||
"Q", ID_APP_EXIT, VIRTKEY, CONTROL // Ctrl+Q | |||
END | |||
</syntaxhighlight> | |||
<br> | |||
===== ステップ 2 : アクセラレータテーブルのロード ===== | |||
アプリケーションクラスでアクセラレータテーブルをロードする。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
BOOL CMyApp::InitInstance() | |||
{ | |||
// アクセラレータテーブルのロード | |||
m_hAccelTable = LoadAccelerators(AfxGetInstanceHandle(), | |||
MAKEINTRESOURCE(IDR_MAINFRAME)); | |||
// メインウィンドウの作成など | |||
// ... | |||
return TRUE; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
===== ステップ 3 : メッセージループでの処理 ===== | |||
メッセージループでTranslateAccelerator関数を呼び出す。<br> | |||
<br> | |||
MFCでは、CWinAppクラスが自動的にアクセラレータテーブルを処理するため、通常は追加のコードは不要である。<br> | |||
<br> | |||
===== ステップ 4 : コマンドハンドラの実装 ===== | |||
各コマンドIDに対するハンドラを実装する。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
void CMainFrame::OnFileNew() | |||
{ | |||
// [Ctrl] + [N]キーが押下された時の処理 | |||
AfxMessageBox(_T("新規ファイル作成")); | |||
} | |||
void CMainFrame::OnFileOpen() | |||
{ | |||
// [Ctrl] + [O]キーが押下された時の処理 | |||
CFileDialog dlg(TRUE); | |||
if (dlg.DoModal() == IDOK) { | |||
OpenFile(dlg.GetPathName()); | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 動的なアクセラレータ作成 ==== | |||
実行時に動的にアクセラレータテーブルを作成する方法を以下に示す。<br> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
void CMainFrame::CreateDynamicAccelerators() | |||
{ | |||
// アクセラレータテーブルの定義 | |||
ACCEL accel[3]; | |||
// [Ctrl] + [N]キー | |||
accel[0].fVirt = FCONTROL | FVIRTKEY; | |||
accel[0].key = 'N'; | |||
accel[0].cmd = ID_FILE_NEW; | |||
// [Ctrl] + [O]キー | |||
accel[1].fVirt = FCONTROL | FVIRTKEY; | |||
accel[1].key = 'O'; | |||
accel[1].cmd = ID_FILE_OPEN; | |||
// [F5]キー | |||
accel[2].fVirt = FVIRTKEY; | |||
accel[2].key = VK_F5; | |||
accel[2].cmd = ID_VIEW_REFRESH; | |||
// アクセラレータテーブルの作成 | |||
m_hAccelTable = CreateAcceleratorTable(accel, 3); | |||
// 既存のアクセラレータテーブルを破棄 | |||
if (m_hOldAccelTable != NULL) { | |||
DestroyAcceleratorTable(m_hOldAccelTable); | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
動的なアクセラレータ作成は、ユーザがキーバインディングをカスタマイズできる機能を実装する場合に有用である。<br> | |||
<br><br> | |||
== ホットキーの登録 == | |||
ホットキーは、アプリケーションがフォーカスを持っていない場合でも検出できるグローバルなキーボードショートカットである。<br> | |||
<br> | |||
<code>RegisterHotKey</code> 関数を使用して、システム全体で有効なホットキーを登録できる。<br> | |||
<br> | |||
==== RegisterHotKey関数 ==== | |||
RegisterHotKey関数は、ホットキーを登録する関数である。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
BOOL RegisterHotKey( | |||
HWND hWnd, | |||
int id, | |||
UINT fsModifiers, | |||
UINT vk | |||
); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) | |||
|- | |- | ||
! 項目 !! 説明 | |||
|- | |- | ||
| | | hWnd || ホットキーが押下された時にWM_HOTKEYメッセージを受け取るウィンドウのハンドル | ||
|- | |- | ||
| | | id || ホットキーの識別子 (0x0000〜0xBFFFの範囲) | ||
|- | |- | ||
| | | fsModifiers || 修飾キー (MOD_ALT、MOD_CONTROL、MOD_SHIFT、MOD_WIN) | ||
|- | |- | ||
| | | vk || 仮想キーコード | ||
|} | |||
</center> | |||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
// [Ctrl] + [Alt] + [F12]キーをホットキーとして登録 | |||
BOOL CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) | |||
{ | |||
if (CFrameWnd::OnCreate(lpCreateStruct) == -1) { | |||
return -1; | |||
} | |||
// ホットキーの登録 (ID: 1) | |||
if (!RegisterHotKey(m_hWnd, 1, MOD_CONTROL | MOD_ALT, VK_F12)) { | |||
AfxMessageBox(_T("ホットキーの登録に失敗しました")); | |||
} | |||
return 0; | |||
} | |||
// ホットキーの解除 | |||
void CMainFrame::OnDestroy() | |||
{ | |||
UnregisterHotKey(m_hWnd, 1); | |||
CFrameWnd::OnDestroy(); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== WM_HOTKEYメッセージの処理 ==== | |||
ホットキーが押下されると、WM_HOTKEYメッセージが送信される。<br> | |||
<br> | |||
メッセージハンドラの実装を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
// ヘッダファイルでメッセージマップを宣言 | |||
class CMainFrame : public CFrameWnd | |||
{ | |||
protected: | |||
afx_msg LRESULT OnHotKey(WPARAM wParam, LPARAM lParam); | |||
DECLARE_MESSAGE_MAP() | |||
}; | |||
// メッセージマップの定義 | |||
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) | |||
ON_WM_HOTKEY() | |||
END_MESSAGE_MAP() | |||
// ハンドラの実装 | |||
LRESULT CMainFrame::OnHotKey(WPARAM wParam, LPARAM lParam) | |||
{ | |||
int nHotKeyId = (int)wParam; | |||
UINT fsModifiers = (UINT)LOWORD(lParam); | |||
UINT vk = (UINT)HIWORD(lParam); | |||
if (nHotKeyId == 1) { | |||
// [Ctrl] + [Alt] + [F12]キーが押下された場合 | |||
AfxMessageBox(_T("ホットキーが押されました")); | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>ホットキーは、システム全体で有効なため、他のアプリケーションと競合しないように注意する必要がある。</u><br> | |||
<u>また、アプリケーション終了時には、必ずUnregisterHotKey関数でホットキーを解除する。</u><br> | |||
<br><br> | |||
== キーボード入力のシミュレーション == | |||
プログラムから仮想的にキーボード入力を生成する技術を、キーボード入力のシミュレーションという。<br> | |||
<br> | |||
Windowsでは、<code>SendInput</code> 関数 と <code>keybd_event</code> 関数の2つの方法がある。<br> | |||
<br> | |||
* SendInput関数 | |||
*: Windows 2000以降で推奨される方法である。 | |||
* keybd_event関数 | |||
*: レガシーな方法であり、互換性のために残されている。 | |||
<br> | |||
==== SendInput関数 ==== | |||
SendInput関数は、キーボードやマウスの入力イベントを合成する関数である。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
UINT SendInput( | |||
UINT nInputs, | |||
LPINPUT pInputs, | |||
int cbSize | |||
); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) | |||
|- | |- | ||
! 項目 !! 説明 | |||
|- | |- | ||
| | | nInputs || 入力イベントの数 | ||
|- | |- | ||
| | | pInputs || INPUT構造体の配列 | ||
|- | |- | ||
| | | cbSize || INPUT構造体のサイズ | ||
| | |} | ||
</center> | |||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
void SendKeyPress(WORD vkCode) | |||
{ | |||
INPUT inputs[2] = {}; | |||
// キー押下イベント | |||
inputs[0].type = INPUT_KEYBOARD; | |||
inputs[0].ki.wVk = vkCode; | |||
inputs[0].ki.dwFlags = 0; // キーダウン | |||
// キー解放イベント | |||
inputs[1].type = INPUT_KEYBOARD; | |||
inputs[1].ki.wVk = vkCode; | |||
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP; | |||
// 入力イベントの送信 | |||
UINT uSent = SendInput(2, inputs, sizeof(INPUT)); | |||
if (uSent != 2) { | |||
AfxMessageBox(_T("SendInputに失敗しました")); | |||
} | |||
} | |||
// 使用例 : [Enter]キーを送信 | |||
void CMyView::SimulateEnterKey() | |||
{ | |||
SendKeyPress(VK_RETURN); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
修飾キー付きのキー入力をシミュレートする例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
void SendCtrlC() | |||
{ | |||
INPUT inputs[4] = {}; | |||
// [Ctrl]キー押下 | |||
inputs[0].type = INPUT_KEYBOARD; | |||
inputs[0].ki.wVk = VK_CONTROL; | |||
inputs[0].ki.dwFlags = 0; | |||
// [C]キー押下 | |||
inputs[1].type = INPUT_KEYBOARD; | |||
inputs[1].ki.wVk = 'C'; | |||
| | inputs[1].ki.dwFlags = 0; | ||
// [C]キー解放 | |||
inputs[2].type = INPUT_KEYBOARD; | |||
inputs[2].ki.wVk = 'C'; | |||
inputs[2].ki.dwFlags = KEYEVENTF_KEYUP; | |||
// [Ctrl]キー解放 | |||
inputs[3].type = INPUT_KEYBOARD; | |||
inputs[3].ki.wVk = VK_CONTROL; | |||
inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; | |||
SendInput(4, inputs, sizeof(INPUT)); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
SendInput関数は、UIPI (User Interface Privilege Isolation) の制約を受ける。<br> | |||
低い整合性レベルのアプリケーションから、高い整合性レベルのアプリケーションに入力を送信することはできない。<br> | |||
<br> | |||
==== keybd_event関数 ==== | |||
keybd_event関数は、レガシーなキーボード入力シミュレーション関数である。<br> | |||
<br> | |||
関数のプロトタイプを以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
VOID keybd_event( | |||
BYTE bVk, | |||
BYTE bScan, | |||
DWORD dwFlags, | |||
ULONG_PTR dwExtraInfo | |||
); | |||
</syntaxhighlight> | |||
<br> | |||
<center> | |||
{| class="wikitable" | |||
|+ 引数 (パラメータ) | |||
|- | |- | ||
! 項目 !! 説明 | |||
|- | |- | ||
| | | bVk || 仮想キーコード | ||
|- | |- | ||
| bScan || スキャンコード<br>(通常は0) | |||
|- | |- | ||
| | | dwFlags || フラグ<br>(KEYEVENTF_KEYUP等) | ||
|- | |- | ||
| | | dwExtraInfo || 追加情報<br>(通常は0) | ||
|} | |} | ||
</center> | </center> | ||
<br> | |||
使用例を以下に示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
void SendKeyPressLegacy(BYTE vkCode) | |||
{ | |||
// キー押下 | |||
keybd_event(vkCode, 0, 0, 0); | |||
// キー解放 | |||
keybd_event(vkCode, 0, KEYEVENTF_KEYUP, 0); | |||
} | |||
// [Ctrl] + [V]キーの送信 | |||
void SendCtrlV() | |||
{ | |||
// [Ctrl]キー押下 | |||
keybd_event(VK_CONTROL, 0, 0, 0); | |||
// [V]キー押下 | |||
keybd_event('V', 0, 0, 0); | |||
// [V]キー解放 | |||
keybd_event('V', 0, KEYEVENTF_KEYUP, 0); | |||
// [Ctrl]キー解放 | |||
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>keybd_event関数は、Windows 2000以降では非推奨とされている。</u><br> | |||
<u>新規開発では、SendInput関数の使用を推奨する。</u><br> | |||
<br> | |||
キーボード入力のシミュレーションは、自動化ツール、テストツール、マクロツールなどの実装に利用される。<br> | |||
ただし、セキュリティ上の理由から、使用には注意が必要である。<br> | |||
<br><br> | <br><br> | ||
== 関連リンク == | |||
* [https://learn.microsoft.com/en-us/windows/win32/inputdev/keyboard-input Microsoft Docs - Keyboard Input] | |||
* [https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes Microsoft Docs - Virtual-Key Codes] | |||
* [https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput Microsoft Docs - SendInput function] | |||
<br><br> | |||
{{#seo: | |||
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki | |||
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,MFC,C++,Keyboard,キーボード,Windows,Programming,GetKeyState,GetAsyncKeyState,SendInput,Accelerator,HotKey,仮想キー,Virtual Key | |||
|description={{PAGENAME}} - MFC/C++におけるキーボード処理の包括的なガイド | |||
|image=/resources/assets/MochiuLogo_Single_Blue.png | |||
}} | |||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:MFC]] | [[カテゴリ:MFC]] | ||
2026年1月31日 (土) 20:04時点における最新版
概要
MFCアプリケーションにおけるキーボード処理は、Windowsのメッセージドリブン設計に基づいて実装される。
キーボード入力は、WM_KEYDOWN、WM_KEYUP、WM_CHARなどのWindowsメッセージとして処理される。
MFCは、これらのメッセージを処理するための便利なメッセージハンドラ (OnKeyDown、OnKeyUp、OnChar等) を提供している。
- キーボードメッセージの種類と処理フロー
- 仮想キーコードと文字コードの違い
- GetKeyState関数とGetAsyncKeyState関数によるキー状態の取得
- MFCのキーボードイベントハンドラ
- アクセラレータキーとホットキーの登録
- キーボード入力のシミュレーション
仮想キーコードは、キーボードのキー自体を識別するためのコードであり、VK_RETURN、VK_SHIFT 等の定数で表現される。
一方、文字コードは、キー入力によって生成される文字を表すコードであり、WM_CHAR メッセージで取得できる。
MFCのキーボード処理を理解することにより、以下に示す機能を実装できる。
- ショートカットキーの実装
- ゲームや対話型アプリケーションのキー入力処理
- カスタムキーバインディング
- キーボードマクロとオートメーション
キーボードメッセージ
Windowsは、キーボード入力を複数のメッセージとして処理する。
| メッセージ | 説明 |
|---|---|
| WM_KEYDOWN / WM_KEYUP | キーが押下された時と離された時に送信される。 |
| WM_CHAR | 文字入力時に送信される。 (TranslateMessage関数による変換後) |
| WM_SYSKEYDOWN / WM_SYSKEYUP | Altキーと組み合わせたシステムキー入力時に送信される。 |
キーボードメッセージの処理フローを理解することは、適切なキーボード処理の実装に不可欠である。
WM_KEYDOWN / WM_KEYUP
- WM_KEYDOWNメッセージ
- キーが押下された時に送信される。
- WM_KEYUPメッセージ
- キーが離された時に送信される。
※注意
WM_KEYDOWN と WM_KEYUP は、文字入力ではなく、キーの物理的な押下と解放を検出する場合に使用する。
| パラメータ | 説明 |
|---|---|
| wParam | 仮想キーコード (VK_RETURN、VK_ESCAPE等) |
| lParam | キーの詳細情報 (リピートカウント、スキャンコード、拡張キーフラグ等) |
| ビット | 説明 |
|---|---|
| 0〜15 | リピートカウント |
| 16〜23 | スキャンコード |
| 24 | 拡張キーフラグ (1の場合、拡張キー) |
| 29 | コンテキストコード (Altキーが押されている場合は1) |
| 30 | 前のキー状態 (既に押されていた場合は1) |
| 31 | 遷移状態 (WM_KEYUPの場合は1) |
WM_CHAR
- WM_CHARメッセージ
- 文字入力時に送信される。
WM_CHARメッセージは、TranslateMessage 関数により WM_KEYDOWN メッセージから生成される。
TranslateMessage 関数は、仮想キーコードを文字コードに変換する。
| パラメータ | 説明 |
|---|---|
| wParam | 文字コード (ASCII文字やUnicode文字) |
| lParam | キーの詳細情報 (WM_KEYDOWNと同様) |
WM_CHARメッセージは、テキスト入力を処理する場合に使用する。
例えば、エディットコントロールやカスタムテキストエディタの実装に利用する。
また、WM_CHARメッセージは、Shift、Ctrl、Altキーの状態を考慮した文字コードを提供する。
例えば、[Shift]キー + [A]キーを同時押下する時、wParamは"A"の文字コードとなる。
WM_SYSKEYDOWN / WM_SYSKEYUP
WM_SYSKEYDOWNメッセージ と WM_SYSKEYUPメッセージは、[Alt]キーと組み合わせたキー入力時に送信される。
これらのメッセージは、システムキーの処理に使用される。
システムキーには、[Alt] + [F4] (ウィンドウを閉じる)、[Alt] + [Tab] (タスク切り替え) 等が含まれる。
メッセージパラメータは、WM_KEYDOWN と WM_KEYUPと同様である。
WM_SYSKEYDOWN と WM_SYSKEYUPを処理する場合、通常は DefWindowProc 関数に渡して、デフォルトのシステム動作を維持する。
カスタム処理が必要な場合のみ、これらのメッセージを独自に処理する。
メッセージの処理フロー
キーボードメッセージの処理フローを以下に示す。
- ユーザがキーを押下する。
- Windowsは、WM_KEYDOWNメッセージを生成する。
- アプリケーションのメッセージループは、TranslateMessage関数を呼び出す。
- TranslateMessage関数は、WM_KEYDOWNからWM_CHARメッセージを生成する。(文字キーの場合)
- DispatchMessage関数は、メッセージをウィンドウプロシージャに送信する。
- ウィンドウプロシージャは、メッセージハンドラを呼び出す。
この処理フローを理解することにより、適切なメッセージハンドラを選択できる。
文字入力を処理する場合は、WM_CHARメッセージを使用する。
キーの物理的な押下を検出する場合は、WM_KEYDOWNメッセージを使用する。
仮想キーの取得
GetKeyState 関数は、特定の仮想キーの状態を取得する関数である。
関数のプロトタイプを以下に示す。
SHORT GetKeyState(int nVirtKey);
| 種別 | 項目 | 説明 |
|---|---|---|
| 引数 (パラメータ) | nVirtKey | 仮想キーコード (VK_SHIFT、VK_CONTROL等) |
| 戻り値 | 最上位ビット (ビット15) | キーが押されている場合は1、それ以外は0 |
| 最下位ビット (ビット0) | キーのトグル状態 (CapsLockやNumLockの場合、ONの時は1) |
使用例を以下に示す。
// Shiftキーが押されているか確認
if (GetKeyState(VK_SHIFT) & 0x8000) {
// [Shift]キーが押下されている場合
}
// CapsLockがONか確認
if (GetKeyState(VK_CAPITAL) & 0x0001) {
// [CapsLock]がONの状態
}
下表に、主要な仮想キー定数を示す。
| 定数 | 説明 |
|---|---|
| VK_SHIFT | Shiftキー |
| VK_CONTROL | Ctrlキー |
| VK_MENU | Altキー |
| VK_LSHIFT | 左Shiftキー |
| VK_RSHIFT | 右Shiftキー |
| VK_LCONTROL | 左Ctrlキー |
| VK_RCONTROL | 右Ctrlキー |
| VK_LMENU | 左Altキー |
| VK_RMENU | 右Altキー |
| 定数 | 説明 |
|---|---|
| VK_LEFT | 左矢印キー |
| VK_UP | 上矢印キー |
| VK_RIGHT | 右矢印キー |
| VK_DOWN | 下矢印キー |
| VK_INSERT | Insertキー |
| VK_DELETE | Deleteキー |
| VK_HOME | Homeキー |
| VK_END | Endキー |
| VK_PRIOR | Page Upキー |
| VK_NEXT | Page Downキー |
| 定数 | 説明 |
|---|---|
| VK_RETURN | Enterキー |
| VK_ESCAPE | Escapeキー |
| VK_SPACE | Spaceキー |
| VK_TAB | Tabキー |
| VK_BACK | Backspaceキー |
| VK_CAPITAL | CapsLockキー |
| VK_NUMLOCK | NumLockキー |
| VK_SCROLL | ScrollLockキー |
| 定数 | 説明 |
|---|---|
| VK_F1 | F1キー |
| VK_F2 | F2キー |
| VK_F3 | F3キー |
| VK_F4 | F4キー |
| VK_F5 | F5キー |
| VK_F6 | F6キー |
| VK_F7 | F7キー |
| VK_F8 | F8キー |
| VK_F9 | F9キー |
| VK_F10 | F10キー |
| VK_F11 | F11キー |
| VK_F12 | F12キー |
GetKeyState 関数は、現在のスレッドのメッセージキュー内の最後のメッセージの時点でのキー状態を返す。
リアルタイムのキー状態を取得する場合は、GetAsyncKeyState 関数を使用する。
GetAsyncKeyState関数
GetAsyncKeyState 関数は、リアルタイムのキー状態を取得する関数である。
関数のプロトタイプを以下に示す。
SHORT GetAsyncKeyState(int vKey);
パラメータと戻り値は、GetKeyState 関数と同様である。
GetKeyState関数との違い
GetKeyState関数とGetAsyncKeyState関数の違いを以下に示す。
| 項目 | GetKeyState関数 | GetAsyncKeyState関数 |
|---|---|---|
| 取得タイミング | メッセージキュー内の最後のメッセージ時点 | 関数呼び出し時点 (リアルタイム) |
| 用途 | メッセージハンドラ内での使用 | ゲームループや非同期処理での使用 |
| 同期性 | 同期的 (メッセージキューと同期) | 非同期的 (現在の物理的なキー状態) |
| マルチスレッド | スレッドごとに独立 | システム全体で共有 |
- GetKeyState関数
- メッセージドリブンな処理に適している。
- メッセージハンドラ内で使用することにより、メッセージが生成された時点のキー状態を正確に取得できる。
- GetAsyncKeyState関数
- リアルタイムなキー状態の検出に適している。
- ゲームのメインループや、定期的なタイマー処理でキー状態をポーリングする場合に使用する。
使用例
GetAsyncKeyState関数の使用例を以下に示す。
// ゲームループ内でのキー状態チェック
void CGameView::OnTimer(UINT_PTR nIDEvent)
{
// 矢印キーで移動制御
if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
m_playerX -= 5; // 左に移動
}
if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
m_playerX += 5; // 右に移動
}
if (GetAsyncKeyState(VK_UP) & 0x8000) {
m_playerY -= 5; // 上に移動
}
if (GetAsyncKeyState(VK_DOWN) & 0x8000) {
m_playerY += 5; // 下に移動
}
// [Space]キーでジャンプ
if (GetAsyncKeyState(VK_SPACE) & 0x8000) {
Jump();
}
Invalidate(); // 再描画
CView::OnTimer(nIDEvent);
}
この例では、タイマーイベント内で定期的にキー状態をチェックし、プレイヤーの位置を更新している。
GetAsyncKeyState関数は、複数のキーの同時押しを検出する場合にも便利である。
// [Ctrl] + [S]キーの同時押下を検出
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) && (GetAsyncKeyState('S') & 0x8000)) {
SaveDocument();
}
MFCでのキーボードイベントハンドラ
MFCは、キーボードメッセージを処理するための便利なメッセージハンドラを提供している。
| メソッド | 説明 |
|---|---|
| OnKeyDown / OnKeyUp | WM_KEYDOWN / WM_KEYUPメッセージの処理 |
| OnChar | WM_CHARメッセージの処理 |
| PreTranslateMessage | メッセージの前処理 (メッセージがDispatchされる前に介入) |
これらのハンドラを適切に使用することにより、柔軟なキーボード処理を実装できる。
OnKeyDown / OnKeyUp
OnKeyDown イベントハンドラ と OnKeyUp イベントハンドラ は、キーの押下と解放を処理するハンドラである。
関数のプロトタイプを以下に示す。
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
| 項目 | 説明 |
|---|---|
| nChar | 仮想キーコード |
| nRepCnt | リピートカウント (キーが押下され続けている場合の繰り返し回数) |
| nFlags | キーの詳細情報 (拡張キーフラグ、コンテキストコード等) |
OnKeyDownハンドラを使用することにより、特定のキーに対する処理を簡潔に実装できる。
使用例を以下に示す。
void CMyView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch (nChar)
{
case VK_LEFT:
// [左矢印]キーが押下された場合
MoveLeft();
break;
case VK_RIGHT:
// [右矢印]キーが押下された場合
MoveRight();
break;
case VK_ESCAPE:
// [Esc]キーが押下された場合
CancelOperation();
break;
case VK_F1:
// [F1]キーが押下された場合 (ヘルプ表示)
ShowHelp();
break;
default:
CView::OnKeyDown(nChar, nRepCnt, nFlags);
break;
}
}
OnChar
OnChar イベントハンドラは、文字入力を処理するハンドラである。
関数のプロトタイプを以下に示す。
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
| 項目 | 説明 |
|---|---|
| nChar | 文字コード (ASCII文字やUnicode文字) |
| nRepCnt | リピートカウント |
| nFlags | キーの詳細情報 |
OnCharハンドラは、テキスト入力を処理する場合に使用する。
Shift、Ctrl、Altキーの状態を考慮した文字コードが提供されるため、文字入力処理が簡潔になる。
使用例を以下に示す。
void CMyView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == VK_RETURN) {
// [Enter]キーが押下された場合
ProcessInput();
}
else if (nChar == VK_ESCAPE) {
// [Esc]キーが押下された場合
CancelInput();
}
else if (nChar >= 32) {
// 印字可能文字の場合
// 文字を入力バッファに追加
m_inputBuffer += (TCHAR)nChar;
Invalidate();
}
CView::OnChar(nChar, nRepCnt, nFlags);
}
PreTranslateMessageメソッド
PreTranslateMessage メソッドは、メッセージがディスパッチされる前に呼び出されるメソッドである。
このメソッドをオーバーライドすることにより、メッセージの前処理やフィルタリングを実装できる。
ディスパッチ (Dispatch) とは、メッセージを適切なウィンドウプロシージャに転送することを意味する。
Windowsのメッセージループでは、一般的に以下に示す流れで処理される。
- GetMessage関数
- メッセージキューからメッセージを取得する。
- TranslateMessage関数
- キーボードメッセージを文字メッセージに変換する。
- DispatchMessage関数
- メッセージを対象ウィンドウのウィンドウプロシージャ (WndProc) に転送する。
PreTranslateMessageメソッドは、このDispatchMessageが呼ばれる前の段階で介入できるメソッドである。
このメソッドがTRUEを返す場合、メッセージはウィンドウプロシージャに転送されずに、破棄または独自処理される。
これにより、特定のキー入力を横取りしてショートカットキーとして処理する等の実装が可能になる。
関数のプロトタイプを以下に示す。
virtual BOOL PreTranslateMessage(MSG* pMsg);
| 種別 | 項目 | 説明 |
|---|---|---|
| パラメータ | pMsg | メッセージ構造体へのポインタ |
| 戻り値 | TRUE | メッセージを処理済み (DispatchMessageに渡さない) |
| FALSE | メッセージを通常通り処理 (DispatchMessageに渡す) |
使用例を以下に示す。
BOOL CMyDialog::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN) {
// [Enter]キーの処理を無効化 (ダイアログを閉じないようにする)
if (pMsg->wParam == VK_RETURN) {
// カスタム処理
OnEnterKey();
return TRUE; // メッセージを消費
}
// [Esc]キーの処理を無効化
if (pMsg->wParam == VK_ESCAPE) {
// カスタム処理
OnEscapeKey();
return TRUE; // メッセージを消費
}
// [Tab]キーで次のコントロールに移動 (カスタム順序)
if (pMsg->wParam == VK_TAB) {
FocusNextControl();
return TRUE; // メッセージを消費
}
}
return CDialog::PreTranslateMessage(pMsg);
}
PreTranslateMessageメソッドは、ダイアログのデフォルト動作を変更する場合に特に有用である。
例えば、ダイアログでは、[Enter]キーを押下するとOKボタンが押下され、[Esc]キーを押下するとキャンセルボタンが押下されるデフォルト動作がある。
PreTranslateMessageメソッドをオーバーライドすることにより、この動作をカスタマイズできる。
アクセラレータキー
アクセラレータキー (ショートカットキー) は、メニューコマンドをキーボードから直接実行する機能である。
MFCでは、リソースを使用した実装と、動的な実装の2つの方法がある。
リソースを使用した実装
リソースエディタを使用してアクセラレータテーブルを定義する方法を以下に示す。
ステップ 1 : リソースファイルの編集
リソースファイル (.rc) にアクセラレータテーブルを追加する。
IDR_MAINFRAME ACCELERATORS
BEGIN
"N", ID_FILE_NEW, VIRTKEY, CONTROL // Ctrl+N
"O", ID_FILE_OPEN, VIRTKEY, CONTROL // Ctrl+O
"S", ID_FILE_SAVE, VIRTKEY, CONTROL // Ctrl+S
VK_F1, ID_HELP, VIRTKEY // F1
VK_F5, ID_VIEW_REFRESH, VIRTKEY // F5
"Q", ID_APP_EXIT, VIRTKEY, CONTROL // Ctrl+Q
END
ステップ 2 : アクセラレータテーブルのロード
アプリケーションクラスでアクセラレータテーブルをロードする。
BOOL CMyApp::InitInstance()
{
// アクセラレータテーブルのロード
m_hAccelTable = LoadAccelerators(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDR_MAINFRAME));
// メインウィンドウの作成など
// ...
return TRUE;
}
ステップ 3 : メッセージループでの処理
メッセージループでTranslateAccelerator関数を呼び出す。
MFCでは、CWinAppクラスが自動的にアクセラレータテーブルを処理するため、通常は追加のコードは不要である。
ステップ 4 : コマンドハンドラの実装
各コマンドIDに対するハンドラを実装する。
void CMainFrame::OnFileNew()
{
// [Ctrl] + [N]キーが押下された時の処理
AfxMessageBox(_T("新規ファイル作成"));
}
void CMainFrame::OnFileOpen()
{
// [Ctrl] + [O]キーが押下された時の処理
CFileDialog dlg(TRUE);
if (dlg.DoModal() == IDOK) {
OpenFile(dlg.GetPathName());
}
}
動的なアクセラレータ作成
実行時に動的にアクセラレータテーブルを作成する方法を以下に示す。
void CMainFrame::CreateDynamicAccelerators()
{
// アクセラレータテーブルの定義
ACCEL accel[3];
// [Ctrl] + [N]キー
accel[0].fVirt = FCONTROL | FVIRTKEY;
accel[0].key = 'N';
accel[0].cmd = ID_FILE_NEW;
// [Ctrl] + [O]キー
accel[1].fVirt = FCONTROL | FVIRTKEY;
accel[1].key = 'O';
accel[1].cmd = ID_FILE_OPEN;
// [F5]キー
accel[2].fVirt = FVIRTKEY;
accel[2].key = VK_F5;
accel[2].cmd = ID_VIEW_REFRESH;
// アクセラレータテーブルの作成
m_hAccelTable = CreateAcceleratorTable(accel, 3);
// 既存のアクセラレータテーブルを破棄
if (m_hOldAccelTable != NULL) {
DestroyAcceleratorTable(m_hOldAccelTable);
}
}
動的なアクセラレータ作成は、ユーザがキーバインディングをカスタマイズできる機能を実装する場合に有用である。
ホットキーの登録
ホットキーは、アプリケーションがフォーカスを持っていない場合でも検出できるグローバルなキーボードショートカットである。
RegisterHotKey 関数を使用して、システム全体で有効なホットキーを登録できる。
RegisterHotKey関数
RegisterHotKey関数は、ホットキーを登録する関数である。
関数のプロトタイプを以下に示す。
BOOL RegisterHotKey(
HWND hWnd,
int id,
UINT fsModifiers,
UINT vk
);
| 項目 | 説明 |
|---|---|
| hWnd | ホットキーが押下された時にWM_HOTKEYメッセージを受け取るウィンドウのハンドル |
| id | ホットキーの識別子 (0x0000〜0xBFFFの範囲) |
| fsModifiers | 修飾キー (MOD_ALT、MOD_CONTROL、MOD_SHIFT、MOD_WIN) |
| vk | 仮想キーコード |
使用例を以下に示す。
// [Ctrl] + [Alt] + [F12]キーをホットキーとして登録
BOOL CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1) {
return -1;
}
// ホットキーの登録 (ID: 1)
if (!RegisterHotKey(m_hWnd, 1, MOD_CONTROL | MOD_ALT, VK_F12)) {
AfxMessageBox(_T("ホットキーの登録に失敗しました"));
}
return 0;
}
// ホットキーの解除
void CMainFrame::OnDestroy()
{
UnregisterHotKey(m_hWnd, 1);
CFrameWnd::OnDestroy();
}
WM_HOTKEYメッセージの処理
ホットキーが押下されると、WM_HOTKEYメッセージが送信される。
メッセージハンドラの実装を以下に示す。
// ヘッダファイルでメッセージマップを宣言
class CMainFrame : public CFrameWnd
{
protected:
afx_msg LRESULT OnHotKey(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
// メッセージマップの定義
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_HOTKEY()
END_MESSAGE_MAP()
// ハンドラの実装
LRESULT CMainFrame::OnHotKey(WPARAM wParam, LPARAM lParam)
{
int nHotKeyId = (int)wParam;
UINT fsModifiers = (UINT)LOWORD(lParam);
UINT vk = (UINT)HIWORD(lParam);
if (nHotKeyId == 1) {
// [Ctrl] + [Alt] + [F12]キーが押下された場合
AfxMessageBox(_T("ホットキーが押されました"));
}
return 0;
}
※注意
ホットキーは、システム全体で有効なため、他のアプリケーションと競合しないように注意する必要がある。
また、アプリケーション終了時には、必ずUnregisterHotKey関数でホットキーを解除する。
キーボード入力のシミュレーション
プログラムから仮想的にキーボード入力を生成する技術を、キーボード入力のシミュレーションという。
Windowsでは、SendInput 関数 と keybd_event 関数の2つの方法がある。
- SendInput関数
- Windows 2000以降で推奨される方法である。
- keybd_event関数
- レガシーな方法であり、互換性のために残されている。
SendInput関数
SendInput関数は、キーボードやマウスの入力イベントを合成する関数である。
関数のプロトタイプを以下に示す。
UINT SendInput(
UINT nInputs,
LPINPUT pInputs,
int cbSize
);
| 項目 | 説明 |
|---|---|
| nInputs | 入力イベントの数 |
| pInputs | INPUT構造体の配列 |
| cbSize | INPUT構造体のサイズ |
使用例を以下に示す。
void SendKeyPress(WORD vkCode)
{
INPUT inputs[2] = {};
// キー押下イベント
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = vkCode;
inputs[0].ki.dwFlags = 0; // キーダウン
// キー解放イベント
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = vkCode;
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
// 入力イベントの送信
UINT uSent = SendInput(2, inputs, sizeof(INPUT));
if (uSent != 2) {
AfxMessageBox(_T("SendInputに失敗しました"));
}
}
// 使用例 : [Enter]キーを送信
void CMyView::SimulateEnterKey()
{
SendKeyPress(VK_RETURN);
}
修飾キー付きのキー入力をシミュレートする例を以下に示す。
void SendCtrlC()
{
INPUT inputs[4] = {};
// [Ctrl]キー押下
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_CONTROL;
inputs[0].ki.dwFlags = 0;
// [C]キー押下
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = 'C';
inputs[1].ki.dwFlags = 0;
// [C]キー解放
inputs[2].type = INPUT_KEYBOARD;
inputs[2].ki.wVk = 'C';
inputs[2].ki.dwFlags = KEYEVENTF_KEYUP;
// [Ctrl]キー解放
inputs[3].type = INPUT_KEYBOARD;
inputs[3].ki.wVk = VK_CONTROL;
inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(4, inputs, sizeof(INPUT));
}
SendInput関数は、UIPI (User Interface Privilege Isolation) の制約を受ける。
低い整合性レベルのアプリケーションから、高い整合性レベルのアプリケーションに入力を送信することはできない。
keybd_event関数
keybd_event関数は、レガシーなキーボード入力シミュレーション関数である。
関数のプロトタイプを以下に示す。
VOID keybd_event(
BYTE bVk,
BYTE bScan,
DWORD dwFlags,
ULONG_PTR dwExtraInfo
);
| 項目 | 説明 |
|---|---|
| bVk | 仮想キーコード |
| bScan | スキャンコード (通常は0) |
| dwFlags | フラグ (KEYEVENTF_KEYUP等) |
| dwExtraInfo | 追加情報 (通常は0) |
使用例を以下に示す。
void SendKeyPressLegacy(BYTE vkCode)
{
// キー押下
keybd_event(vkCode, 0, 0, 0);
// キー解放
keybd_event(vkCode, 0, KEYEVENTF_KEYUP, 0);
}
// [Ctrl] + [V]キーの送信
void SendCtrlV()
{
// [Ctrl]キー押下
keybd_event(VK_CONTROL, 0, 0, 0);
// [V]キー押下
keybd_event('V', 0, 0, 0);
// [V]キー解放
keybd_event('V', 0, KEYEVENTF_KEYUP, 0);
// [Ctrl]キー解放
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
}
※注意
keybd_event関数は、Windows 2000以降では非推奨とされている。
新規開発では、SendInput関数の使用を推奨する。
キーボード入力のシミュレーションは、自動化ツール、テストツール、マクロツールなどの実装に利用される。
ただし、セキュリティ上の理由から、使用には注意が必要である。
関連リンク
- Microsoft Docs - Keyboard Input
- Microsoft Docs - Virtual-Key Codes
- Microsoft Docs - SendInput function