第180章 DTPと通知メッセージ


第178章のプログラムでは、ダイアログボックスを閉じないと 親ウィンドウに日付・時刻が表示されませんでした。 DTPコントロールを操作して日付・時刻が変えられた瞬間に 刻々と親ウィンドウにそのデータを表示するにはどうしたら良いのでしょうか。 これには、通知メッセージを使います。通知メッセージは今まで何回も 出てきました。 (第65章第108章第131章などを参照)



さて、プログラムのストーリーを考えてみます。 DTPコントロールに変化が起こったら この時の通知メッセージをダイアログのプロシージャで捕まえます。 そして、DTPの内容をGetWindowText関数で調べて、グローバル変数に コピーします。そして、InvalidateRect関数を呼んで親にWM_PAINT メッセージを発生させます。さて、これでよいのでしょうか? お気づきの人も多いと思いますが、通常のダイアログボックスでは EndDialogでダイアログが終了するまで制御を戻しません。 したがって、上のようなストーリーではうまくいきません。 モードレス・ダイアログボックスを使います。(第30章

DTPのデータが変化した時DTN_DATETIMECHANGE通知メッセージが送られます。

DTN_DATETIMECHANGE lpChange = (LPNMDATETIMECHANGE) lParam;

変化が起こった時にDTPコントロールからその親に送られます。 WM_NOTIFYの形をとります。このコントロールの親は0を返さなくてはいけません。

typedef struct tagNMDATETIMECHANGE { NMHDR nmhdr; DWORD dwFlags; SYSTEMTIME st; } NMDATETIMECHANGE, FAR * LPNMDATETIMECHANGE;

またまた、変な構造体が出てきました。

nmhdrはいつものNMHDR構造体です。

dwFlagsはDTPコントロールにデータがセットされているかどうか のフラグを示します。GDT_NONEの時はデータがセットされていません。 GDT_VALIDの時はセットされています。

stはSYSTEMTIME構造体で現在のコントロールのデータが格納されます。 SYSTEMTIME構造体も今までに何回も出てきました。 (第34章参照)

では、プログラムを見てみます。

// newctl03.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END MENUITEM "入力(&T)", IDM_INPUT END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 159, 51 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "日付・時刻入力" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "閉じる",IDOK,54,30,50,14 END

ダイアログのスタイルの所でWS_VISIBLEが加わっていることに注意して下さい。 リソース・エディタで作る人は「ダイアログのプロパティ」の「その他のスタイル」の所で 「可視」にチェックをつけてください。(ここで、可視にしなくてもCreateDialog関数の 呼び出しの後ShowWindow関数で見えるようにしてもよいです。)

また、「OK」ボタンは「閉じる」ボタンにしました。さらに、「キャンセル」ボタンは なくしました。

// newctl03.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <commctrl.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HWND MakeTimeCtl(HWND); HWND MakeDateCtl(HWND); HWND hDlgModeless; //モードレスダイアログボックスのハンドル char szClassName[] = "newctl03"; //ウィンドウクラス HINSTANCE hInst; char szTime[256] = ""; char szDate[256] = "";

モードレス・ダイアログボックスのハンドルはあちこちで 使うのでグローバル変数にしておくと便利です。

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)) { if (hDlgModeless == NULL || !IsDialogMessage(hDlgModeless, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; }

モードレス・ダイアログボックスを取り扱うので、 メッセージループの所が少し違います。(第30章参照)

//ウィンドウ・クラスの登録 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, "猫でもわかるPicker Ctrl", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 300, //幅 140, //高さ 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; HDC hdc; PAINTSTRUCT ps; INITCOMMONCONTROLSEX ic; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_INPUT: if (hDlgModeless == NULL) { hDlgModeless = CreateDialog(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); } else { MessageBox(hWnd, "すでにダイアログボックスは開いています", "警告", MB_OK | MB_ICONEXCLAMATION); } break; } break; case WM_CREATE: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_DATE_CLASSES; InitCommonControlsEx(&ic); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); TextOut(hdc, 10, 10, szDate, strlen(szDate)); TextOut(hdc, 10, 50, szTime, strlen(szTime)); EndPaint(hWnd, &ps); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { if (hDlgModeless) DestroyWindow(hDlgModeless); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

IDM_INPUTのところをみてください。モードレス・ダイアログボックスが存在 しない時はCreateDialog関数でダイアログボックスを作りますが、すでに 存在する時はメッセージボックスで注意を促します。また、リソースの所で ダイアログボックスのウィンドウスタイルにWS_VISIBLEを指定しなかった場合は CreateDialog関数を実行してもダイアログボックスは見えないので注意して下さい。

また、プログラム終了時にモードレス・ダイアログボックスが存在する時は これを破棄してから、親ウィンドウを破棄しています。(WM_CLOSEの時)

HWND MakeTimeCtl(HWND hWnd) { HWND hTime; LPSTR lpstrTime = "tthh'時'mm'分'ss'秒'"; hTime = CreateWindowEx(0, DATETIMEPICK_CLASS, NULL, WS_BORDER | WS_VISIBLE | WS_CHILD | DTS_TIMEFORMAT, 160, 10, 120, 25, hWnd, NULL, hInst, NULL); SendMessage(hTime, DTM_SETFORMAT, 0, (LPARAM)lpstrTime); return hTime; } HWND MakeDateCtl(HWND hWnd) { HWND hDate; LPSTR lpstrDate = "yyy'年'MM'月'dd'日'dddd"; hDate = CreateWindowEx(0, DATETIMEPICK_CLASS, NULL, WS_BORDER | WS_VISIBLE | WS_CHILD | DTS_LONGDATEFORMAT, 10, 10, 150, 25, hWnd, NULL, hInst, NULL); SendMessage(hDate, DTM_SETFORMAT, 0, (LPARAM)lpstrDate); return hDate; }

これは、第178章のものと全く同じです。

LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hDate, hTime, hParent; LPNMDATETIMECHANGE lpnmchange; switch (msg) { case WM_CLOSE: DestroyWindow(hDlg); hDlgModeless = NULL; return FALSE; case WM_NOTIFY: lpnmchange = (LPNMDATETIMECHANGE)lp; if (lpnmchange->nmhdr.code == DTN_DATETIMECHANGE) { if ((HWND)lpnmchange->nmhdr.hwndFrom == hTime) { GetWindowText(hTime, szTime, sizeof(szTime)); InvalidateRect(hParent, NULL, TRUE); return FALSE; } if ((HWND)lpnmchange->nmhdr.hwndFrom == hDate) { GetWindowText(hDate, szDate, sizeof(szDate)); InvalidateRect(hParent, NULL, TRUE); return FALSE; } } return FALSE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: GetWindowText(hTime, szTime, sizeof(szTime)); GetWindowText(hDate, szDate, sizeof(szDate)); InvalidateRect(hParent, NULL, TRUE); SendMessage(hDlg, WM_CLOSE, 0, 0); return TRUE; case IDCANCEL: SendMessage(hDlg, WM_CLOSE, 0, 0); return TRUE; } break; case WM_INITDIALOG: hDate = MakeDateCtl(hDlg); hTime = MakeTimeCtl(hDlg); hParent = GetParent(hDlg); GetWindowText(hTime, szTime, sizeof(szTime)); GetWindowText(hDate, szDate, sizeof(szDate)); InvalidateRect(hParent, NULL, TRUE); return TRUE; } return FALSE; }

