WisdomSoft - for your serial experiences.

ボタン

マウスの左クリックなどで押すことができるボタンコントロールをウィンドウに追加します。ボタンが押されると親ウィンドウのウィンドウプロシージャにメッセージが送られてくるので、これを感知して処理することで、ユーザーの入力に反応するプログラムを作れます。

クリックに反応するボタン

ボタンは、ユーザーの入力に反応して押すことができるコントロールです。マウスカーソルがボタン上にある状態で左クリックすれば、ボタンが押されたように見た目が変化します。スタティックコントロールは動作のないコントロールでしたが、多くのコントロールはボタンのようにユーザーの入力に反応します。

ボタンを作成する方法はスタティックコントロールと同じです。CreateWindow() 関数か CreateWindowEx() 関数を用いて、ウィンドウクラス名に BUTTON という文字列を指定します。

コード1
#include <windows.h>
#define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window")

LRESULT CALLBACK SampleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg) 
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hwnd , uMsg , wParam , lParam);
	}

	return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND window, button;
	WNDCLASSEX wcx;
	int returnCode = 0;
	ULONG p;

	wcx.cbSize = sizeof(WNDCLASSEX);
	wcx.style = CS_HREDRAW | CS_VREDRAW;
	wcx.lpfnWndProc = SampleWindowProc;
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	wcx.hInstance = hInstance;
	wcx.hIcon =  NULL;
	wcx.hCursor = NULL;
	wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
	wcx.lpszMenuName = NULL;
	wcx.lpszClassName = WINDOWS_CLASS_NAME;
	wcx.hIconSm = NULL;

	if (!RegisterClassEx(&wcx)) 
	{
		OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n"));
		return 0;
	}

	window = CreateWindowEx(
		WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL
	);
	if (!window)
	{
		OutputDebugString(TEXT("Error: ウィンドウを作成できません。\n"));
		return 0;
	}

	button = CreateWindowEx(
		WS_EX_LEFT, TEXT("BUTTON"), TEXT("Stand by Ready!!"),
		WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
		0, 0, 400, 100,
		window, NULL, hInstance, NULL
	);
	if (!button)
	{
		OutputDebugString(TEXT("Error: コントロールを作成できません。\n"));
		return 0;
	}

	while(TRUE)
	{
		MSG msg;
		int r = GetMessage(&msg, NULL, 0, 0);
		if (r > 0) DispatchMessage(&msg);
		else if(r == -1)
		{
			OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n"));
			break;
		}
		else
		{
			returnCode = msg.wParam;
			break;
		}
	}

	return returnCode;
}
実行結果
コード1 実行結果

コード1はボタンを生成し、アプリケーションウィンドウの子ウィンドウとして設定しています。実行結果のように 1 つのボタンが表示されます。ボタン上でクリックすると、ボタンが押し込まれるように外観が変化します。しかし、このプログラムではボタンがクリックされた時の処理を書いていないため、ボタンを押しても何も反応しません。

クリックの通知を処理する

ボタンが押されたことをプログラムで感知し、ボタンが押されたときに何らかの反応を返すには、ボタンから送られてくるメッセージを処理します。ボタンが押されると、親ウィンドウのウィンドウプロシージャに WM_COMMAND メッセージを送ります。このメッセージは、子ウィンドウによって何らかのコマンドが発行されたときに送られます。

コード2
#include <windows.h>
#define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window")

