Programming Field - プログラミング Tips

ダイアログ: メモリ上にテンプレートを作成

ダイアログ ボックスは、普通はリソースから作成するか、システムが提供するメッセージボックスを表示させる以外では作成できません。しかし、リソースのデータと同じダイアログ テンプレートを作成することによって、動的にダイアログを作成することが出来ます。

Win16 版のものは Win32 のとは異なります。「Win16 でメモリ上にテンプレートを作成」をご覧下さい

ダイアログ テンプレート

ダイアログ テンプレートは、DLGTEMPLATE 構造体を先頭とするバイナリ データです。メモリ上で DLGTEMPLATE の直後にはメニュー、キャプション (タイトルバーの文字列) や、必要があればフォントデータなどが続きます。

その後には、コントロールの数だけ DLGITEMTEMPLATE 構造体が来ます。その後にコントロールのクラス名、テキスト、データが続きます。

以下は DLGTEMPLATE、DLGITEMTEMPLATE 構造体の定義です。

typedef struct
{
    DWORD style;
    DWORD dwExtendedStyle;
    WORD cdit;
    short x;
    short y;
    short cx;
    short cy;

    // <Menu ID>;
    // <Window Class>;
    // <Dialog Title>;
    // WORD fontSize;
    // WCHAR fontName[];
    // DLGITEMTEMPLATE controls[cdit];
} DLGTEMPLATE, FAR* LPDLGITEMPLATE;
typedef struct
{
    // BYTE padding[];
    DWORD style;
    DWORD dwExtendedStyle;
    short x;
    short y;
    short cx;
    short cy;
    WORD id;

    // <Window Class>;
    // <Control Text>;
    // WORD SizeOfCreationData;
    // BYTE CreationData[SizeOfCreationData];
} DLGITEMTEMPLATE, FAR* LPDLGITEMTEMPLATE;

※ 上記の定義でコメントアウトしている部分は、実際には設定する内容によって型やサイズ・メモリ上での位置が変わってしまうため、明確な定義をすることが出来ません。

構造体のメンバ

style
ダイアログ、またはコントロールのスタイルです。(WS_ 、DS_ など)
dwExtendedStyle
ダイアログ、またはコントロールの拡張スタイルです。(WS_EX_ など)
cdit
ダイアログが持つコントロールの数です。(Count of DLGITEMTEMPLATE の略)
x, y, cx, cy
ダイアログ、またはコントロールのサイズです。このサイズはダイアログベース単位で指定する必要があります。ピクセルからこの単位に変換するには、GetDialogBaseUnits 関数を使う方法がありますが、本来はフォントのサイズを計算に含める必要があり、おすすめできません。
id
コントロールの ID です。なお、IDOK (= 1) を指定し、スタイルに BS_DEFPUSHBUTTON を指定すると既定のボタン (エディットコントロールで Enter をしたときに自動的に押されるボタン) に、IDCANCEL (= 2) を指定するとキャンセルボタン (ESC キーを押すと自動的に押されるボタン) になります。
<Menu ID>
メニューリソースの ID を指定します。整数値で指定する場合は、このメンバは WORD[2] となり、最初に 0xFFFF、2 番目に ID を指定します。
ID を文字列で指定する場合は、このメンバは NULL で終わる Unicode 文字列となります。NULL 文字の次の位置が、次のメンバの始点となります。
指定しない場合 (メニュー無し) は、WORD サイズで 0 を指定します。(空の文字列と同じ)
<Window Class>
ウィンドウのクラスを指定します。指定方法は <Menu ID> と同じです。
ここでも整数値が指定できます。最初に 0xFFFF を指定した後、以下の値を指定すると、クラス名の文字列を入れなくてもそのクラスで作成できます。
コントロール
0x0080ボタン (Button)
0x0081エディットコントロール (Edit)
0x0082スタティックコントロール (Static)
0x0083リストボックス (ListBox)
0x0084スクロールバー (ScrollBar)
0x0085コンボボックス (ComboBox)
<Dialog Title><Control Text>
ダイアログのタイトル、コントロールのテキストを指定します。指定方法は <Menu ID> と同じです。
ダイアログのタイトルの場合は文字列のみですが、コントロールのテキストの場合、最初が 0xFFFF なら、このメンバは WORD[2] となり、次に来る WORD 値 (最初の値は左の通り 0xFFFF) は ID です。この ID はスタティック コントロールなどで、画像を表示する際にリソースから読み込む時のリソース ID となります。
fontSize
フォントのサイズをポイント単位で指定します。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません
fontName
フォントの名前を、NULL で終わる Unicode 文字列で指定します。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません
padding
次のメンバ (ここでは style) が「DWORD 境界」(16 進数で下 1 桁が 0、4、8、C となるような位置) に来るようにサイズを調節します。メモリのスタート地点が DWORD 境界である場合、次のコードで行えます。(pb はメモリ上のデータで、コントロールデータの最初の位置にあるとします。)
pb = (BYTE FAR*) (((DWORD_PTR) pb + 3) & ~((DWORD_PTR) 3));
SizeOfCreationData, CreationData
WM_CREATE メッセージの lParam のデータで渡される任意のデータを指定します。SizeOfCreationData はそのサイズで、0 を指定した場合は CreationData は存在しません。なお、SizeOfCreationData には SizeOfCreationData 分のサイズ (sizeof(WORD)) も含みます

