第67章 プロパティシートを作る


Windows95では、デスクトップウィンドウを右クリックして、 「プロパティ」を選択すると「画面のプロパティ」という プロパティシートが現れます。特殊なダイアログボックスの ようなものです。筆者が初めて95に触れたとき、このプロパティ シートというものに大変驚いたものです。 最近のソフトは、いろいろな設定をする場面で プロパティシートがよく使われます。 これをよく観察すると、最下部に「OK」「キャンセル」「更新」 ボタンが付いています。また、各ページにボタンが付いていることも あります。「OK」ボタンを押すと、各ページで設定した事柄が 実行され、プロパティシートは消滅?します。「更新」ボタンを 押すと設定した事項が実行されますが、プロパティシートは残ったままです。 「試しに実行してみる」時に便利な機能です。

ところで、このプロパティシートはどのように作ればよいのでしょうか。 VC++のリソース関係のメニューを見ても「プロパティシートの新規作成」 なんていうのはありません。 実は、リソースはダイアログボックスのものを使います。 では、最初に今回作るサンプルを見てみましょう。

プログラムを実行すると右のようなウィンドウが現れます。 このメニューの中の「オプション」「設定」を選ぶと、

左のような、プロパティシートが現れます。 各項目を記入して「OK」ボタンを押すとそれが、親ウィンドウの クライアント領域に表示されます。また、このプロパティシートでは 「更新」ボタンがないことに注意してください。

では、さっそく作り方の説明をします。

まず、各ページをダイアログボックスのリソースを 作るときの要領で作ります。

プロパティシートが3ページあればダイアログボックスのリソースが 3つできます。それぞれのリソース名を"PAGE1", "PAGE2", "PAGE3" などとします(名前は何でもよい)。

次に、各ページについてPROPSHEETPAGE構造体を設定して、 CreatePropertySheetPage関数を実行し各ページを作ります。 各ページができたら、PROPSHEETHEADER 構造体を設定して、 PropertySheet関数を実行すれば終わりです。 また、各ページはダイアログボックスですから、それぞれ ダイアログプロシージャがあってそれぞれ処理を行います。 では、まずPROPSHEETPAGE構造体から見てみましょう。

