タブオーダーとダイアログの初期化

Tabキーによるフォーカス移動

タブストップ

Windowsは多くの操作をキーボードで行うことができます。
例えばコントロールに対するクリック動作はスペースキーやEnterキーで行うことができます。
別のコントロールへの操作切り替え(フォーカスの移動)はTabキーで行うことができます。

フォーカスとはキー入力を受け付ける状態のことです。
例えばボタンがフォーカスを持っている場合、スペースキーやEnterキーでボタンをクリックすることができます。
エディットコントロールがフォーカスを持っている場合、文字入力が可能になります。

Tabキーでコントロールがフォーカスを得るには、コントロールにWS_TABSTOPスタイルが設定されている必要があります。
これをタブストップといいます。
Visual Studioのダイアログエディタでは、プロパティの「タブ位置」から設定できます。
タブストップの設定

キーボードで操作可能なコントロールのほとんどはデフォルトでこの設定はTrueになっていますが、ラジオボタンだけはデフォルトでFalseになっています。
ラジオボタンはグループでひとつのタブストップを持ち、Tabキーでグループがフォーカスを得ると現在選択中のボタンにフォーカスがセットされます。
グループ内の別のラジオボタンへのフォーカス移動は矢印キーで可能です。

ラジオボタングループ内に選択中のボタンが無く、タブストップも設定されていない場合、Tabキーでグループにフォーカスを移動することはできません。
とはいえ、ラジオボタンはグループ内のどれかひとつが常に選択中であることが想定されるコントロールなので、タブストップを設定するよりもダイアログの初期化処理でいずれかのボタンを選択状態にしておくのが良いでしょう。
ダイアログの初期化処理については後述します。

タブオーダー

Tabキーでフォーカスを移動する際、移動の順序はタブオーダーで決定されます。
タブオーダーはVisual Studioではメニューバーの「書式」→「タブオーダー」(またはCtrl + D)から設定できます。
タブオーダーの設定

上図ではダイアログエディタ上に1~8の番号が割り振られています。
コントロールをクリックすると、クリックした順序通りに番号が変更されます。
この番号順にTabキーでフォーカスが移動します。
終了する場合は何もないところをクリックするか、メニューから再度「タブオーダー」を選択します。

タブオーダーはタブストップ(WS_TABSTOPスタイル)が設定されていないコントロールや、フォーカスが持てないコントロールにも設定することができます。
これらのコントロールにはフォーカスは移動しませんが、タブオーダーの設定はリソーススクリプト上でコントロールが記述される順序の設定と同じです。
つまりタブオーダーはコントロールのグループ化にも影響します。

ダイアログの初期化処理

WM_INITDIALOGメッセージ

ダイアログボックスはあらかじめ作成しておいたリソースを読み込んで使用します。
ダイアログウィンドウやコントロール類はプロパティからあらかじめ色々な設定が可能ですが、ここで設定できないものはプログラム実行時に初期化処理を行います。
ウィンドウの場合はWM_CREATEメッセージでウィンドウの初期化処理をしますが、ダイアログの場合はWM_INITDIALOGメッセージで行います。
これはダイアログの作成時に一度だけ送られてくるメッセージです。

WPARAMにはキーボードフォーカスを受け取る既定のコントロールのハンドルが格納されています。
WM_INITDIALOGメッセージの処理時にTRUEを返すことで、このコントロールにフォーカスがセットされます。
既定のフォーカスをセットしない場合はFALSEを返します。
ダイアログプロシージャでは「メッセージを処理したらTRUE、処理しない場合はFALSEを返す」という決まりでしたが、このメッセージでは戻り値の扱い方が異なるので注意が必要です。

LPARAMはダイアログ作成関数から渡される追加の初期化データです。
通常は使用しません。

DialogBoxマクロCreateDialogマクロでダイアログを作成した場合、WM_INITDIALOGメッセージのLPARAMは常に0です。
それぞれのマクロが実際に呼び出す関数(DialogBoxParam関数やCreateDialogParam関数)を使用することで、ダイアログプロシージャがWM_INITDIALOGメッセージを受け取ったときに任意のデータを渡すことができます。

SetFocus関数

