ボタンコントロール

コントロール

ウィンドウアプリは、そのウィンドウ上で様々な操作が視覚的に可能です。
アプリを操作するために重要なのがコントロールです。

コントロールは「ボタン」「チェックボックス」「リストボックス」「エディットコントロール」などが代表的なものです。
まずは最もよく使用されるであろうボタンコントロールについて説明します。

ボタンコントロールの作成

コントロールはウィンドウの一種であり、親ウィンドウ内に配置される子ウィンドウとして扱います。
ウィンドウなのでCreateWindow関数で作成します。
(→ウィンドウの作成)


#define IDC_BUTTON1 100

HWND hBtn = CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE,
			10, 10, 60, 24,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);

第一引数はクラス名の指定ですが、ここにはBUTTONという文字列を指定します。
今までは自分で定義した文字列(クラス名)を指定していましたが、コントロールを作成する場合はシステムクラス(システム定義済みクラス)という、あらかじめ定義されたウィンドウクラスを使用します。
ボタンコントロールを作成する場合は「BUTTON」クラスを使用するというわけです。

第二引数はボタンのキャプションです。
つまりボタン上に表示する文字列です。

第三引数のウィンドウスタイルでは、WS_CHILDWS_VISIBLEを指定します。
WS_CHILDフラグは子ウィンドウを作成するという意味で、これは必須です。
WS_VISIBLEフラグは表示状態で作成するという意味で、必須ではないですがデフォルトでは非表示で作成されるので、通常はこのフラグで表示状態にして作成します。
コントロールのウィンドウスタイルはコントロール独自の物もいろいろと用意されているので、必要に応じて追加します。

続く四つの引数はクライアント領域上のコントロールの位置とサイズです。
(X座標,Y座標,幅,高さ)

第八引数hWndParentには親ウィンドウのウィンドウハンドルを指定します。
この親ウィンドウ上にコントロールが配置されます。

第九引数hMenuには子ウィンドウのウィンドウID(コントロールID)を指定します。
これは親ウィンドウが各コントロールを識別するための識別子で、実体はただの整数値です。
通常はdefineで任意の値を定義しておいてそれを指定します。
なお、要求される引数の型はHMENU型なのでキャストしないと警告がでます。

残りの引数は通常のウィンドウ作成と同じです。

戻り値は作成したコントロールのウィンドウハンドルです。
コントロールに対する操作はウィンドウハンドルを使用する場合と子ウィンドウIDを使用する場合があります。
ウィンドウハンドルはHWND型で、CreateWindow関数によりシステムが自動的に決定する値です。
子ウィンドウIDはプログラマが決定する任意の整数値です。
この二つは似ているようで別物なので注意しましょう。

WM_COMMANDメッセージ

CreateWindow関数の引数に間違いがなければ、これでボタンが作成されます。
しかし作成しただけではクリックしても何も起こりません。

ボタンをクリックすると、親ウィンドウにWM_COMMANDメッセージが送信されます。
このメッセージの処理でボタンクリック時の動作を実装します。

サンプルコード1

簡単なボタンを作成してみます。


#include <windows.h>

#define IDC_BUTTON1 100

//ウィンドウの生成等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE,
			10, 10, 60, 24,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		break;

	case WM_COMMAND: //コントロールの操作
		MessageBox(hWnd, L"ボタンをクリックしました。", L"情報", MB_OK);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

今回はWM_CREATEメッセージ内でボタンを作成しています。
ボタンをクリックするとメッセージボックスが表示されます。
ボタンコントロールのテスト

今回はボタンのウィンドウハンドル(CreateWindow関数の戻り値)は使用しないので取得していません。

上記のサンプルコードにはWM_PAINTメッセージの処理がないことに注目してください。
コントロール類の描画はWM_PAINTメッセージに関係なく行われます。
これはコントロール自体がウィンドウであり、それぞれのコントロールが独自の描画処理を行っているためです。

コントロールは親ウィンドウが破棄される時に自動的に破棄されるため、破棄の処理を記述する必要はありません。

複数のボタンの設置

ボタンを複数設置する場合、当然ですがそれぞれのボタンで異なる処理を実装する必要がありますが、上記コードではそれが出来ていません。
WM_COMMANDメッセージのWPARAMの下位ワード(LOWORDマクロで取得)に、クリックしたボタンの子ウィンドウIDが格納されています。
これでボタンを判別することができます。


#include 

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100
#define IDC_BUTTON2 101

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		CreateWindow(
			L"BUTTON", L"ボタン1",
			WS_CHILD | WS_VISIBLE,
			10, 10, 70, 24,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"ボタン2",
			WS_CHILD | WS_VISIBLE,
			85, 10, 70, 24,
			hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);
		break;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			MessageBox(hWnd, L"ボタン1をクリックしました。", L"情報", MB_OK);
			break;
		case IDC_BUTTON2:
			MessageBox(hWnd, L"ボタン2をクリックしました。", L"情報", MB_OK);
			break;
		}		
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

ボタン毎に異なるメッセージを表示するサンプルです。
複数のボタンの設置

ボタンに限らず、コントロール類は大体この方法で処理を振り分けます。

ちなみにLPARAMにはコントロールのウィンドウハンドルが格納されています。
コントロールIDもコントロールのウィンドウハンドルも、コントロールを識別するためのものですが使い方が異なるので注意してください。

