ダイアログ

ウィンドウアプリはメインとなるウィンドウを持つのが基本ですが、必要に応じて複数のウィンドウを表示することもできます。
(→複数のウィンドウを持つアプリ)
しかし、ちょっとした情報の表示や設定ウィンドウとして使うだけならばわざわざもう一枚分のウィンドウを定義するのは面倒です。
メッセージボックスで物足りない場合は自前のダイアログ(ダイアログボックス)を作成できます。

ダイアログの作成

ダイアログはリソースファイルを使用して作成します。
まずはリソースファイルの項を参考にしてリソースファイルを追加します。

次にリソースの追加ダイアログで「Dialog」を選択して新規作成します。
Dialogリソースの新規作成

すると以下のようなダイアログの編集画面が開きます。
ダイアログエディタ

Visual Studioではダイアログをグラフィカルな編集画面で作成することができます。
左上の「ツールボックス」内にコントロール類の一覧があり、ここからコントロールを選択してダイアログ上をクリック(またはドラッグ&ドロップ)することでダイアログ上に配置することができます。
ツールボックス

今回は「Static Text」を配置します。
これはスタティックコントロールです。

配置したコントロールは右クリック→「プロパティ」(またはコントロールを選択状態で「F4」キー)から詳細な設定を行うことができます。
(ダイアログウィンドウ自体もプロパティでいろいろ設定できます)
コントロールのプロパティ

プロパティペインではコントロールのID(識別子)や見た目などを変更できます。
スタティックコントロールなどのテキスト情報は「キャプション」から変更できます。
さらに今回は「テキストの配置」を「Center」に変更してテキストを中央揃えにしてみます。
(下の画像は配置したスタティックコントロールの横幅をウィンドウ一杯に広げています)
キャプションとテキストの配置の変更
プロパティペインはよく使用するので、右上のピン止めアイコンをオンにして常に表示しておくと便利かもしれません。

また、最初から配置されている「OK」と「キャンセル」ボタンのIDも確認しておきましょう。
それぞれ「IDOK」と「IDCANCEL」になっていると思います。
これは後に使用します。
OKボタンとキャンセルボタンのIDを確認

これらのボタンは必要が無ければ削除しても構いません。
コントロールの削除はコントロールを選択して「Del」キーです。

グリッド表示

コントロールを配置する場合、グリッド表示にすると各コントロールを綺麗に整列しやすくなります。
(このアイコンが表示されていない場合は「表示」メニュー→「ツールバー」→「ダイアログエディター」をオンにすると表示されます)
グリッド表示に切り替え

グリッド表示はコントロールの配置やサイズの変更時に一定間隔でスナップするようになります。
要するに位置やサイズなどの調整が簡単になります。
グリッド表示

ダイアログのテスト

ダイアログはプログラムを実際に実行する前にその表示をテストすることができます。
ダイアログのテストアイコン

上記アイコンをクリック(または「Ctrl + T」)すると、プログラムから呼び出した時と同じ見た目でダイアログが表示されます。
ダイアログのテスト

今回はウィンドウサイズとコントロールを以下のように調整しました。
ダイアログの調整

リソースファイルは以下のようになります。


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

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

IDD_DIALOG1 DIALOGEX 0, 0, 200, 70
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,90,48,50,14
    PUSHBUTTON      "キャンセル",IDCANCEL,144,48,50,14
    CTEXT           "ダイアログボックスのテストです",IDC_STATIC,6,12,185,8
END

ダイアログの呼び出し

DialogBoxマクロ

プログラムからダイアログボックスを呼び出すにはDialogBoxマクロを使用します。

INT_PTR DialogBoxW(
 HINSTANCE hInstance,
 LPCWSTR lpTemplate,
 HWND hWndParent,
 DLGPROC lpDialogFunc
);
モジュールhInstance内のダイアログボックステンプレートlpTemplateからモーダルダイアログボックスを作成し、オーナーをウィンドウhWndParentとする。
戻り値はEndDialog関数で指定された値。
hWndParentが無効なハンドルな場合は0を返す。
それ以外の理由で関数が失敗した場合は-1を返す。

これは関数ではなくDialogBoxParam関数を呼び出すマクロとして定義されています。