LRESULT CALLBACK SampleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg) 
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_COMMAND:
		MessageBox(hwnd, TEXT("Set up!"), TEXT("確認"), MB_ICONINFORMATION);
		return 0;
	default:
		return DefWindowProc(hwnd , uMsg , wParam , lParam);
	}

	return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND window, button;
	WNDCLASSEX wcx;
	int returnCode = 0;
	ULONG p;

	wcx.cbSize = sizeof(WNDCLASSEX);
	wcx.style = CS_HREDRAW | CS_VREDRAW;
	wcx.lpfnWndProc = SampleWindowProc;
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	wcx.hInstance = hInstance;
	wcx.hIcon =  NULL;
	wcx.hCursor = NULL;
	wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
	wcx.lpszMenuName = NULL;
	wcx.lpszClassName = WINDOWS_CLASS_NAME;
	wcx.hIconSm = NULL;

	if (!RegisterClassEx(&wcx)) 
	{
		OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n"));
		return 0;
	}

	window = CreateWindowEx(
		WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL
	);
	if (!window)
	{
		OutputDebugString(TEXT("Error: ウィンドウを作成できません。\n"));
		return 0;
	}

	button = CreateWindowEx(
		WS_EX_LEFT, TEXT("BUTTON"), TEXT("Stand by Ready!!"),
		WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
		0, 0, 400, 100,
		window, NULL, hInstance, NULL
	);
	if (!button)
	{
		OutputDebugString(TEXT("Error: コントロールを作成できません。\n"));
		return 0;
	}

	while(TRUE)
	{
		MSG msg;
		int r = GetMessage(&msg, NULL, 0, 0);
		if (r > 0) DispatchMessage(&msg);
		else if(r == -1)
		{
			OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n"));
			break;
		}
		else
		{
			returnCode = msg.wParam;
			break;
		}
	}

	return returnCode;
}
実行結果
コード2 実行結果

コード2はウィンドウプロシージャで WM_COMMAND メッセージを受け取った時にメッセージボックスを表示します。ボタンが押されると、ボタンによって親ウィンドウに WM_COMMAND メッセージが送られるため、ボタンを押すとメッセージが表示されるプログラムのように動作します。

しかし WM_COMMAND メッセージは、ウィンドウに登録されている様々なコントロールから送られてきます。そのため、複数のコントロールが配置されている場合、どのコントロールから、どのようなコマンドが発行されたのかを調べる必要があります。

WM_COMMAND メッセージがコントロールによって発行された場合 lParam パラメータにメッセージを送出したコントロールのハンドルが格納されています。wParam パラメータは 32 ビット中の上位 16 ビットと下位 16 ビットにデータが分割され、上位 16 ビットにコントロール定義の通知コードが、下位 16 ビットにコントロールの識別 ID が格納されています。

コントロール定義の通知コードは、コントロールの種類によって意味が異なります。ボタンから送られた WM_COMMAND メッセージの場合、以下の通知コードのいずれかが格納されています。多くの場合は、ボタンがクリックされたことを表す BN_CLICKED と比較する形になるでしょう。

表1 ボタンの通知コード
定数 意味
BN_CLICKED ユーザーがボタンをクリックした。
BN_DBLCLK ユーザーが BS_OWNERDRAW または BS_RADIOBUTTON スタイルを持つボタンをダブルクリックした。
BN_DISABLE ボタンが使用不能になった。
BN_DOUBLECLICKED BN_DBLCLK と同じ。
BN_HILITE ユーザーがボタンを選択した。この通知コードは Windows 3.0 以前の 16 ビット版との互換性のために残されている。アプリケーションは BS_OWNERDRAW ボタンスタイルと DRAWITEMSTRUCT 構造体を使うべきである。
BN_KILLFOCUS キーボードフォーカスを失った。ボタンは、この通知メッセージを送るために BS_NOTIFY スタイルを持たなければならない。
BN_PAINT ボタンを再描画しなければならない。
BN_PUSHED BN_HILITE と同じ。
BN_SETFOCUS キーボードフォーカスを得た。ボタンは、この通知メッセージを送るために BS_NOTIFY スタイルを持たなければならない。
BN_UNHILITE ハイライトがボタンから削除されなければならない。
BN_UNPUSHED BN_UNHILITE と同じ。

子ウィンドウは識別用の ID を持つことができます。ID は CreateWindow() 関数または CreateWindowEx() 関数の hMenu パラメータに指定します。通常、このパラメータにはメニューのハンドルを指定しますが、子ウィンドウ(ウィンドウに追加するコントロール)の場合は、コントロールを識別する ID を設定できます。その場合、一意に識別できる任意の整数を HMENU 型キャストしてください。