ダイアログ テンプレート拡張版

また、拡張版として DLGTEMPLATEEX、DLGITEMTEMPLATEEX 構造体も存在します。Windows 95、Windows NT 3.51 以降で使用できます。(DLGTEMPLATE は Windows NT 3.1 で使用可)

typedef struct
{
    WORD dlgVer;
    WORD signature;
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    WORD cDlgItems;
    short x;
    short y;
    short cx;
    short cy;

    // <Menu ID>;
    // <Window Class>;
    // <Dialog Title>;
    // WORD fontSize;
    // WORD fontWeight;
    // BYTE fontItalic;
    // BYTE fontCharset;
    // WCHAR fontName[];
    // DLGITEMTEMPLATEEX controls[cDlgItems];
} DLGTEMPLATEEX, FAR* LPDLGTEMPLATEEX;
typedef struct
{
    // BYTE padding[];
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    short x;
    short y;
    short cx;
    short cy;
    DWORD id;

    // <Window Class>;
    // <Control Text>;
    // WORD SizeOfCreationData;
    // BYTE CreationData[SizeOfCreationData];
} DLGITEMTEMPLATEEX, FAR* LPDLGITEMTEMPLATEEX;

※ 上記の定義でコメントアウトしている部分は、実際には設定する内容によって型やサイズ・メモリ上での位置が変わってしまうため、明確な定義をすることが出来ません。

構造体のメンバ

