プロパティシート1

プロパティシート1
プロパティシート2

今回は、プロパティシートを作成します。

下図のような、ダイアログボックスのメニューから「プロパティ」を選択すると、 プロパティシートが立ち上げるようにします。

テキストの表示 - インコのWindowsSDK で作成したソースを修正します。

流れ

プロパティシートは、コモンコントロールのひとつです。コモンコントロールは他にも、ツリービューやリストビュー、ツールバーなどがあります。
コモンコントロールを使う場合は、下記1、2の両方を行って下さい。

1.commctrl.h をインクルード

2.comctl32.lib を読み込む

3.Windows 2000で動作できるアプリケーションを作成する場合は、

#define _WIN32_WINNT 0x0500

を追加。0x0500はWindows 2000を示します。
これは、後で説明するPROPSHEETPAGE構造体がWindows XPで追加されたためです。
Visual C++ 2010の場合は、作成したアプリケーションがWindows 2000で動作しないため、これを追加する必要はありません。

WM_INITDIALOG

リストボックス - インコのWindowsSDK でも説明しましたが、WM_INITDIALOGは、ダイアログが作成されたときに一度だけ実行されます。

1.InitCommonControls()でコモンコントロールの初期化
コモンコントロールを使う前にこの初期化が必要です。

WM_COMMAND

1.「プロパティ」をメニューから選択された場合、WM_COMMANDメッセージが発生します。

1.1.プロパティシートを作成します。まずは、PROPSHEETPAGE構造体をセット

typedef struct _PROPSHEETPAGE {
    DWORD dwSize;
    DWORD dwFlags;
    HINSTANCE hInstance;
    union {
        LPCSTR pszTemplate;
        LPCDLGTEMPLATE pResource;
        };
    union {
        HICON hIcon;
        LPCSTR pszIcon;
        };
    LPCSTR pszTitle;
    DLGPROC pfnDlgProc;
    LPARAM lParam;
    LPFNPSPCALLBACK pfnCallback;
    UINT *pcRefParent;
    LPCTSTR pszHeaderTitle;
    LPCTSTR pszHeaderSubTitle;
#if (_WIN32_WINNT >= 0x0501)  // Windows XP以上
    HANDLE hActCtx;
#endif

} PROPSHEETPAGE, *LPPROPSHEETPAGE;

構造体のメンバ 意味
dwSize この構造体のサイズ
sizeof(PROPSHEETPAGE)をセットします。
dwFlagsフラグ
PSP_DEFAULT構造体の全てのメンバに対し、デフォルト値を使用
PSP_HASHELP「ヘルプ」ボタンを使用可能にします
PSP_USEHICONページタブの小さなアイコンとして hIcon (ハンドル)を使用
PSP_USEICONIDページタブの小さなアイコンとして pszIcon (リソース)を使用
PSP_USETITLE ダイアログのタイトルに pszTitle を使用
デフォルトでは、ダイアログボックステンプレートのタイトルが使用
hInstance ダイアログボックステンプレートなどのリソースが格納されている モジュールのインスタンスハンドル
pszTemplateダイアログボックステンプレートのID
hIcon ページのタブの小さなアイコンとして用いるアイコンのハンドル
PSP_USEICON フラグが設定されていなければ、このメンバは無視
pszIcon ページのタブの小さなアイコンとして用いるアイコンのID
(タブのアイコンをリソースから取得する場合)
PSP_USEICONID フラグが設定されていなければ、このメンバは無視
pszTitle ページのタイトルとなる文字列、またはリソースID
PSP_USETITLE フラグが設定されていなければ、このメンバは無視
pfnDlgProcページダイアログのダイアログプロシージャへのポインタ
lParamページダイアログのダイアログプロシージャに渡したいデータ
pfnCallbackLPFNPSPCALLBACK 型コールバック関数のポインタ
このコールバック関数は、ページが作成されたり、破棄される時に呼び出されます