ダイアログを開いたとき、既定のフォーカスはフォーカスが持てるコントロールのうち「表示状態」「無効ではない」「タブストップが設定されている」コントロールのうち、タブオーダーの番号が最も若い(小さい)コントロールに設定されます。

WM_INITDIALOGメッセージでTRUEを返すとこのコントロールにフォーカスがセットされますが、別のコントロールにセットしたい場合はSetFocus関数を使用します。
(もちろん他のメッセージの処理時にも使用できます)

HWND SetFocus(
 HWND hWnd
);
キーボードフォーカスを、同じスレッド内のウィンドウhWndに設定する。
戻り値は以前にフォーカスを持っていたウィンドウ。
失敗した場合はNULLを返す。

WM_INITDIALOGメッセージ内でこの関数を使用する場合、ダイアログプロシージャの戻り値にはFALSEを指定する必要があります。

GetFocus関数

現在フォーカスを持っているウィンドウ(コントロール)を取得する場合はGetFocus関数を使用します。

HWND SetFocus();
同じスレッド内のキーボードフォーカスを持つウィンドウのハンドルを返す。
失敗した場合はNULLを返す。

SetFocus関数およびGetFocus関数は、同じスレッド内のウィンドウに対してのみ有効です。
別のアプリケーションに対してフォーカスを設定/取得することはできません。

サンプルコード


//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ で生成されたインクルード ファイル。
// Resource.rc で使用
//
#define IDD_DIALOG1                     101
#define IDC_CHECK1                      1001
#define IDC_EDIT1                       1002

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DIALOG1 DIALOGEX 0, 0, 200, 70
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,144,48,50,14
    CONTROL         "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,6,39,10
    EDITTEXT        IDC_EDIT1,6,18,186,14,ES_AUTOHSCROLL
END

#include <windows.h>
#include "resource.h"

//ダイアログハンドル
HWND hDlg;

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

#define IDC_BUTTON1 100

//チェックボックスの状態
BOOL check1;

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

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			//ダイアログボックスの呼び出し
			DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);
			break;
		}
		break;
	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

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

//ダイアログプロシージャ
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
	case WM_INITDIALOG: //ダイアログの初期化
		//チェックボックスの状態を復元
		CheckDlgButton(hWnd, IDC_CHECK1, check1);

		//チェックボックスの状態によって
		//エディットコントロールのテキストを変える
		switch (check1)
		{
		case BST_CHECKED:
			SetDlgItemText(hWnd, IDC_EDIT1, L"チェック-オン");
			break;
		case BST_INDETERMINATE:
			SetDlgItemText(hWnd, IDC_EDIT1, L"チェック-あいまい");
			break;
		default:
			SetDlgItemText(hWnd, IDC_EDIT1, L"チェック-オフ");
			break;
		}

		//チェックボックスにフォーカスをセット
		SetFocus(GetDlgItem(hWnd, IDC_CHECK1));
		//既定のフォーカスを使用しないのでFALSEを返す
		return FALSE;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_CHECK1:
			check1 = IsDlgButtonChecked(hWnd, IDC_CHECK1);

			//チェックボックスの状態によって
			//エディットコントロールのテキストを変える
			switch (check1)
			{
			case BST_CHECKED:
				SetDlgItemText(hWnd, IDC_EDIT1, L"チェック-オン");
				break;
			case BST_INDETERMINATE:
				SetDlgItemText(hWnd, IDC_EDIT1, L"チェック-あいまい");
				break;
			default:
				SetDlgItemText(hWnd, IDC_EDIT1, L"チェック-オフ");
				break;
			}
			return FALSE;

		case IDOK:
		case IDCANCEL:
			EndDialog(hWnd, IDOK);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

WM_INITDIALOGメッセージ内でSetFocus関数を使用し、チェックボックスにフォーカスをセットしています。
ダイアログはチェックボックスにフォーカスが当たっている状態で開かれるので、そのままスペースキーを押すとチェックのオン/オフが可能です。
エディットコントロールの内容はチェックをオン/オフする度に変わります。
ダイアログを閉じ、再度開くと以前のチェックの状態が復元されます。
WM_INITDIALOGメッセージのサンプル