dlgVer
ダイアログテンプレートのバージョンを指定します。この値は 1 になります。
signature
このダイアログテンプレートが拡張版かどうかを示すフラグを指定します。。この値は 0xFFFF になります。(これが 0xFFFF でない場合、このテンプレートは拡張版ではありません (つまり DLGTEMPLATE 構造体)。)
helpID
WM_HELP メッセージの HELPINFO 構造体で使用されるヘルプ ID を指定します。詳しくは WM_HELP、HELPINFO 構造体を調べてください。
exStyle
ダイアログ、またはコントロールの拡張スタイルです。(WS_EX_ など)
style
ダイアログ、またはコントロールのスタイルです。(WS_ 、DS_ など)
cDlgItems
ダイアログが持つコントロールの数です (DLGTEMPLATE 構造体の cdit に同じ)。
x, y, cx, cy
ダイアログ、またはコントロールのサイズです (DLGTEMPLATE、DLGITEMTEMPLATE 構造体と同じ)。
id
コントロールの ID です (DLGTEMPLATE 構造体と同じ)。ただし、サイズは DWORD で、0x10000 (65536) を超える ID も設定可能です。
※ マイクロソフトのドキュメントでは WORD 値になっていますが、それだと正しく動きません。
<Menu ID>
メニューリソースの ID を指定します (DLGTEMPLATE 構造体と同じ)。
<Window Class>
ウィンドウのクラスを指定します (DLGTEMPLATE、DLGITEMTEMPLATE 構造体と同じ)。
<Dialog Title><Control Text>
ダイアログのタイトル、コントロールのテキストを指定します (DLGTEMPLATE、DLGITEMTEMPLATE 構造体と同じ)。
fontSize
フォントのサイズをポイント単位で指定します (DLGTEMPLATE 構造体と同じ)。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません
fontWeight
フォントのウェイト (標準は FW_NORMAL (= 400)、太字は FW_BOLD (= 700) ) を指定します (LOGFONT 構造体の lfWeight に同じ)。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません。また、どのような値を指定しても、FW_NORMAL に変更されるそうです。
fontItalic
フォントがイタリック (斜体) かどうかを指定します (LOGFONT 構造体の lfItalic に同じ)。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません
fontCharset
フォントの文字セットを指定します (LOGFONT 構造体の lfCharset に同じ)。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません
fontName
フォントの名前を、NULL で終わる Unicode 文字列で指定します。ただし、DS_SETFONT または DS_SHELLFONT スタイルが無い場合、このメンバを指定することは出来ません
padding
次のメンバ (ここでは helpID) が「DWORD 境界」に来るようにサイズを調節します (DLGITEMTEMPLATE 構造体とほぼ同じ ← 次に来るメンバが違うだけ)。
SizeOfCreationData, CreationData
WM_CREATE メッセージの lParam のデータで渡される任意のデータを指定します (DLGITEMTEMPLATE 構造体と同じ)。ただし、SizeOfCreationData には SizeOfCreationData 分のサイズ (sizeof(WORD)) は含まれません

作成例

ここでは、DLGTEMPLATEEX、DLGITEMTEMPLATEEX 構造体を使った例を示します。(→ DLGTEMPLATE、DLGITEMTEMPLATE 構造体を使った例)