第一引数hInstanceはダイアログテンプレート(次の引数)が含まれる実行ファイルのインスタンスを指定します。
NULLを指定すると現在の実行ファイルのインスタンスを指定したことになります。

第二引数lpTemplateはダイアログテンプレートというものですが、これは先ほど作成したダイアログリソースの識別子をMAKEINTRESOURCEマクロを使用して指定します。

MAKEINTRESOURCEマクロを使用する理由についてはリソースを指定する識別子および文字列についてを参照してください。

第三引数hWndParentはダイアログボックスの所有者となるウィンドウです。
このダイアログボックスはメッセージボックスと同じくモーダルダイアログボックスで、ダイアログを閉じるまで所有者ウィンドウに制御を返しません。
(処理が一時的に停止する)

第四引数lpDialogFuncダイアログプロシージャです。
ダイアログボックスはウィンドウと同じように、メッセージを処理する専用のコールバック関数を持ちます。
その関数をここで指定します。

戻り値は後述するEndDialog関数で決定します。

ダイアログプロシージャ

ダイアログプロシージャの定義はウィンドウプロシージャとよく似ています。

INT_PTR CALLBACK DlgProc(
 HWND,
 UINT,
 WPARAM,
 LPARAM
){}
ダイアログプロシージャの関数定義。

戻り値の型が異なる以外はウィンドウプロシージャと同じです。
関数名も自由です。

ダイアログプロシージャは、ダイアログ上に配置したコントロールから送られてきたメッセージを処理します。
基本的にはウィンドウプロシージャと同じで、例えばボタンなどのクリックはWM_COMMANDメッセージが送信され、WPARAMの下位ワードにボタンID(識別子)が格納されています。

ウィンドウプロシージャと異なるのは戻り値の指定です。
ウィンドウプロシージャの場合はメッセージを処理した場合は0を返し、処理しない場合はDefWindowProc関数を実行して返す、という決まりでした。
ダイアログプロシージャの場合は、メッセージを処理した場合はTRUEを返し、それ以外の場合はFALSEを返す決まりになっています。
FALSEを返すと、システムがダイアログのデフォルト動作を実行します。
DefWindowProc関数は実行しないでください。

戻り値はINT_PTR型ですが、BOOL型で定義しても問題ありません。

EndDialog関数

もう一つ、EndDialog関数も使用します。
これは名前の通りダイアログボックスを終了させます。

BOOL EndDialog(
 HWND hDlg,
 INT_PTR nResult
);
ダイアログhDlgを戻り値nResultで終了する。
成功した場合は0以外を、失敗した場合は0を返す。

これはダイアログプロシージャ内で使用する関数です。
ダイアログプロシージャの第一引数HWNDにはダイアログのハンドルが格納されています。
これをそのままEndDialog関数の第一引数hDlgに渡すことでダイアログを終了できます。

第二引数nResultは、DialogBoxマクロの戻り値となる値です。
(この関数の戻り値ではありません)
例えばダイアログボックスを閉じる際にクリックされたボタンの種類などを格納できます。

サンプルコード

具体的なサンプルコードを示します。


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

//ウィンドウプロシージャのプロトタイプ宣言
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

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

#define IDC_BUTTON1 100

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static WCHAR *txt;
	HDC hdc;
	PAINTSTRUCT ps;
	int ret;

	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:
			//ダイアログボックスの呼び出し
			ret = DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);
			if (ret == IDOK)
				txt = L"IDOK";
			else
				txt = L"IDCANCEL";
			InvalidateRect(hWnd, NULL, TRUE);
			break;
		}
		break;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 10, 50, txt, lstrlen(txt));
		EndPaint(hWnd, &ps);
		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_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDOK:
			EndDialog(hWnd, IDOK);
			return TRUE;
		case IDCANCEL:
			EndDialog(hWnd, IDCANCEL);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

「Open Dialog」ボタンをクリックするとダイアログボックスが表示されます。
「OK」「キャンセル」ボタンをクリックするとダイアログボックスが閉じられ、メインウィンドウにはクリックしたボタンの情報が表示されます。
ちなみに右上の×ボタンをクリックした場合も「IDCANCEL」が送られてきます。
ダイアログボックスのサンプルコード