キーボードのイベント

キーイベントの処理

続いて、キーボードのイベント処理を行います。WIN32APIは、キーボードのアップ・ダウンをイベントで取得することができます。

サンプルプログラム

では実際にキーボードのイベント処理を行うプログラムを見てみましょう。まずは以下のプロジェクトを実行してみてください。

win32proj4-1:WinMain.cpp
ダウンロード
// ************************************
// Ex キーボードのイベント処理を行う
// ************************************
//必要なヘッダーファイルのインクルード
#define STRICT

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>

// シンボル定義及びマクロ
#define WINDOW_WIDTH    800
#define WINDOW_HEIGHT   600

//文字列描画用配列
TCHAR   szstr[256] = _T("キーを押していません");

//ポイント構造体
POINT  pt = { 400, 200 };

//	インスタンス(グローバル変数)
HINSTANCE hInst;

//	ウィンドウプロシージャのコールバック関数
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nCmdShow)
{
	static TCHAR szWindowClass[] = _T("Sample04");
	static TCHAR szTitle[] = _T("キーボードのイベントを処理するプログラム");

	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL,
			_T("RegisterClassExの処理に失敗しました"),
			_T("Sample04"),
			NULL);

		return 1;
	}

	hInst = hInstance; // グローバル変数に値を入れる

	// The parameters to CreateWindow explained:
	// szWindowClass				: アプリケーションの名前
	// szTitle						: タイトルバーに現れる文字列
	// WS_OVERLAPPEDWINDOW			: 生成するウィンドウのタイプ
	// CW_USEDEFAULT, CW_USEDEFAULT	: 最初に置くポジション (x, y)
	// WINDOW_WIDTH, WINDOW_HEIGHT	: 最初のサイズ (幅, 高さ)
	// NULL							: このウィンドウの親ウィンドウのハンドル
	// NULL							: メニューバー(このサンプルでは使用せず)
	// hInstance					: WinMain関数の最初のパラメータ
	// NULL							: WM_CREATE情報(このアプリケーションでは使用せず)
	HWND hWnd = CreateWindow(
		szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		WINDOW_WIDTH, WINDOW_HEIGHT,
		NULL,
		NULL,
		hInstance,
		NULL
		);
	//	ウィンドウが生成できなかった場合
	if (!hWnd)
	{
		MessageBox(NULL,
			_T("ウィンドウ生成に失敗しました!"),
			_T("Sample04"),
			NULL);
		return 1;
	}

	// ウィンドウの表示に必要なパラメータ:
	// hWnd		: CreateWindowの戻り値
	// nCmdShow	: WinMainの引数の4番目
	ShowWindow(hWnd,
		nCmdShow);
	UpdateWindow(hWnd);

	// メインのメッセージループ:
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;
}

//	ウィンドウプロシージャ(メッセージに対するコールバック関数)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;
	switch (message)
	{
	case WM_PAINT:
		//	描画処理の開始
		hdc = BeginPaint(hWnd, &ps);
		// 文字列の出力。
		TextOut(hdc,
			5, 5,
			szstr, _tcslen(szstr));
		//	ペイント処理の終了
		EndPaint(hWnd, &ps);
		break;
		//キーを押した
	case WM_KEYDOWN:
		switch (wParam)
		{
		//	エスケープキーの場合
		case VK_ESCAPE:
			//終了メッセージを発生させる
			PostMessage(hWnd, WM_CLOSE, 0, 0);
			break;
		//	スペースキーの場合
		case VK_SPACE:
			_stprintf_s(szstr, _T("%s"), _T("スペースキーを押しました"));
			break;
		//	Aキーの場合
		case 'A':
			_stprintf_s(szstr, _T("%s"), _T("Aキーを押しました"));
			break;
		//	Bキーの場合
		case 0x42:
			_stprintf_s(szstr, _T("%s"), _T("Bキーを押しました"));
			break;
		default:
			_stprintf_s(szstr, _T("%s"), _T("キーを押しました"));
			break;
		}
		//再描画メッセージを発生させる
		InvalidateRect(hWnd, NULL, TRUE);
		return 0;
		//キーを放した
	case WM_KEYUP:
		_stprintf_s(szstr, _T("%s"), _T("キーを押していません"));
		//再描画メッセージを発生させる
		InvalidateRect(hWnd, NULL, TRUE);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}

	return 0;
}

プログラムを実行すると、ウィンドウが出現し、以下のように画面左上に「キーボードのイベントを処理するプログラム」という画面が出現します。(図4-1.)

図4-1.プログラムを起動時

プログラムを起動時

ここで、スペースキーを押すと、以下のように「スペースキーを押しました」という文字列が出現します。

図4-2.スペースキーを押したとき

スペースキーを押したとき

手を離すと、「キーを押していません」と表示されます。

図4-3.キーから手を離したとき

キーから手を離したとき

同様に、Aキーを押したときは「Aキーを押しました」と、Bキーを押したときは「Bキーを押しました」と、それ以外のキーの場合は「キーを押しました」と表示されます。キーから手を離したときの結果も一緒です。エスケープキーを押すと、プログラムが終了します。

プログラムの仕組み

POINT構造体