// ※ DLGTEMPLATEEX、DLGITEMTEMPLATEEX は元々定義されていません。
//   上記の定義をヘッダーなどに含めてください。

    HGLOBAL hgbl;
    LPDLGTEMPLATEEX lpdtex;
    LPDLGITEMTEMPLATEEX lpditex;
    LPWORD lpw;
    LPBYTE lpb;
    LPWSTR lpwsz;
    int nChar;

    // 実際は、サイズはデータが収まりきるように
    // あらかじめ計算しておく必要があります。
    // GMEM_ZEROINIT であらかじめデータを 0 に設定しておきます。
    // (省略されているメンバは 0 になります)
    hgbl = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, 1024);
    if (!hgbl)
        ErrorAndExit();

    // ダイアログボックスの初期値を設定します。
    // ※ hgbl と lpdtex のアドレスは同じになります (GMEM_FIXED のため)。
    lpdtex = (LPDLGTEMPLATEEX) GlobalLock(hgbl);
    lpdtex->dlgVer = 1;
    lpdtex->signature = 0xFFFF;
    lpdtex->style = WS_POPUP | WS_BORDER | WS_SYSMENU
                     | DS_MODALFRAME | WS_CAPTION | DS_SETFONT;  // フォント付き
    lpdtex->cDlgItems = 2;  // コントロールの数
    lpdtex->x = 10;   lpdtex->y = 10;
    lpdtex->cx = 100; lpdtex->cy = 100;

    // 次のメンバの設定に移ります。
    lpw = (LPWORD) (lpdtex + 1);

    // <Menu ID>
    //   *lpw++ = の文は、その位置に値を設定した後 ++ を行います。
    //   (つまり *lpw = 0; lpw++; と同じ)
    //   正式には lpw++ がインクリメントする前のアドレスを返し、
    //   そのアドレスに対して *XXX = 0 を行っています。
    *lpw++ = 0;

    // <Window Class>
    *lpw++ = 0;

    // <Dialog Title>
    lpwsz = (LPWSTR) lpw;

    // MultiByteToWideChar 関数で ANSI 文字列を Unicode 文字列に変換します。
    // 戻り値 (書き込んだ文字列の長さ) に NULL 文字が含まれているので + 1 しません。
    nChar = MultiByteToWideChar(CP_ACP, 0, "ダイアログ テスト", -1, lpwsz, 50);

    // lpw = (LPWORD) lpwsz は必要ありません (アドレスが同じであるため)。
    lpw += nChar;

    // WORD fontSize
    // ※ DS_SETFONT を指定しなかった場合、コントロールの設定に
    //   移るまでのデータ設定は行いません。
    *lpw++ = 9;

    // WORD fontWeight
    *lpw++ = FW_NORMAL;

    // BYTE fontItalic、BYTE fontCharset
    //   2 つをまとめて MAKEWORD マクロを使っても設定できます。
    //     *lpw++ = MAKEWORD(FALSE, DEFAULT_CHARSET)
    lpb = (LPBYTE) lpw;
    *lpb++ = FALSE;
    *lpb++ = DEFAULT_CHARSET;
    lpw = (LPWORD) lpb;

    // WCHAR fontName[]
    lpwsz = (LPWSTR) lpw;
    nChar = MultiByteToWideChar(CP_ACP, 0, "MS P明朝", -1, lpwsz, 50);
    lpw += nChar;

    // コントロールの設定に移ります。
    //   移る前に DWORD 境界に揃えます。
    lpw = (LPWORD) (((DWORD_PTR) lpw + 3) & ~((DWORD_PTR) 3));
    lpditex = (LPDLGITEMTEMPLATEEX) lpw;

    // OK ボタンを作ります。
    lpditex->x = 10;  lpditex->y = 70;
    lpditex->cx = 60; lpditex->cy = 14;
    lpditex->id = IDOK;  // OK ボタンの ID
    lpditex->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON;

    // 次のメンバの設定に移ります。
    lpw = (LPWORD) (lpditex + 1);

    // <Window Class>
    //   これでも構いません。
    //     lpwsz = (LPWSTR) lpw;
    //     nChar = MultiByteToWideChar(CP_ACP, 0, "Button", -1, lpwsz, 50);
    //     lpw += nChar;
    *lpw++ = 0xFFFF;  // 数値指定に必要
    *lpw++ = 0x0080;  // ボタンのウィンドウクラスを指定します。

    // <Control Text>
    lpwsz = (LPWSTR) lpw;
    nChar = MultiByteToWideChar(CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw += nChar;

    // WORD SizeOfCreationData
    //   特にデータは無いため、0 で終わります。
    *lpw++ = 0;

    // 次のコントロールの設定に移ります。
    lpw = (LPWORD) (((DWORD_PTR) lpw + 3) & ~((DWORD_PTR) 3));
    lpditex = (LPDLGITEMTEMPLATEEX) lpw;

    lpditex->x = 10;  lpditex->y = 10;
    lpditex->cx = 40; lpditex->cy = 9;
    lpditex->id = IDC_STATIC;  // スタティックコントロールによくある ID (IDC_STATIC = -1)
    lpditex->style = WS_CHILD | WS_VISIBLE | SS_LEFT;

    lpw = (LPWORD) (lpditex + 1);

    // <Window Class>
    //   これでも構いません。
    //     lpwsz = (LPWSTR) lpw;
    //     nChar = MultiByteToWideChar(CP_ACP, 0, "Static", -1, lpwsz, 50);
    //     lpw += nChar;
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0082;  // スタティックコントロールのウィンドウクラスを指定します。

    // <Control Text>
    lpwsz = (LPWSTR) lpw;
    nChar = MultiByteToWideChar(CP_ACP, 0, "メッセージ", -1, lpwsz, 50);
    lpw += nChar;

    // WORD SizeOfCreationData
    //   特にデータは無いため、0 で終わります。
    *lpw++ = 0;

    // データ設定は終了しました。ロックを解除します。
    GlobalUnlock(hgbl);

    // DialogBoxIndirect を呼び出して作成します。GMEM_FIXED なので
    // LPDLGTEMPLATE の引数には hgbl をそのまま指定します。
    // (hInst、hWndOwner、DialogProc は既に定義されているものとします。)
    DialogBoxIndirect(hInst, (LPDLGTEMPLATE) hgbl, hWndOwner, (DLGPROC) DialogProc);

    // メモリを解放します。
    GlobalFree(hgbl);

最終更新日: 2005/04/17