typedef struct _PROPSHEETPAGE { // psp DWORD dwSize; DWORD dwFlags; HINSTANCE hInstance; union { LPCTSTR pszTemplate; LPCDLGTEMPLATE pResource; }; union { HICON hIcon; LPCTSTR pszIcon; }; LPCTSTR pszTitle; DLGPROC pfnDlgProc; LPARAM lParam; LPFNPSPCALLBACK pfnCallback; UINT FAR * pcRefParent; } PROPSHEETPAGE, FAR *LPPROPSHEETPAGE; typedef const PROPSHEETPAGE FAR *LPCPROPSHEETPAGE;

dwSizeは、この構造体のサイズです。

dwFlagsは、この構造体のどのメンバを利用するかを指定します。 PSP_DEFAULTは、デフォルトです。
PSP_HASHELPは、このページがアクティブになったら「ヘルプ」ボタンを 表示します。
PSP_USEHICONは、タブの小さなアイコンとして共用体のhIconを使用します。
PSP_USEICONIDは、アイコンリソースの名前または、タブの小さなアイコンとして 共用体のpszIconを使用します。
hInstanceは、インスタンスハンドル。

pszTemplateは、ダイアログボックスのリソース名を指定します。

pfnDlgProcは、ダイアログボックスプロシージャへのポインタを指定します。

その他についてはヘルプ等を参照してください。

ページの設定が終わったら、ページをCreatePropertySheetPage関数で作ります。

HPROPSHEETPAGE CreatePropertySheetPage( LPCPROPSHEETPAGE lppsp );

lppspは、先ほど設定したPROPSHEETPAGE構造体へのポインタです。 戻り値は、成功すれば新しいページのハンドル、失敗したときはNULLを返します。 各ページを作ったら、PROPSHEETHEADER構造体を設定します。

typedef struct _PROPSHEETHEADER { // psh 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 FAR *phpage; }; PFNPROPSHEETCALLBACK pfnCallback; } PROPSHEETHEADER, FAR *LPPROPSHEETHEADER; typedef const PROPSHEETHEADER FAR *LPCPROPSHEETHEADER;

dwSizeは、この構造体のサイズです。
dwFlagsは、使用するメンバを示します。PROPSHEETPAGE構造体の時と 似ています。PSH_NOAPPLYNOWは、「更新」ボタンを削除します。
hwndParentは、その名の通り親ウィンドウのハンドルです。
pszCaptionは、説明するまでもなくプロパティシートのタイトルです。

最後に、PropertySheet関数を呼び出します。

int PropertySheet( LPCPROPSHEETHEADER lppsph );

lppsphは、先ほど設定したPROPSHEETHEADER構造体へのポインタです。

ところで、プロパティシートで「OK」ボタンが押されたときの 処理は、どこで行えばよいのか?ということがありますが、 各ダイアログプロシージャで行うのが一番簡単です。
WM_NOTIFYメッセージを捕まえて、NMHDR構造体のcodeメンバが PSN_APPLYであれば、「OK」ボタンが押されたことになります。 どういう場合に、どこのプロシージャにWM_NOTIFYメッセージが届くかは 自分で各プロシージャのいろいろなところにMessageBox関数を 書いて調べるとよくわかります。 今回は、更新ボタンとモードレスにする方法については触れませんでした。 また、後の章で解説する予定です。

// tab01.rc // 自前で作る人はwindows.hと、自作のヘッダーファイルをインクルード ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "設定(&S)", IDM_OPTION END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // PAGE1 DIALOG DISCARDABLE 0, 0, 126, 47 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "氏名・年齢" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,59,7,60,13,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,59,23,60,13,ES_AUTOHSCROLL LTEXT "氏名:",IDC_STATIC,7,7,18,8 LTEXT "年齢:",IDC_STATIC,7,28,18,8 END PAGE2 DIALOG DISCARDABLE 0, 0, 126, 47 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "住所・電話" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT4,31,28,88,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,31,7,88,12,ES_AUTOHSCROLL LTEXT "住所:",IDC_STATIC,7,7,21,11 LTEXT "電話:",IDC_STATIC,7,29,21,11 END PAGE3 DIALOG DISCARDABLE 0, 0, 126, 47 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "性別" FONT 9, "MS Pゴシック" BEGIN CONTROL "男性",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,39,7,30,10 CONTROL "女性",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,39,17,30, 10 CONTROL "不明",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,39,27,30, 10 END

次に、resource.hの一部を示します。自分でヘッダーファイルを作る人は これを参考にしてください。

// resource.hの一部 #define IDC_EDIT1 1000 #define IDC_EDIT2 1001 #define IDC_EDIT3 1003 #define IDC_EDIT4 1004 #define IDC_RADIO1 1005 #define IDC_RADIO2 1006 #define IDC_RADIO3 1007 #define IDM_OPTION 40001 #define IDM_END 40002

次に、ソースファイルを示します。

// tab01.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlg1Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlg2Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlg3Proc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int seibetsu = 3; HWND hParent; //親ウィンドウ void CreateMyProp(HWND); char szClassName[] = "tab0101"; //ウィンドウクラス char str_name[64], str_age[64], str_address[64], str_phone[64]; char *str1_org = "名前 = %s"; char *str2_org = "年齢 = %s"; char *str3_org = "住所 = %s"; char *str4_org = "電話 = %s"; char *str5_org = "性別 = %s";

seibetsuの初期状態は「不明」(3)にしておきました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!hPrevInst) { if (!InitApp(hCurInst)) return FALSE; } if (!InitInstance(hCurInst, nCmdShow)) { return FALSE; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

これは、いつもと同じです。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; return (RegisterClass(&wc)); }

いつもと同じですが、今回はクラスメニューを使うので メニューリソース名を忘れずに記述しておいてください。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; int wmax, hmax; wmax = GetSystemMetrics(SM_CXSCREEN); hmax = GetSystemMetrics(SM_CYSCREEN); hWnd = CreateWindow(szClassName, "猫でもわかるプロパティシート", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 (wmax - 300) / 2, //X座標 (hmax - 150) / 2, //Y座標 300, //幅 150, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; // 親のウィンドウハンドルをグローバル変数に保存 hParent = hWnd; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

今回は、ディスプレイの幅、高さをGetSystemMetrics関数で 求めておいて、親ウィンドウをディスプレイの中央に表示するようにしてみました。 第16章の方法と比べてみてください。
また、GetSystemMetrics関数は、第64章で ちょっとだけ出てきました。

int GetSystemMetrics( int nIndex );

nIndexにいろいろなものを設定すると、いろいろなものが取得できます。 システム メトリックとは、Windowsが表示するさまざまな表示要素の幅と高さのことです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; HDC hdc; PAINTSTRUCT ps; char str[64], sex[6]; switch (msg) { case WM_CREATE: InitCommonControls(); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_OPTION: CreateMyProp(hWnd); break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0L); break; } break; case WM_PAINT: switch (seibetsu) { case 1: strcpy(sex, "男性"); break; case 2: strcpy(sex, "女性"); break; case 3: strcpy(sex, "不明"); break; } hdc = BeginPaint(hWnd, &ps); wsprintf(str, str1_org, str_name); TextOut(hdc, 0, 0, str, strlen(str)); wsprintf(str, str2_org, str_age); TextOut(hdc, 0, 20, str, strlen(str)); wsprintf(str, str3_org, str_address); TextOut(hdc, 0, 40, str, strlen(str)); wsprintf(str, str4_org, str_phone); TextOut(hdc, 0, 60, str, strlen(str)); wsprintf(str, str5_org, sex); TextOut(hdc, 0, 80, str, strlen(str)); EndPaint(hWnd, &ps); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

プロパティシートで設定した事項をWM_PAINTのところで、クライアント領域に 表示させています。

LRESULT CALLBACK MyDlg1Proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { NMHDR *nmhdr; switch(msg) { case WM_INITDIALOG: Edit_SetText(GetDlgItem(hWnd, IDC_EDIT1), str_name); Edit_SetText(GetDlgItem(hWnd, IDC_EDIT2), str_age); return TRUE; case WM_NOTIFY: nmhdr = (NMHDR *)lp; switch(nmhdr->code) { case PSN_APPLY: Edit_GetText(GetDlgItem(hWnd, IDC_EDIT1), str_name, sizeof(str_name)); Edit_GetText(GetDlgItem(hWnd, IDC_EDIT2), str_age, sizeof(str_age)); InvalidateRect(hParent, NULL, TRUE); break; default: return FALSE; } break; } return FALSE; } LRESULT CALLBACK MyDlg2Proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { NMHDR *nmhdr; switch(msg) { case WM_INITDIALOG: Edit_SetText(GetDlgItem(hWnd, IDC_EDIT3), str_address); Edit_SetText(GetDlgItem(hWnd, IDC_EDIT4), str_phone); return TRUE; case WM_NOTIFY: nmhdr = (NMHDR *)lp; switch(nmhdr->code) { case PSN_APPLY: Edit_GetText(GetDlgItem(hWnd, IDC_EDIT3), str_address, sizeof(str_address)); Edit_GetText(GetDlgItem(hWnd, IDC_EDIT4), str_phone, sizeof(str_phone)); InvalidateRect(hParent, NULL, TRUE); return TRUE; default: return FALSE; } break; } return FALSE; } LRESULT CALLBACK MyDlg3Proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { NMHDR *nmhdr; switch(msg) { case WM_INITDIALOG: switch (seibetsu) { case 1: Button_SetCheck(GetDlgItem(hWnd, IDC_RADIO1), BST_CHECKED); break; case 2: Button_SetCheck(GetDlgItem(hWnd, IDC_RADIO2), BST_CHECKED); break; case 3: Button_SetCheck(GetDlgItem(hWnd, IDC_RADIO3), BST_CHECKED); break; } return TRUE; case WM_NOTIFY: nmhdr = (NMHDR *)lp; switch (nmhdr->code) { case PSN_APPLY: if(Button_GetCheck(GetDlgItem(hWnd, IDC_RADIO1)) == BST_CHECKED) seibetsu = 1; if(Button_GetCheck(GetDlgItem(hWnd, IDC_RADIO2)) == BST_CHECKED) seibetsu = 2; if(Button_GetCheck(GetDlgItem(hWnd, IDC_RADIO3)) == BST_CHECKED) seibetsu = 3; InvalidateRect(hParent, NULL, TRUE); return TRUE; default: return FALSE; } break; } return FALSE; }

各ページのダイアログプロシージャです。 PSN_APPLYメッセージの処理の仕方に注意してください。

void CreateMyProp(HWND hWnd) { HINSTANCE hInst; PROPSHEETPAGE psp; PROPSHEETHEADER psh; HPROPSHEETPAGE hpsp[3]; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); psp.dwSize = sizeof(PROPSHEETPAGE); psp.dwFlags = PSP_DEFAULT; psp.hInstance = hInst; psp.pszTemplate = "PAGE1"; psp.pfnDlgProc = (DLGPROC)MyDlg1Proc; hpsp[0] = CreatePropertySheetPage(&psp); psp.pszTemplate = "PAGE2"; psp.pfnDlgProc = (DLGPROC)MyDlg2Proc; hpsp[1] = CreatePropertySheetPage(&psp); psp.pszTemplate = "PAGE3"; psp.pfnDlgProc = (DLGPROC)MyDlg3Proc; hpsp[2] = CreatePropertySheetPage(&psp); memset(&psh, 0, sizeof(PROPSHEETHEADER)); psh.dwSize = sizeof(PROPSHEETHEADER); psh.dwFlags = PSH_NOAPPLYNOW; psh.hInstance = hInst; psh.hwndParent = hWnd; psh.nPages = 3; psh.phpage = hpsp; psh.pszCaption = "設定"; (HWND)PropertySheet(&psh); return; }

前述のプロパティシートの作り方の解説を参考にソースコードを 読んでみてください。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Aug/09/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。