1.2.CreatePropertySheetPage()プロパティシートの新しいページを作成します。
この関数の引数に、PROPSHEETPAGE構造体をセットしてください。
CreatePropertySheetPage()の戻り値は、プロパティシートの作成した新しいページのハンドルですので、これをHPROPSHEETPAGE型の配列に保存します。

1.3.PROPSHEETHEADER構造体をセット

typedef struct _PROPSHEETHEADER 
{
    DWORD dwSize;
    DWORD dwFlags;
    HWND  hwndParent;
    HINSTANCE hInstance;
    union {
        HICON hIcon;
        LPCTSTR pszIcon;
        };
    LPCTSTR pszCaption;
    UINT nPages;
    union {
        UINT nStartPage;
        LPCTSTR pStartPage;
        };
    union {
        LPCPROPSHEETPAGE ppsp;
        HPROPSHEETPAGE *phpage;
        };
    PFNPROPSHEETCALLBACK pfnCallback;
    union {
        HBITMAP hbmWatermark;
        LPCTSTR pszbmWatermark;
        };
    HPALETTE hplWatermark;
    union {
        HBITMAP hbmHeader; 
        LPCSTR pszbmHeader;
        };
} PROPSHEETHEADER, *LPPROPSHEETHEADER;

構造体のメンバ 意味
dwSize この構造体のサイズ
sizeof(PROPSHEETHEADER)をセットします。
dwFlagsフラグ
PSH_DEFAULT構造体の全てのメンバに対し、デフォルト値を使用
PSH_HASHHELP「ヘルプ」ボタンを使用可能にします。
PSH_MODELESSモードレスダイアログボックスのプロパティシートを作成
PSH_MULTILINETABS複数行表示のタブを使用
PSH_NOAPPLYNOW 「適用」ボタンを削除
PSH_PROPTITLEpszCaption で指定された文字列を プロパティシートのタイトルとして使用
PSH_USECALLBACKプロパティシートを初期化する時 pfnCallbackで指定した関数を呼び出します
PSH_USEHICONプロパティシートダイアログボックスのアイコンとして hIcon (ハンドル)を使用
PSH_USEHICONプロパティシートダイアログボックスのアイコンとして pszIcon (リソース)を使用
PSH_WIZARDウィザードプロパティシートを作成
hwndParent 親ウィンドウのハンドル
hInstance リソースを含むモジュールのインスタンスハンドルを指定
リソースからアイコンなどを読み込む場合は、必ず指定
hIcon プロパティシートのタイトルバーに表示するアイコンのハンドル
PSH_USEHICON フラグが設定されていなければ、このメンバは無視
pszIcon タイトルバーに表示するアイコンのID
PSH_USEICONID フラグが設定されていなければ、このメンバは無視
pszCaption ページのタイトルとなる文字列、またはリソースID
nPages phpage メンバが指す配列内にある要素の数
nStartPage プロパティシートを作成した時、初期時に表示するページ番号
ppsp プロパティシートの各ページを定義した PROPSHEETPAGE 構造体の配列へのポインタ
phpage 各ページのハンドルの配列へのポインタを指定
pfnCallbackPFNPROPSHEETCALLBACK 型コールバック関数のポインタ
このコールバック関数は、プロパティシート初期化時に呼び出されます
hwndDlg プロパティシートダイアログボックスのハンドル

1.4.PropertySheet()でプロパティシートの作成

int PropertySheet(LPCPROPSHEETHEADER lppsph);

lppsphPROPSHEETHEADER 構造体へのポインタ

プロパティシートのコールバック関数

プロパティシートは、タブごとにコールバック関数が必要です。
コールバック関数とは、プログラム中で、呼び出し先の関数の実行中に実行されるように、あらかじめ指定しておく関数のことです。

1.WM_INITDIALOG
SetWindowText()で、表示する文字列を、テキストボックスにセット

2.WM_NOTIFY

● WM_NOTIFYメッセージとは、