プログラムの流れを説明する前に、20行目で使用されている、座標を表す構造体、POINTについて説明します。この構造体は、windows.hをインクルードすることにより使用可能になる構造体で、以下のような構造をしています。

POINT構造体
struct POINT {
   LONG x;   //点のx座標を指定します。
   LONG y;   //点のy座標を指定します。
};

例)

POINT ptA;
ptA.x = 370;
ptA.y = 550;

POINT ptB = {370,550};

以後、この構造体を利用して、平面上の座標を表していくことにします。

WM_KEYDOWN,WM_KEYUP

ここでは、新たに二つの新しいイベントが出てきます。129行目に出てくるWM_KEYDOWNが、キーボードを押すと発生するメッセージで、157行目に出てくるWM_KEYUPイベントが、キーボードを放すと発生するメッセージです。

なお、Altおよび、F10キーは、システムキーと呼ばれ、特別なキーとされており、このイベントでは処理できません。システムキーの場合は、WM_SYSKEYDOWNおよびWM_SYSKEYUPが必要です。

押されたキーの特定

WM_KEYDOWN,WM_KEYUPでは、キーボードを押した・離したというイベントを検知できますが、押されたキーの種類まで特定できません。そこで、それを特定するのに必要となるのが、ウィンドウプロシージャの引数である、wParamです。WM_KEYDOWNイベントが発生した場合、ここにはキーボードの識別情報が格納されます。

一般的によく使うのは、VK_から始まる、仮想キーコードと呼ばれる値です。133行目のVK_ESCAPEがエスケープキーを、138行目のVK_SPACEキーがスペースキーを表す仮想キーコードです。

それ以外の数字やアルファベットなどは、ASCIIコードで特定できます。例えば、142行目のように、Aキーが押された場合、AキーASCIIコードを表す、'A'で、この値を表現できます。ASCIIコードは数値なので、そのまま数値を入れてもかまいません。146行目のように、0x42とすると、丁度これはBのアスキーコードですから、'B'という表現と同じ値となります。

再描画

ところで、このサンプルでは、キーボードを押す・離すといった処理で、画面の内容が更新されます。このように、何らかのイベントで画面を更新したい場合、そのタイミングで、ウィンドウズに再描画を促すことができます。それを可能にしているのが、InvalidateRect関数です。

InvalidateRect関数
BOOL InvalidateRect(
HWND hWnd,              // ウィンドウのハンドル
CONST RECT *lpRect,  // 長方形の座標
BOOL bErase              // 消去するかどうかの状態
);

指定されたウィンドウの更新リージョンに 1 個の長方形を追加します。更新リージョンとは、ウィンドウのクライアント領域のうち、再描画しなければならない部分のことです。この命令が発行されると、ウィンドウズは、WM_PAINTイベントを発行して、指定された領域が再描画されます。(図4-4.)

図4-4.InvalidateRectの処理の流れ

InvalidateRectの処理の流れ

このサンプルでは、154行目および160行目でこの処理が行われています。長方形の座標としてNULLが指定されていますが、NULLを指定すると、クライアント領域全体を更新リージョンへ追加します。

PostMessagePostMessage

また、163行目にでてくる、PostQuitMessage関数は、ウィンドウにしていしたイベントを自分自身に向けて発生させる関数です。

PostMessage関数の仕様
BOOL PostMessage(
    HWND hWnd,         // ポスト先ウィンドウのハンドル
    UINT Msg,             // メッセージ
    WPARAM wParam,   // メッセージの最初のパラメータ
    LPARAM lParam       // メッセージの 2 番目のパラメータ
);

Msgで指定されたメッセージと、wParam、lParamというパラメータの値を設定して、ポスト先のウィンドウに送ることができます。このサンプルでは135行目で以下のように記述されています。

PostMessage関数
PostMessage(hWnd, WM_CLOSE, 0, 0);

hWndには、このアプリ自身のウィンドウハンドルであり、WM_CLOSEを送ることにより、自分自身にウィンドウズの終了処理が行われるのです。そのため、このサンプルではエスケープキーを押すとウィンドウが閉じ、アプリケーションが終了します。

図4-5.PostMessageの処理の流れ

PostMessageの処理の流れ

文字列の処理

文字列のコピー

このプログラムでは、17行目で定義されている配列変数szstrで文字列を処理しています。すでに説明してある通り、TCHAR型の文字列であることから、_Tマクロを使用して、文字列の処理を行っています。

このプログラムの中で、しばしばszstrの中身が変更されます。そのために用いられているのが、_stprintf_s関数です。

_stprintf_s関数

_stprintf_s関数は、TCHAR型の文字列をコピーするための関数で、標準のC言語のsprintf関数に相当します。これにより、139行目の処理をもとに説明していくことします。

_stprintf_s関数の使用例
_stprintf_s(szstr, _T("%s"), _T("スペースキーを押しました"));

これにより、右辺の、「スペースキーを押しました」という文字列が、szstrにコピーされます。_Tマクロがついている以外は、sprintfとまったく同じ用法です。この関数の末尾についている_sというのは、セキュリティチェックがされていることを意味します。

文字列のコピーは、しばしばOSのセキュリティホールになりやすいので、WIN32APIでは標準の関数のほかにこのようなセキュリティホールに対応した関数が用意されており、なるべくそちらを使うことが奨励されています。