第212章 ウィザードを作ろう


今回は、簡単なウィザードを作ります。 ウィザードとは質問に答えていくとそのソフトの設定などが完成する というものです。コンピュータになれていないユーザーには 大変便利なものです。



今回作るプログラムは、質問に答えていくと 氏名、住所、生年月日の変数が埋まっていって、 最終的にはこれらを、クライアント領域に表示するというものです。

メニューの「オプション」「登録ウィザード」を選択すると 左のようなウィザードが出てきます。これに、氏名を入力して 「次へ」ボタンを押すと、今度は住所を入力するウィザードが 出てきます。最後に生年月日を入力するウィザードが出てくるので、 これに入力をして「完了」ボタンを押します。



作り方は基本的にはプロパティシートと同じです。 プロパティシートについてはすでに 第67章で解説しています。

0.コモンダイアログの準備 1.ウィザードの各ページをダイアログボックスを作る要領で   リソース・エディタで作っておく 2.PROPSHEETPAGE構造体のメンバを設定 3.CreatePropertySheetPage関数で新しいページを作る   この関数の戻り値を配列に保存(HPROPSHEETPAGE) 4.ページ数だけ2.3.を繰り返す 5.PROPSHEETHEADER構造体の必要メンバを設定 6.この時dwFlagsメンバをPSH_WIZARDにしておく 7.PropertySheet関数を呼び出す

プロパティシートとの違いは6.だけです。

では、プログラムをみてみましょう。

// wiz01.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "登録ウィザード(&W)", IDM_WIZ END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // PAGE1 DIALOG DISCARDABLE 0, 0, 235, 39 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "氏名の入力" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,65,20,119,12,ES_AUTOHSCROLL LTEXT "氏名を入力して下さい。",IDC_STATIC,66,7,68,8 END PAGE2 DIALOG DISCARDABLE 0, 0, 255, 43 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "住所の入力" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,85,17,100,12,ES_AUTOHSCROLL LTEXT "住所を入力して下さい。",IDC_STATIC,85,7,68,8 END PAGE3 DIALOG DISCARDABLE 0, 0, 262, 43 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "生年月日の入力" FONT 9, "MS Pゴシック" BEGIN LTEXT "生年月日を入力して下さい。",IDC_STATIC,82,10,82,8 LTEXT "西暦",IDC_STATIC,82,28,15,8 EDITTEXT IDC_EDIT1,101,24,33,12,ES_AUTOHSCROLL LTEXT "年",IDC_STATIC,138,28,8,8 EDITTEXT IDC_EDIT2,145,24,17,12,ES_AUTOHSCROLL LTEXT "月",IDC_STATIC,166,28,8,8 EDITTEXT IDC_EDIT3,178,24,17,12,ES_AUTOHSCROLL LTEXT "日",IDC_STATIC,198,28,8,8 END

ま、普通のメニューとダイアログボックスのリソース・スクリプトです。 ダイアログボックスの大きさはかなり無視されるのでできあがりをみながら 適当に調整して下さい。

// wiz01.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Page1Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Page2Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Page3Proc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MyPropCreate(HWND); char szClassName[] = "wiz01"; //ウィンドウクラス HINSTANCE hInst; HWND hDlg1, hDlg2; //次の変数がウィザードによって値が代入されます。 char szName[64], szAddress[128], szBirthDay[64];

エディットボックス関係のマクロを使うのでwindowsx.hをインクルードするのを 忘れないでください。

また、プログラムの関係上1番目と2番目のダイアログボックスの ウィンドウハンドルをコピーするためのグローバル変数を用意しておきます。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); 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; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるウィザード", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

このへんは毎度同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; INITCOMMONCONTROLSEX ic; HDC hdc; PAINTSTRUCT ps; char szStr[512]; switch (msg) { case WM_CREATE: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&ic); strcpy(szName, "未記入"); strcpy(szAddress, "未記入"); strcpy(szBirthDay, "未記入"); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); wsprintf(szStr, "%s, %s, %s", szName, szAddress, szBirthDay); TextOut(hdc, 10, 10, szStr, lstrlen(szStr)); EndPaint(hWnd, &ps); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_WIZ: MyPropCreate(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_CREATEが来たら、コモンコントロールを初期化してszName, szAddress, szBirthDayに「未記入」の文字列を書きこみます。

WM_PAINTメッセージが来たら、szName, szAddress, szBirthDayを表示します。

メニューで「登録ウィザード」(IDM_WIZ)が選択されたら、自作関数の MyPropCreate関数(後述)を呼び出します。

void MyPropCreate(HWND hWnd) { PROPSHEETPAGE psp; PROPSHEETHEADER psh; HPROPSHEETPAGE hp[3]; psp.dwSize = sizeof(PROPSHEETPAGE); psp.dwFlags = PSP_DEFAULT; psp.hInstance = hInst; psp.pszTemplate = "PAGE1"; psp.pfnDlgProc = (DLGPROC)Page1Proc; hp[0] = CreatePropertySheetPage(&psp); psp.pszTemplate = "PAGE2"; psp.pfnDlgProc = (DLGPROC)Page2Proc; hp[1] = CreatePropertySheetPage(&psp); psp.pszTemplate = "PAGE3"; psp.pfnDlgProc = (DLGPROC)Page3Proc; hp[2] = CreatePropertySheetPage(&psp); memset(&psh, 0, sizeof(PROPSHEETHEADER)); psh.dwSize = sizeof(PROPSHEETHEADER); psh.dwFlags = PSH_WIZARD; psh.hInstance = hInst; psh.hwndParent = hWnd; psh.nPages = 3; psh.phpage = hp; PropertySheet(&psh); return; }

ウィザードを作る関数です。 型どおりに作ります。

psh.dwFlags = PSH_WIZARD;

のみがプロパティシートと異なります。

LRESULT CALLBACK Page1Proc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { LPPSHNOTIFY lppshN; switch (msg) { case WM_INITDIALOG: hDlg1 = hDlg; return TRUE; case WM_NOTIFY: lppshN = (LPPSHNOTIFY)lp; switch (lppshN->hdr.code) { case PSN_SETACTIVE: PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_NEXT); break; } } return FALSE; }

