キー入力
キーボードの押下
マウス操作と同じく、キーボードのキー入力も「押す」と「離す」の二種類のメッセージが通知されます。
メッセージ | 説明 |
---|---|
WM_KEYDOWN | キーを押す |
WM_KEYUP | キーを離す |
WM_SYSKEYDOWN | システムキーを押す |
WM_SYSKEYUP | システムキーを離す |
WM_KEYDOWN
/WM_KEYUP
メッセージはAltキーとF10キー以外のキー入力時に通知されます。
AltキーとF10キーはシステムキーといって、Windowsの操作のための特殊なキーなので別扱いになっています。
(それ以外のキーを一般キーともいいます)
Windows自体はマウスが無くても、キーボードのみで操作可能なように作られています。
(Windows上で動作するアプリケーションはその実装次第)
AltキーとF10キーはウィンドウのメニュー項目を選択するために使用されています。
キーボードはキーを押しっぱなしにすると連続して文字が入力されますが、その都度WM_KEYDOWNメッセージが通知されます。
キーの判別
キーボードメッセージが送られてきたとき、ウィンドウプロシージャのWPARAMには入力があったキーの仮想キーコード(Virtual Key Codes)というものが格納されています。
仮想キーコードはキーボードの機種による違いを吸収するもので、機種によって異なる「生の」キー入力情報を仮想的なキー情報に変換し、汎用的に扱うための仕組みです。
仮想キーコードは「VK_○○」という形式で定数が用意されています。
ただし英数字のキーなど、機能ではなく文字が割り当てられているキーに関してはASCIIコードと同じなので定数はありません。
仮想キーコード定数はかなり数が多いので主要なものを紹介します。
仮想キーコード | 説明 | 16進数 |
---|---|---|
VK_BACK | BackSpace | 0x08 |
VK_TAB | Tab | 0x09 |
VK_CLEAR | Clear | 0x0C |
VK_RETURN | Return(Enter) | 0x0D |
VK_SHIFT | Shift | 0x10 |
VK_CONTROL | Ctrl | 0x11 |
VK_MENU | Alt | 0x12 |
VK_PAUSE | Pause | 0x13 |
VK_CAPITAL | Caps Lock | 0x14 |
VK_ESCAPE | Escape | 0x1B |
VK_SPACE | Space | 0x20 |
VK_PRIOR | Page Up | 0x21 |
VK_NEXT | Page Down | 0x22 |
VK_END | End | 0x23 |
VK_HOME | Home | 0x24 |
VK_LEFT | ← | 0x25 |
VK_UP | ↑ | 0x26 |
VK_RIGHT | → | 0x27 |
VK_DOWN | ↓ | 0x28 |
VK_PRINT | Print Screen | 0x2A |
VK_INSERT | Insert | 0x2D |
VK_DELETE | Delete | 0x2E |
VK_LWIN | 左Windows | 0x5B |
VK_RWIN | 右Windows | 0x5C |
VK_APPS | Menu (アプリケーションキー) |
0x5D |
VK_NUMPAD0~ VK_NUMPAD9 |
テンキー0~9 | 0x60~ 0x69 |
VK_MULTIPLY | テンキー* | 0x6A |
VK_ADD | テンキー+ | 0x6B |
VK_SEPARATOR | テンキーEnter | 0x6C |
VK_DECIMAL | テンキー. | 0x6E |
VK_DIVIDE | テンキー/ | 0x6F |
VK_F1~ VK_F12 |
F1~F12 | 0x70~ 0x7B |
VK_F13~ VK_F24 |
F13~F24 | 0x7C~ 0x87 |
VK_NUMLOCK | Num Lock | 0x90 |
VK_SCROLL | Scroll Lock | 0x91 |
キーボードの機種によっては搭載していないキーがあったり、別のキーなのに同じ仮想キーコードが送られてくることもあります。
例えばReturnキーとテンキーのEnterは同じ仮想キーとなることが多いです。
Shiftキー、Ctrlキー、Altキーなどは左右にあることが多いですが、左右どちらのキーから入力されたかはWPARAMを調べるだけでは判別することはできません。
仮想キーコードを表示するサンプルコードです。
#include <windows.h>
#include <strsafe.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 128
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int c;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"キーコード: 0x%02X";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, c);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_KEYDOWN: //キー押下
c = wParam;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_SYSKEYDOWN: //システムキー押下
c = wParam;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
以下は「A」キーを押したところです。
「VK_A」という仮想キーコードの定数はありませんが、char型の「'A'」は内部的には「0x41(10進数で65)」なので、定数は必要ないわけです。
文字の判別
WM_KEYDOWN
メッセージで取得できる仮想キーコードは、「押したキーの種類」を取得するもので、実際に入力される文字の判別には使えません。
大文字の「A」と小文字の「a」では同じ番号が取得されるためです。
ShiftキーやCaps Lockなどの状態をすべて調べて文字を判別するようなコードを書くことも出来なくはないかもしれませんが、もっと簡単に判別する方法が用意されています。
WinMain関数内のメッセージループでは、GetMessage関数を使用してウィンドウに通知されたメッセージを取得していました。
このメッセージ(MSG構造体の変数)はDispatchMessage
関数でウィンドウプロシージャに送られるわけですが、その手前でTranslateMessage
関数を呼び出します。
- BOOL TranslateMessage(
const MSG *lpMsg
); - 仮想キーコードを文字メッセージに変換する。
キー入力メッセージを処理した場合は0以外を返す。
それ以外の場合は0を返す。
つまりメッセージループは以下のようになります。
MSG msg;
BOOL ret;
//メッセージループ
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
return -1;
}
else
{
TranslateMessage(&msg); //これを追加
DispatchMessage(&msg);
}
}
このようにすると、文字キーの入力が発生したときにWM_CHAR
というメッセージが発行されます。
システムキーの入力の場合はWM_SYSCHAR
メッセージが発行されます。
ウィンドウプロシージャ内ではこのメッセージを補足して処理を行います。
WM_CHARメッセージが通知されたとき、WPARAMには文字コードが入っています。
これは大文字小文字の状態を適切に処理した後の値なので、そのまま文字として表示することができます。
#include <windows.h>
#include <strsafe.h>
//グローバル変数
HINSTANCE hInst; //インスタンス
WCHAR szTitle[] = L"テストアプリ"; //タイトル
WCHAR szWindowClass[] = L"MyTestApp"; //ウィンドウクラス名
//関数プロトタイプ宣言
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow)
{
MyRegisterClass(hInstance);
if (!InitInstance(hInstance, nCmdShow))
{
return 0;
}
MSG msg;
BOOL ret;
//メッセージループ
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
//GetMessage関数の実行失敗
return -1;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//ウィンドウクラスの登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
//省略
}
//ウィンドウの作成
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
//省略
}
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rt;
static int v, c;
WCHAR buf[BUFFERSIZE];
static const WCHAR* format = L"仮想キー:\t0x%02X\n"
L"文字コード:\t0x%02X\n"
L"文字:\t\t%c";
switch (message)
{
case WM_PAINT: //ウィンドウの描画
StringCchPrintf(buf, BUFFERSIZE, format, v, c, c);
GetClientRect(hWnd, &rt);
hdc = BeginPaint(hWnd, &ps);
DrawText(hdc, buf, -1, &rt, DT_WORDBREAK | DT_EXPANDTABS);
EndPaint(hWnd, &ps);
break;
case WM_KEYDOWN:
v = wParam;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_SYSKEYDOWN:
v = wParam;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_CHAR:
c = wParam;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_SYSCHAR:
c = wParam;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
このコードは入力されたキーの仮想キーコード、文字コード、文字を表示します。
キー入力メッセージが送られてくる順番は
- WM_KEYDOWN
- WM_CHAR
- WM_KEYUP
となります。
GetKeyState関数
上記のキーの判別は、そのキーが押下された(または離された)時に、そのメッセージの処理内でのみ可能です。
それ以外の個所で特定のキーの状態を知るにはGetKeyState
関数を使用します。
- SHORT GetKeyState(
int nVirtKey
); - キーnVirtKeyの状態を取得する。
引数は状態を取得したいキーの仮想キーコードです。
(文字キーの場合はASCIIコード)
戻り値は少しややこしいです。
最上位ビットが1の場合、キーが押されています。
0ならば押されていません。
最下位ビットはキーを押すごとに1と0の状態が切り替わります。
これはCapsLockキーの状態を知る場合などに利用します。
キーが押されているか否かは戻り値の最上位ビットを調べれば良いわけです。
最上位ビットが0なら正数、1なら負数になりますから、0未満か否かを判定することでキーの状態を判定することができます。
(律儀に「0x8000」とビットAND演算しても良いです→ビット演算の活用法)
GetKeyState関数は例えば修飾キーと文字キーが同時に押されているか否かを判別する時に使用できます。
case WM_KEYDOWN:
//「Ctrl + A」の入力を判定する
if (wParam == 'A' && GetKeyState(VK_CONTROL) < 0) {
MessageBox(hWnd, L"Ctrl + Aが押されました。", L"情報", MB_OK);
}
break;
「A」キーが押された時に送られてくるWM_KEYDOWN
メッセージでは、WPARAMには「'A'」の情報しか入っていません。
GetKeyState関数を使用することで、同時押しを判別できるようになります。
なお、GetKeyState関数では以下の仮想キーコードが追加で使用できます。
仮想キーコード | 説明 | 16進数 |
---|---|---|
VK_LSHIFT | 左Shift | 0xA0 |
VK_RSHIFT | 右Shift | 0xA1 |
VK_LCONTROL | 左Ctrl | 0xA2 |
VK_RCONTROL | 右Ctrl | 0xA3 |
VK_LMENU | 左Alt | 0xA4 |
VK_RMENU | 右Alt | 0xA5 |
キーボードメッセージが送られてこない?
ウィンドウ上にボタンなどのコントロール類を配置した場合、WM_KEYDOWN
などのキーボードメッセージがウィンドウに送られてこないことがあります。
これはコントロール類にフォーカスがあるとキーボードメッセージはそのコントロールのウィンドウプロシージャに送られるのが原因です。
(コントロールは親ウィンドウにメッセージを再送信しない)
これの解決法は、何らかの方法でフォーカスを親ウィンドウにセットしなおす、コントロールをサブクラス化して親ウィンドウにメッセージを再送する、などの方法があります。