wParam パラメータを上位 16 ビットと下位 16 ビットに分けるには、シフト演算と論理演算を使います。

WORD hiWord = (WORD)((wParam >> 16) & 0xFFFF); //上位 16 ビット
WORD loWord = (0xFFFF & wParam); //下位 16 ビット

上記のような形で上位ビットと下位ビットを取り出せますが、このような演算に展開してくれるマクロ関数が用意されています。上位 16 ビットの抽出には HIWORD() マクロ関数を、下位 16 ビットの抽出には LOWORD() マクロ関数を使います。

HIWORD() マクロ関数
WORD HIWORD(DWORD dwValue);
LOWORD マクロ関数
WORD LOWORD(DWORD dwValue);

それぞれ dwValue パラメータには分割する 32 ビット整数を指定します。

コード3
#include <windows.h>
#define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window")

#define ID_BUTTON1 10000
#define ID_BUTTON2 10001

LRESULT CALLBACK SampleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg) 
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED)
		{
			if(LOWORD(wParam) == ID_BUTTON1)
				MessageBox(hwnd, TEXT("Set up!"), TEXT("確認"), MB_ICONINFORMATION);
			if(LOWORD(wParam) == ID_BUTTON2)
				MessageBox(hwnd, TEXT("Sammlung!"), TEXT("確認"), MB_ICONINFORMATION);
		}
		return 0;
	default:
		return DefWindowProc(hwnd , uMsg , wParam , lParam);
	}

	return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND window, button1, button2;
	WNDCLASSEX wcx;
	int returnCode = 0;
	ULONG p;

	wcx.cbSize = sizeof(WNDCLASSEX);
	wcx.style = CS_HREDRAW | CS_VREDRAW;
	wcx.lpfnWndProc = SampleWindowProc;
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	wcx.hInstance = hInstance;
	wcx.hIcon =  NULL;
	wcx.hCursor = NULL;
	wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
	wcx.lpszMenuName = NULL;
	wcx.lpszClassName = WINDOWS_CLASS_NAME;
	wcx.hIconSm = NULL;

	if (!RegisterClassEx(&wcx)) 
	{
		OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n"));
		return 0;
	}

	window = CreateWindowEx(
		WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"),
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL
	);
	if (!window)
	{
		OutputDebugString(TEXT("Error: ウィンドウを作成できません。\n"));
		return 0;
	}

	button1 = CreateWindowEx(
		WS_EX_LEFT, TEXT("BUTTON"), TEXT("Stand by Ready!!"),
		WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
		0, 0, 400, 100,
		window, (HMENU)ID_BUTTON1, hInstance, NULL
	);
	button2 = CreateWindowEx(
		WS_EX_LEFT, TEXT("BUTTON"), TEXT("Anfang"),
		WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
		0, 110, 400, 100,
		window, (HMENU)ID_BUTTON2, hInstance, NULL
	);
	if (!button1 || !button2)
	{
		OutputDebugString(TEXT("Error: コントロールを作成できません。\n"));
		return 0;
	}

	while(TRUE)
	{
		MSG msg;
		int r = GetMessage(&msg, NULL, 0, 0);
		if (r > 0) DispatchMessage(&msg);
		else if(r == -1)
		{
			OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n"));
			break;
		}
		else
		{
			returnCode = msg.wParam;
			break;
		}
	}

	return returnCode;
}
実行結果
コード3 実行結果

コード3は 2 つのボタンをウィンドウに追加しています。それぞれのボタンが押されると WM_COMMAND メッセージが送られてくるので、wParam パラメータの上位 16 ビットを取り出してクリックイベントによるコマンドかどうか通知コードを調べます。通知コードがボタンクリックだった場合、wParam パラメータの下位 16 ビットを取り出してボタン ID と比較し、どちらのボタンが押されたのか識別しています。

WM_COMMAND メッセージが送られたとき lParam パラメータに子ウィンドウのハンドルが格納されているため、ハンドルによって識別できる場合は lParam パラメータと比較する方法も検討できるでしょう。