通知コード

WM_COMMANDメッセージの受信時、WPARAMの下位ワードはコントロールの子ウィンドウIDですが、上位ワード(HIWORDマクロで取得)には通知コードというものが格納されています。
これはボタンなどのコントロールの状態が変化したときに送られてきます。

ボタンの場合は「クリックされた」ことを状態変化としてWM_COMMANDメッセージを通知します。
このとき、WPARAMの上位ワードに「クリックされた」という通知コードが格納されています。

ボタンの通知コードには以下のようなものがあります。

定数 説明
BN_CLICKED ボタンがクリックされたとき
BN_PAINT ボタンの再描画が必要なとき
Windows3.0以前との互換性のために存在
BN_HILITE
BN_PUSHED
ボタンが選択されたとき
Windows3.0以前との互換性のために存在
BN_UNHILITE
BN_UNPUSHED
ボタンの選択が解除されたとき
Windows3.0以前との互換性のために存在
BN_DISABLE ボタンが無効化されたとき
Windows3.0以前との互換性のために存在
BN_DOUBLECLICKED
BN_DBLCLK
ボタンがダブルクリックされたとき
BS_NOTIFYフラグの追加が必要
BN_SETFOCUS ボタンがフォーカスを受け取ったとき
BS_NOTIFYフラグの追加が必要
BN_KILLFOCUS ボタンがフォーカスを失ったとき
BS_NOTIFYフラグの追加が必要

クリック以外の通知コードは、ウィンドウスタイルにBS_NOTIFYフラグの追加が必要です。
BS_NOTIFYフラグを追加するとクリック以外の操作時にもWM_COMMANDメッセージが通知されるようになるので、通知コードを見て処理を分岐させる必要があります。


#include <windows.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100
#define IDC_BUTTON2 101

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static const WCHAR* txt[] = {
		L"- - -",
		L"クリック",
		L"ダブルクリック",
		L"フォーカス オン",
		L"フォーカス オフ"
	};
	static int state;
	
	HDC hdc;
	PAINTSTRUCT ps;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE | BS_NOTIFY, //BS_NOTIFYを追加
			10, 10, 60, 24,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		CreateWindow( //ダミーのボタン
			L"BUTTON", L"Dummy",
			WS_CHILD | WS_VISIBLE,
			75, 10, 60, 24,
			hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);
		break;
		
	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			switch (HIWORD(wParam)) //通知コードを調べる
			{
			case BN_CLICKED:
				state = 1;
				break;
			case BN_DBLCLK:
				state = 2;
				break;
			case BN_SETFOCUS:
				state = 3;
				break;
			case BN_KILLFOCUS:
				state = 4;
				break;
			}
			InvalidateRect(hWnd, NULL, TRUE);
			break;
		}
		break;

	case WM_PAINT: //ウィンドウの描画
		hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 10, 34, txt[state], lstrlen(txt[state]));
		EndPaint(hdc, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

実行結果です。
ボタンの通知コードのテスト

左側のボタンを操作する度に、下のテキストが切り替わります。
フォーカスのオン/オフの操作を確認するためにダミーのボタンを横に配置しています。
ダミーボタンはコード上は何も処理を行いませんが、クリックすることで左側のボタンからフォーカスが外れるため、BN_KILLFOCUSの処理が行われテキストが切り替わります。

フォーカスとは、キー入力の対象になっている要素を指す言葉です。
マウスで選択したコントロールはそのままキー入力の対象になり、これを「フォーカスを得る」とか「フォーカスを受け取る」などと言います。
キー入力の対象が別のコントロールに移ることを「フォーカスが外れる」とか「フォーカスを失う」などといいます。

ボタンコントロールはマウスで選択することでフォーカスを得ることができます。
(Enterキーやスペースキーでボタンをクリックできるようになる)
コントロールによってはフォーカスを得ることができないものもあります。

ボタンのスタイル

ボタンのウィンドウスタイルには以下の定数を任意で追加できます。

定数 説明
BS_PUSHBUTTON プッシュボタン
(通常のボタン。デフォルト)
BS_DEFPUSHBUTTON 既定のボタンを表す、やや強調表示されたプッシュボタン
BS_TEXT ボタン上にテキストを表示
(デフォルト)
BS_ICON ボタンにアイコンを表示
BS_BITMAP ボタンにビットマップを表示
BS_LEFT テキストの左揃え
BS_RIGHT テキストの右揃え
BS_CENTER テキストの水平方向の中央揃え
BS_TOP テキストの上揃え
BS_BOTTOM テキストの下揃え
BS_VCENTER テキストの垂直方向の中央揃え
BS_MULTILINE テキストがボタン矩形内に収まらない場合に、テキストを折り返して表示
BS_NOTIFY 親ウィンドウにフォーカスの状態変化を通知
(BN_SETFOCUSBN_KILLFOCUS)
BS_FLAT フラットスタイルボタン
BS_OWNERDRAW オーナードローボタン
コントロール内の描画をプログラマ自身が行う

特に何も追加しない場合はBS_PUSHBUTTONBS_TEXTを指定した時と同じになります。
これは通常のボタン(プッシュボタン)です。
ボタンにはプッシュボタン以外にも種類がありますが、それは別のページで改めて説明します。