1ページ目のプロシージャです。 WM_INITDIALOGが来たら自分自身のハンドルをグローバル変数の hDlg1にコピーしておきます。 通常のダイアログボックスの時と同じようにメッセージ処理を したらTRUEを返します。 次にこのページがアクティブになったら「NEXT」ボタンを表示します。

PSN_SETACTIVE lppsn = (LPPSHNOTIFY) lParam;

ページがアクティブになろうとしている時に通知されます。 WM_NOTIFYの形で送られてきます。アクティブになるのを 受け入れる時は0を返します

lppsnは、PSHNOTIFY構造体のアドレスを表します。 この構造体は

typedef struct _PSHNOTIFY { NMHDR hdr; LPARAM lParam; } PSHNOTIFY, FAR *LPPSHNOTIFY;

のように定義されています。

VOID PropSheet_SetWizButtons( HWND hPropSheetDlg, DWORD dwFlags );

プロパティシート(ウィザード)の「戻る」「次へ」「完了」 ボタンを使用可能または、不能にします。

hPropSheetDlgはプロパティシートのハンドルです。
dwFlagsは使用可能、不能にするボタンを選択します。

ここには、PSWIZB_BACKを含めることができます。これは「戻る」 ボタンを有効にします。さらに、次のうちの1つを含めることができます。
PSWIZB_NEXT「次へ」ボタンを使用可能にします。
PSWIZB_FINISH「次へ」ボタンの位置に「完了」ボタンを表示して
これを有効にします。
PSWIZB_DISABLEDFINISH「次へ」ボタンの位置に「完了」ボタンを表示して
これを使用不能にします。

ここで、間違いやすいのがWM_INITDIALOGを処理した時とPSN_SETACTIVEを 処理した時で戻りが違う点です。

さらに、勘違いしやすいのがプロパティシートの場合、2ページ目が表示されている時 「戻る」ボタンで1ページ目に戻っても1ページ目のプロシージャにはWM_INITDIALOG メッセージは来ません。2ページ目が表示されている時も1ページ目は見えないだけで 存在しているからです。

LRESULT CALLBACK Page2Proc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { LPPSHNOTIFY lppshN; switch (msg) { case WM_INITDIALOG: hDlg2 = hDlg; return TRUE; case WM_NOTIFY: lppshN = (LPPSHNOTIFY)lp; switch (lppshN->hdr.code) { case PSN_SETACTIVE: PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_NEXT); break; } } return FALSE; }

2ページ目のプロシージャです。

WM_INITDIALOGメッセージが来たら、自分自身のウィンドウハンドルを グローバル変数hDlg2にコピーします。

PSN_SETACTIVEが来たら「戻る」「次へ」ボタンを有効にします。

LRESULT CALLBACK Page3Proc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { LPPSHNOTIFY lppshN; static HWND hYear, hMon, hDate, hName, hAddress; char szYear[16], szMonth[8], szDate[8]; HWND hMain; switch (msg) { case WM_INITDIALOG: hYear = GetDlgItem(hDlg, IDC_EDIT1); hMon = GetDlgItem(hDlg, IDC_EDIT2); hDate = GetDlgItem(hDlg, IDC_EDIT3); hName = GetDlgItem(hDlg1, IDC_EDIT1); hAddress = GetDlgItem(hDlg2, IDC_EDIT1); return TRUE; case WM_NOTIFY: lppshN = (LPPSHNOTIFY)lp; switch (lppshN->hdr.code) { case PSN_WIZFINISH: Edit_GetText(hYear, szYear, sizeof(szYear)); Edit_GetText(hMon, szMonth, sizeof(szMonth)); Edit_GetText(hDate, szDate, sizeof(szDate)); Edit_GetText(hName, szName, sizeof(szName)); Edit_GetText(hAddress, szAddress, sizeof(szAddress)); wsprintf(szBirthDay, "%s/%s/%s", szYear, szMonth, szDate); hMain = GetParent(GetParent(hDlg)); InvalidateRect(hMain, NULL, TRUE); break; case PSN_SETACTIVE: PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_FINISH); break; } } return FALSE; }

3ページ目のプロシージャです。

INIT_DIALOGメッセージが来たらstatic変数に各ページの エディットコントロールのウィンドウハンドルを取得します。 1,2ページのプロシージャでそれぞれ自分自身のウィンドウハンドルを グローバル変数にコピーしておいたのは、ここで必要になるからです。

「完了」ボタンが押されたら(PSN_WIZFINISH)各ページのエディットコントロールの 文字列を取得します。そしてメインウィンドウのクライアント領域を 無効化してWM_PAINTメッセージを呼び出して表示を行います。

PSN_WIZFINISH lppsn = (LPPSHNOTIFY) lParam;

ユーザーが「完了」ボタンを押したことを通知します。
lppsnはPSHNOTIFY構造体のアドレスです。

PSN_SETACTIVEが来たら、「戻る」「完了」ボタンを有効にします。

さて、このプログラムにもいろいろ不満があります。 各ページで有効な回答をしていなくても、次のページへ行けてしまいます。 また、一度「完了」ボタンを押してしまってから、内容を一部修正したい時 再度すべてのウィザードに答えなくてはなりません。 このへんを改良してみて下さい。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

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