ラジオボタン

ラジオボタンの作成

ラジオボタンコントロールは、複数の選択肢からひとつを選択する際に使用するコントロールです。
チェックボックスコントロールと同じくオン/オフの状態を持てるボタンですが、オンの状態を持てるのは同時にひとつだけです。
一つのラジオボタンの状態をオンにすると、他のすべてのラジオボタンの状態はオフになります。

これもボタンの一種なので、BUTTONクラスで作成します。
ウィンドウスタイルにはBS_RADIOBUTTONを指定します。

コントロールの作成の基本的なことはボタンコントロールの項を参照してください。


#define IDC_RADIO1 100

CreateWindow(
	L"BUTTON", L"Radio1",
	WS_CHILD | WS_VISIBLE | BS_RADIOBUTTON,
	10, 10, 80, 25,
	hWnd, (HMENU)IDC_RADIO1, hInst, NULL);

これでラジオボタンを作成できます。
以下は二つ並べた画像です。
ラジオボタンの作成

チェックボックスコントロールの時と同じく、このままではクリックしても状態は変わりません。
クリックと同時に状態を切り替えるにはBS_AUTORADIOBUTTONスタイルを使用します。


CreateWindow(
	L"BUTTON", L"Radio1",
	WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
	10, 10, 80, 25,
	hWnd, (HMENU)IDC_RADIO1, hInst, NULL);

チェック状態の取得/設定

チェック状態の取得や設定についてはチェックボックスコントロールと同じなので、そちらを参照してください。
(3状態のラジオボタンはありません)
ラジオボタンのテキストの位置もチェックボックスと同様に設定できます。

グループ化

ウィンドウ上に複数のラジオボタンを配置すると、そのうちのひとつしかオン状態になることができません。
複数の選択肢グループを作成したい場合はWS_GROUPスタイルを利用します。

WS_GROUPスタイルを設定したコントロールは、次にWS_GROUPスタイルを持つコントロールが現れるまで(または最後まで)をひとつのグループとみなします。
ラジオボタンコントロールに適用すると、グループごとに選択状態を持つことができます。


#include <windows.h>

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

#define IDC_RADIO1 100
#define IDC_RADIO2 101
#define IDC_RADIO3 102
#define IDC_RADIO4 103

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND h;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ラジオボタンの作成
		//グループ1
		h = CreateWindow(
			L"BUTTON", L"Radio1",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP,
			10, 10, 80, 25,
			hWnd, (HMENU)IDC_RADIO1, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"Radio2",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			90, 10, 80, 25,
			hWnd, (HMENU)IDC_RADIO2, hInst, NULL);
		//先頭をオンにしておく
		SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);

		//グループ2
		h = CreateWindow(
			L"BUTTON", L"Radio3",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP,
			10, 40, 80, 25,
			hWnd, (HMENU)IDC_RADIO3, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"Radio4",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			90, 40, 80, 25,
			hWnd, (HMENU)IDC_RADIO4, hInst, NULL);
		//先頭をオンにしておく
		SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);
		break;

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

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

「Radio1とRadio2」「Radio3とRadio4」をグループ化しています。
ラジオボタンのグループ化

ちなみにラジオボタンは「(グループ内で)どれかひとつが常にオンの状態」になっていることが想定されます。
ラジオボタンの作成直後はすべてオフの状態なので、作成後すぐにどれかひとつをオンにする処理を行うのが良いでしょう。

グループボックス

コントロールのグループ化にはもう一つ、グループボックスを使用する方法もあります。
これはBS_GROUPBOXスタイルを持つボタンとして作成します。
ボタンコントロールの一種ですが、クリック等でイベントは発生しません。


#include <windows.h>

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

#define IDC_STATIC -1

#define IDC_RADIO1 100
#define IDC_RADIO2 101
#define IDC_RADIO3 102
#define IDC_RADIO4 103

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND hGroup1, hGroup2;
	HWND h;
	
	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//グループボックス1
		hGroup1 = CreateWindow(
			L"BUTTON", L"グループ1",
			WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
			10, 10, 200, 60,
			hWnd, (HMENU)IDC_STATIC, hInst, NULL);
		//グループボックス2
		hGroup2 = CreateWindow(
			L"BUTTON", L"グループ2",
			WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
			10, 80, 200, 60,
			hWnd, (HMENU)IDC_STATIC, hInst, NULL);

		//ラジオボタンの作成
		//hGroup1を親に設定
		h = CreateWindow(
			L"BUTTON", L"Radio1",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			10, 25, 80, 25,
			hGroup1, (HMENU)IDC_RADIO1, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"Radio2",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			90, 25, 80, 25,
			hGroup1, (HMENU)IDC_RADIO2, hInst, NULL);
		//先頭をオンにしておく
		SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);

		//グループ2
		//hGroup2を親に設定
		h = CreateWindow(
			L"BUTTON", L"Radio3",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			10, 25, 80, 25,
			hGroup2, (HMENU)IDC_RADIO3, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"Radio4",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			90, 25, 80, 25,
			hGroup2, (HMENU)IDC_RADIO4, hInst, NULL);
		//先頭をオンにしておく
		SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);
		break;

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

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

まずグループボックスを作成し、それを親に指定してグループ化したいコントロールを作成します。
同じ親を持つコントロール同士がグループ化されます。

グループ化されたコントロールの座標はグループボックスの左上が基準となります。
また、グループボックス自体はイベントを発生させないので、子ウィンドウIDで識別する必要がありません。
なのでIDC_STATICという共通の子ウィンドウIDを指定しています。

グループボックスはタイトルと枠線を持つことが出来るので、グループであることがユーザーにも分かりやすいという利点があります。