WM_NOTIFYメッセージは、コモンコントロールでイベントが起こった場合、およびコモンコントロールが情報を親ウィンドウに要求する場合に、コモンコントロールの親ウィンドウに送信されます。

・WM_NOTIFYメッセージ発生時の wParam [コールバック関数の第3引数]の値・・・
イベントが発生したコモンコントロールのコントロールID
・WM_NOTIFYメッセージ発生時の lParam [コールバック関数の第4引数]の値・・・
NMHDR構造体、あるいはNMHDR構造体を最初のメンバに持つ構造体のアドレス

NMHDR構造体のidFromメンバが コントロールIDと同じかどうかで、メッセージを識別します。

typedef struct tagNMHDR {
    HWND hwndFrom;   // コントロールのハンドル
    UINT idFrom;     // コントロールID
    UINT code;       // 通知コード
} NMHDR;

WM_NOTIFYメッセージのlParamである NMHDR構造体の hwndFromメンバ、もしくは idFromメンバで、コントロールを識別します。

NMHDR構造体のcodeメンバが「通知コード」です。この通知コードが発生したメッセージになります。

・ WM_NOTIFYメッセージを使った、分かりにくい仕様になった理由

WM_からはじまるメッセージに追加しないで、WM_NOTIFYメッセージを使ったややこしい仕様になったのは、 もともとWM_からはじまるメッセージが 0x0400 までしか追加できない仕様になっており、WM_からはじまるメッセージの数が増えすぎて、追加できなくなったからだと思われます。
実際、Visual C++ 2010の WinUser.h を見てみますと、すでに0x038Fまで使用しています。

● 今回のアプリケーションでの使用方法

今回の場合、ユーザーが「OK」ボタンを押すと、ダイアログプロシージャにWM_NOTIFYメッセージが送信されます。 NMHDR構造体の通知コードは PSN_APPLYになります。

NMHDR構造体nmhdrの通知コードcode [nmhdr->code] が PSN_APPLY のとき、GetWindowText()で、テキストボックスから文字列を取得します。

int GetWindowText(
  HWND hWnd,        // ウィンドウまたはコントロールのハンドル
  LPTSTR lpString,  // テキストバッファ
  int nMaxCount     // コピーする最大文字数
);

「->」は、アロー演算子と呼ばれるものです。構造体へのポインタを参照する時は、「構造体->メンバ名」のように使います。

ソースコードの入力

ソースコードは下記のように入れてください。

test.cpp
#include <windows.h>
#include <commctrl.h>
#include "resource.h"

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

// グローバル変数:
TCHAR szBuf_edit1[64], szBuf_edit2[64];

// このコード モジュールに含まれる関数の宣言を転送します:
BOOL CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK DialogProc1(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK DialogProc2(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
          PSTR lpCmdLine, int nCmdShow )
{
    DialogBox(hInstance, TEXT("DIALOG_BOX"), NULL, DialogProc);
    return 0;
}

BOOL CALLBACK DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    UINT wmId;
    PROPSHEETPAGE psp;
    PROPSHEETHEADER psh;
    HPROPSHEETPAGE hPsp[2];
    switch (msg)
    {
        case WM_INITDIALOG:
            InitCommonControls(); 
            break;
        case WM_CLOSE:
            EndDialog(hWnd, IDOK);
            break;
        case WM_COMMAND:
            wmId = LOWORD(wParam);
            switch (wmId)
            {
                case IDM_EXIT:
                    EndDialog(hWnd, IDOK);
                    break;
                case IDM_PROP:
                    psp.dwSize = sizeof(PROPSHEETPAGE);
                    psp.dwFlags = PSP_DEFAULT | PSP_USEICONID;
                    psp.pszIcon = TEXT("IDI_TEST");
                    psp.hInstance = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);

                    psp.pszTemplate = TEXT("DIALOG1");
                    psp.pfnDlgProc = (DLGPROC)DialogProc1;
                    hPsp[0] = CreatePropertySheetPage(&psp);

                    psp.pszTemplate = TEXT("DIALOG2");
                    psp.pfnDlgProc = (DLGPROC)DialogProc2;
                    hPsp[1] = CreatePropertySheetPage(&psp);

                    psh.dwSize = sizeof (PROPSHEETHEADER);
                    psh.dwFlags = PSH_NOAPPLYNOW | PSH_USEHICON;
                    psh.hwndParent = hWnd;
                    psh.hIcon = LoadIcon(NULL, IDI_ASTERISK);
                    psh.pszCaption = TEXT("設定");
                    psh.nPages = 2;
                    psh.phpage = hPsp;
                    PropertySheet(&psh);
                    break;
                default:
                    break;
            }
        default:
            break;
    }
    return FALSE;
}

