モードレスダイアログ

ダイアログの項で作成したダイアログはモーダルダイアログボックスというもので、ダイアログが閉じられるまでは所有者ウィンドウに制御を返さないものでした。
これに対して、ダイアログを開いている状態でも所有者ウィンドウを操作できるダイアログをモードレスダイアログボックスといいます。

モードレスダイアログボックスの作成

CreateDialogマクロ

モードレスダイアログはCreateDialogマクロを使用して作成します。

HWND CreateDialogW(
 HINSTANCE hInstance,
 LPCWSTR lpTemplateName,
 HWND hWndParent,
 DLGPROC lpDialogFunc
);
モジュールhInstance内のダイアログボックステンプレートlpTemplateNameからモードレスダイアログボックスを作成し、オーナーをウィンドウhWndParentとする。
戻り値は作成したダイアログのハンドル。
関数が失敗した場合はNULLを返す。

これは関数ではなくCreateDialogParam関数を呼び出すマクロです。

このマクロの引数はDialogBoxマクロと同じです。
使用するダイアログボックステンプレートはモーダルダイアログボックスと同じものを使用できます。

モードレスダイアログはモーダルダイアログとは違い、作成した後すぐに制御を返します。
戻り値は作成したダイアログのハンドルです。

ダイアログの表示状態

モードレスダイアログは初期状態では不可視です。
作成後にShowWindow関数を使用して表示するか、ダイアログテンプレードで初期表示状態を「True」に変更しておく必要があります。
表示状態はダイアログのプロパティから変更できます。
ダイアログテンプレートの初期表示状態の変更

メッセージの処理

モーダルダイアログの場合は何もしなくてもダイアログプロシージャにメッセージが送られて来ましたが、モードレスダイアログの場合はメッセージをダイアログに送るための処理を追加する必要があります。
モードレスダイアログにメッセージを送信するにはIsDialogMessage関数を使用します。

BOOL IsDialogMessageW(
 HWND hDlg,
 LPMSG lpMsg
);
メッセージlpMsgがダイアログhDlgへのメッセージかを判断し、真の場合にメッセージを処理する。
メッセージが処理された場合は0以外を返し、処理されない場合は0を返す。

この関数はメッセージループで使用します。


//ダイアログハンドル
//この変数にCreateDialogマクロの戻り値を格納する
HWND g_hDlg;

int APIENTRY wWinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPWSTR lpCmdLine,
	int nCmdShow)
{
	MyRegisterClass(hInstance);
	if (!InitInstance(hInstance, nCmdShow))
	{
		return 0;
	}

	HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));

	MSG msg;
	BOOL ret;

	//メッセージループ
	while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
	{
		if (ret == -1)
		{
			//GetMessage関数の実行失敗
			return -1;
		}
		if(!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)
			&& (!g_hDlg || !IsDialogMessage(g_hDlg, &msg)))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);		
		}
	}

	return (int)msg.wParam;
}

アクセラレータを使用しない場合はLoadAccelerators関数およびTranslateAccelerator関数の処理は必要ありません。

上記コードの条件判定がややこしい場合は分割して記述してもかまいません。


while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
	if (ret == -1)
	{
		//GetMessage関数の実行失敗
		return -1;
	}
	if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
	{
		if (g_hDlg && IsDialogMessage(g_hDlg, &msg))
			continue;

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

//アクセラレータがない場合
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
	if (ret == -1)
	{
		//GetMessage関数の実行失敗
		return -1;
	}
	if (!g_hDlg || !IsDialogMessage(g_hDlg, &msg))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

ダイアログボックスハンドル(変数g_hDlg)はWinMain関数やウィンドウプロシージャなどから使用されるため、グローバル変数として定義しておきます。

IsDialogMessage関数のダイアログハンドルにはNULLを渡すことはできないので、関数の実行前に変数のNULLチェックが必要がです。

IsDialogMessage関数はメッセージの判定とメッセージの処理を行います。
メッセージが処理された場合(真を返す場合)はTranslateMessage関数DispatchMessage関数は実行してはいけません。

ダイアログプロシージャ

ダイアログプロシージャ内の処理は基本的にモーダルダイアログボックスと同じです。
ただし、ダイアログを閉じる際はEndDialog関数ではなくDestroyWindow関数を使用します。

サンプルコード

このコードはボタンをひとつ作成し、クリック時にCreateDialog関数を呼び出しモードレスダイアログを作成します。


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

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

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

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

#define IDC_BUTTON1 100
WCHAR* g_strDlgState = L"Close";

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

	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:
			//NULLでなければアクティブにする
			if (g_hDlg)
			{
				SetActiveWindow(g_hDlg);
				break;
			}

			//モードレスダイアログボックスの呼び出し
			g_hDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);
			g_strDlgState = L"Open";
			InvalidateRect(hWnd, NULL, TRUE);
			break;
		}
		break;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 10, 50, g_strDlgState, lstrlen(g_strDlgState));
		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:
		case IDCANCEL:
			DestroyWindow(hWnd);
			g_hDlg = NULL;
			g_strDlgState = L"Close";
			InvalidateRect(GetParent(hWnd), NULL, TRUE);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

モードレスダイアログはすぐに所有者ウィンドウに制御を返すので、ダイアログを呼び出すボタンを何度でも押すことが出来てしまいます。
そのたびにダイアログが開くのは不都合なので、ダイアログハンドルをチェックしてNULLならばダイアログを開くようにしています。
NULLで無かった場合はダイアログをアクティブにします。

ダイアロブプロシージャでは自身の破棄の処理しかしていませんが、ダイアログが閉じられた後はグローバル変数g_hDlgに格納されているハンドルは無効になるので、NULLを代入しておきます。

今回はモーダルダイアログで作成したダイアログテンプレートをそのまま流用したので、「IDOK」「IDCANCEL」のふたつのボタンがありますが、どちらを押しても同じ処理を行うようにしています。
モードレスダイアログではどのボタンを押してダイアログを閉じたか、という情報を返すことはできないので別の手段を用いる必要があります。

ダイアログが表示されない場合、ダイアログエディタのプロパティで表示状態が「True」になっているかを確認してください。
(→ダイアログの表示状態)

GetParent関数

今回は文字列型のグローバル変数(g_strDlgState)を使用して、ダイアログの状態をメインウィンドウに表示しています。
メインウィンドウを再描画するためにメインウィンドウのハンドルが必要になりますが、これはGetParent関数に子ウィンドウハンドルを渡すことで取得できます。

HWND GetParent(
 HWND hWnd
);
ウィンドウhWndの親ウィンドウまたは所有者ウィンドウのハンドルを取得する。
失敗した場合はNULLを返す。