ただし、この方法で作成したラジオボタンの親ウィンドウはメインのウィンドウではなくグループボックスとなります。
コントロールを操作したときにWM_COMMANDメッセージが送られますが、この送信先は親ウィンドウです。
つまり、メインウィンドウのウィンドウプロシージャにはWM_COMMANDメッセージが送られてこないので、ラジオボタンの操作に対する処理ができません。

対策としては以下の二つがあります。

見た目だけ利用する

ひとつは、グループ化にはWS_GROUPスタイルを利用し、グループボックスはその見た目だけ利用する、という方法です。
グループ化したいコントロールの親ウィンドウはいつも通りメインウィンドウを指定します。
そしてグループ化したコントロールとグループボックスの座標を重ねることで、グループボックスを利用してグループ化しているように見せることができます。


case WM_CREATE: //ウィンドウ作成
	//グループボックス1
	CreateWindow(
		L"BUTTON", L"グループ1",
		WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
		10, 10, 200, 60,
		hWnd, (HMENU)IDC_STATIC, hInst, NULL);
	//グループボックス2
	CreateWindow(
		L"BUTTON", L"グループ2",
		WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
		10, 80, 200, 60,
		hWnd, (HMENU)IDC_STATIC, hInst, NULL);

	//ラジオボタンの作成
	//グループ1
	h = CreateWindow(
		L"BUTTON", L"Radio1",
		WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP,
		20, 35, 80, 25,
		hWnd, (HMENU)IDC_RADIO1, hInst, NULL);
	CreateWindow(
		L"BUTTON", L"Radio2",
		WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
		100, 35, 80, 25,
		hWnd, (HMENU)IDC_RADIO2, hInst, NULL);
	//先頭をオンにしておく
	SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);

	//グループ2
	h = CreateWindow(
		L"BUTTON", L"Radio3",
		WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP,
		20, 105, 80, 25,
		hWnd, (HMENU)IDC_RADIO3, hInst, NULL);
	CreateWindow(
		L"BUTTON", L"Radio4",
		WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
		100, 105, 80, 25,
		hWnd, (HMENU)IDC_RADIO4, hInst, NULL);
	//先頭をオンにしておく
	SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);
	break;

実行結果は最初のサンプルコードと同じです。
これは単純かつ分かりやすい方法です。
ただしグループボックスの座標と各コントロールの座標は連動しないので、位置調整がちょっとだけ面倒です。

サブクラス化

もうひとつはサブクラスという方法を使用します。
サブクラスについては上記ページで改めて説明しますが、簡単に言うと既存のコントロールの動作を変更する手法です。
これを利用して、グループボックスに「WM_COMMANDメッセージを受け取ったらそのまま親ウィンドウに送信する」という動作を追加します。


#pragma comment(lib, "comctl32.lib")

#include <windows.h>
#include <commctrl.h>

//関数プロトタイプ宣言
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK GroupBoxProc(HWND, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR);

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

#define IDC_STATIC -1

#define IDC_RADIO1 100
#define IDC_RADIO2 101
#define IDC_RADIO3 102
#define IDC_RADIO4 103

//グループボックスのウィンドウプロシージャ
LRESULT CALLBACK GroupBoxProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
	switch (uMsg)
	{
	case WM_COMMAND:
		//WM_COMMANDメッセージを受け取ったら
		//そのまま親ウィンドウに送る
		SendMessage((HWND)dwRefData, WM_COMMAND, wParam, lParam);
		break;
	}
	return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND hGroup1, hGroup2;
	HWND h;
	
	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//グループボックス1
		hGroup1 = CreateWindow(
			L"BUTTON", L"グループ1",
			WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
			10, 10, 200, 60,
			hWnd, (HMENU)IDC_STATIC, hInst, NULL);
		//サブクラス化
		SetWindowSubclass(hGroup1, GroupBoxProc, 0, hWnd);

		//グループボックス2
		hGroup2 = CreateWindow(
			L"BUTTON", L"グループ2",
			WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
			10, 80, 200, 60,
			hWnd, (HMENU)IDC_STATIC, hInst, NULL);
		//サブクラス化
		SetWindowSubclass(hGroup2, GroupBoxProc, 0, hWnd);

		//ラジオボタンの作成
		//hGroup1を親に設定
		h = CreateWindow(
			L"BUTTON", L"Radio1",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			10, 25, 80, 25,
			hGroup1, (HMENU)IDC_RADIO1, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"Radio2",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			90, 25, 80, 25,
			hGroup1, (HMENU)IDC_RADIO2, hInst, NULL);
		//先頭をオンにしておく
		SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);

		//グループ2
		//hGroup2を親に設定
		h = CreateWindow(
			L"BUTTON", L"Radio3",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			10, 25, 80, 25,
			hGroup2, (HMENU)IDC_RADIO3, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"Radio4",
			WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
			90, 25, 80, 25,
			hGroup2, (HMENU)IDC_RADIO4, hInst, NULL);
		//先頭をオンにしておく
		SendMessage(h, BM_SETCHECK, BST_CHECKED, 0);
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_RADIO1:
			MessageBox(hWnd, L"ラジオボタン1をクリックしました。", L"情報", MB_OK);
			break;
		case IDC_RADIO2:
			MessageBox(hWnd, L"ラジオボタン2をクリックしました。", L"情報", MB_OK);
			break;
		case IDC_RADIO3:
			MessageBox(hWnd, L"ラジオボタン3をクリックしました。", L"情報", MB_OK);
			break;
		case IDC_RADIO4:
			MessageBox(hWnd, L"ラジオボタン4をクリックしました。", L"情報", MB_OK);
			break;
		}
		break;

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

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

こちらの方法はサブクラス化するのにひと手間かかりますが、グループ内コントロールの座標がコントロールボックス基準になるので、配置がしやすいというメリットがあります。