BOOL CALLBACK DialogProc1(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    NMHDR *nmhdr;

    switch(msg)
    {
        case WM_INITDIALOG:
            SetWindowText(GetDlgItem(hWnd, IDC_EDIT1), szBuf_edit1);
            break;
        case WM_NOTIFY:
            nmhdr = (NMHDR *)lParam;
            switch(nmhdr->code)
            {
                case PSN_APPLY:
                    GetWindowText(GetDlgItem(hWnd, IDC_EDIT1),
                      szBuf_edit1, sizeof(szBuf_edit1));
                    break;
                default:
                    break;
            }
            break;
    }
    return FALSE;
}

BOOL CALLBACK DialogProc2(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    NMHDR *nmhdr;

    switch(msg)
    {
        case WM_INITDIALOG:
            SetWindowText(GetDlgItem(hWnd, IDC_EDIT2), szBuf_edit2);
            break;
        case WM_NOTIFY:
            nmhdr = (NMHDR *)lParam;
            switch(nmhdr->code)
            {
                case PSN_APPLY:
                    GetWindowText(GetDlgItem(hWnd, IDC_EDIT2),
                      szBuf_edit2, sizeof(szBuf_edit2));
                    break;
                default:
                    break;
            }
            break;
    }
    return FALSE;
}

resource.h

#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDM_PROP 106
#define IDC_HP 109
#define IDC_EDIT1 1001
#define IDC_EDIT2 1002
#ifndef IDC_STATIC
#define IDC_STATIC -1
#endif

Reseditで作成した場合は、これとは異なるソースコードになります。
上記の太線で示している箇所のみ追加です。

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

/////////////////////////////////////////////////////////////////////////////
//
// アイコン
//

IDI_TEST ICON "test.ico"

/////////////////////////////////////////////////////////////////////////////
//
// メニュー
//

IDC_HP MENU
BEGIN
    POPUP "ファイル(&F)"
    BEGIN
        MENUITEM "アプリケーションの終了(&X)", IDM_EXIT
        MENUITEM "プロパティ(&P)", IDM_PROP
    END
    POPUP "ヘルプ(&H)"
    BEGIN
        MENUITEM "バージョン情報(&A)...", IDM_ABOUT
    END
END

/////////////////////////////////////////////////////////////////////////////
//
// ダイアログ
//

DIALOG_BOX DIALOG 0, 0, 170, 62
FONT 9, "MS UI Gothic"
CAPTION "ダイアログ"
MENU IDC_HP
BEGIN
    LTEXT           "Copyright (C) 2008",IDC_STATIC,42,26,114,8
END

DIALOG1 DIALOG 0, 0, 170, 62
FONT 9, "MS UI Gothic"
CAPTION "設定1"
BEGIN
    EDITTEXT        IDC_EDIT1, 2, 2, 166, 13, ES_AUTOHSCROLL
END

DIALOG2 DIALOG 0, 0, 170, 62
FONT 9, "MS UI Gothic"
CAPTION "設定2"
BEGIN
    EDITTEXT        IDC_EDIT2, 2, 2, 166, 13, ES_AUTOHSCROLL
END

Reseditで作成した場合は、これとは異なるソースコードになります。
上記の太線で示している箇所のみ追加です。