WM_CLOSEメッセージを処理しています。これは、タイトルバーを 右クリックして(システム・メニューが出る)「閉じる」を選択するとやってきます。 これが来た時、DestroyWindow関数を実行してダイアログを破棄します。 また、ダイアログのハンドルをNULLにしておきます。(プログラムの他の部分で この値がNULLかどうかでダイアログが存在しているかどうかを判断しているので 必ずNULLにしておく必要があります。)

WM_NOTIFYメッセージが来た時は、lpの値をNMDATETIMECHANGEへの ポインタ変数にコピーして、通知メッセージの種類を調べます。 DTN_DATETIMECHANGEであれば、次にこのメッセージがどのコントロールから 来たのかを調べて、必要な処理を行います。
通知メッセージのこの手の処理の時不思議に思われるかもしれません。 たとえばlpにNMHDR構造体のアドレスを持った通知メッセージもあります。 その場合

lpnmchange = (LPNMDATETIMECHANGE)lp;

で無理やりDATETIMECHANGE構造体へのポインタ変数に lpの値をコピーしても大丈夫なのでしょうか。答えは「大丈夫」です。 DATETIMECHANGE構造体の最初のメンバがNMHDR構造体であるからです。

「閉じる」(IDOK)ボタンやタイトルバーの右端にある「バッテン印」(IDCANCEL) が押された時は自分自身にWM_CLOSEメッセージを送ってダイアログを破棄します。

今回は、簡単でした。


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

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