第85章 MDIでメモ帳もどきを作る その2


今回は、前章の残りの解説をします。

LRESULT CALLBACK FrameWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { static HWND hClient; HWND hChild; CLIENTCREATESTRUCT ccs; MDICREATESTRUCT mdic; char str[64], *str_org = "ドキュメント No. = %d"; switch (msg) { case WM_CREATE: ccs.hWindowMenu = hMenuFirstWnd; ccs.idFirstChild = IDM_FIRSTCHILD; hClient = CreateWindow( "MDICLIENT", NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0, hWnd, (HMENU)1, hInst, (LPSTR)&ccs); return 0; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_NEW: doc_no++; wsprintf(str, str_org, doc_no); mdic.szClass = szChildDoc; mdic.szTitle = str; mdic.hOwner = hInst; mdic.x = CW_USEDEFAULT; mdic.y = CW_USEDEFAULT; mdic.cx = CW_USEDEFAULT; mdic.cy = CW_USEDEFAULT; mdic.style = 0; mdic.lParam = 0; hChild = (HWND)SendMessage(hClient, WM_MDICREATE, 0, (LPARAM)&mdic); return 0; case IDM_CLOSE: hChild = (HWND)SendMessage(hClient, WM_MDIGETACTIVE, 0, 0); if (hChild) SendMessage(hClient, WM_MDIDESTROY, (WPARAM)hChild, 0); return 0; case IDM_EXIT: SendMessage(hWnd, WM_CLOSE, 0, 0); return 0; case IDM_CLOSEALL: EnumChildWindows(hClient, &CloseAllProc, 0); return 0; case IDM_TILE: SendMessage(hClient, WM_MDITILE, 0, 0); return 0; case IDM_CASCADE: SendMessage(hClient, WM_MDICASCADE, 0, 0); return 0; case IDM_ARRANGE: SendMessage(hClient, WM_MDIICONARRANGE, 0, 0); return 0; default: hChild = (HWND)SendMessage(hClient, WM_MDIGETACTIVE, 0, 0); if (IsWindow(hChild)) SendMessage(hChild, WM_COMMAND, wp, lp); break; } break; case WM_QUERYENDSESSION: case WM_CLOSE: SendMessage(hWnd, WM_COMMAND, IDM_CLOSEALL, 0); if (GetWindow(hClient, GW_CHILD)) return 0; break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefFrameProc(hWnd, hClient, msg, wp, lp); }

フレームウィンドウができたらすぐにクライアントウィンドウを 作ります。このときCLIENTCREATESTRUCT構造体のメンバを設定します。

typedef struct tagCLIENTCREATESTRUCT { // ccs HANDLE hWindowMenu; UINT idFirstChild; } CLIENTCREATESTRUCT;

というようになっています。hWindowMenuは、「ウィンドウ」メニューの メニューハンドルを指定します。idFirstChildには最初に作成される ドキュメントウィンドウの識別子を指定します。この構造体のアドレスは クライアントウィンドウを作るときの最後の引数に使われます。

次にクライアントウィンドウを作ります。これは、既製品のウィンドウで "MDICLIENT"ウィンドウクラスです。

メニューが選択されたらそれぞれの処理を実行するのは今までと同じです。

「新規作成」(IDM_NEW)が選択されたらドキュメントウィンドウを作成します。 ドキュメントウィンドウの作り方は独特で次のようにします。

1.MDICREATESTRUCT構造体のメンバを設定(CreateWindow関数の引数とほとんど同じ) 2.クライアントウィンドウに対してWM_MDICREATEメッセージを送る

これでドキュメントウィンドウができあがります。

「(ドキュメントウィンドウを)閉じる」(IDM_CLOSE)メッセージが来たら アクティブなドキュメントウィンドウのハンドルを取得してこれに対して 閉じるメッセージを送ります。これは具体的にはクライアントウィンドウに対して WM_MDIDESTROYメッセージを送ります。このメッセージのWPARAMに閉じる ウィンドウのハンドルを指定します。

アプリケーションの終了(IDM_EXIT)が選択されたときは、フレームウィンドウに 対していつもと同じように処理をします。

「(ドキュメントウィンドウを)すべて閉じる」(IDM_CLOSEALL)が選択されたときは ちょっと面倒です。と、いうのも現在いくつドキュメントウィンドウが存在するか わからないからです。こんな時はEnumChildWindows関数を使って片っ端から 子供ウィンドウのハンドルを調べてコールバック関数に渡していきます。

BOOL EnumChildWindows( HWND hWndParent, // 親のハンドル WNDENUMPROC lpEnumFunc, // コールバック関数のアドレス LPARAM lParam // アプリケーション定義の値 );

最後の引数は使わないときは0にしておきます。この関数で 指定したコールバック関数のなかでドキュメントウィンドウを閉じていきます。

並べて表示、重ねて表示、アイコンの整列などが選択されたら クライアントウィンドウに対してWM_MDITILE, WM_MDICASCADE, WM_MDIARRANG メッセージを送ります。これは、楽です。

自分で処理しないメッセージは DefFrameProcにさせます。

LRESULT CALLBACK DocProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { static HWND hClient, hFrame; HWND hEdit; RECT rc; switch (msg) { case WM_CREATE: hClient = GetParent(hWnd); hFrame = GetParent(hClient); GetClientRect(hWnd, &rc); hEdit = CreateWindow( "EDIT", NULL, WS_VISIBLE | WS_CHILD | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | WS_HSCROLL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hWnd, NULL, hInst, NULL); return 0; case WM_SIZE: GetClientRect(hWnd, &rc); hEdit = GetWindow(hWnd, GW_CHILD); MoveWindow(hEdit, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); break; case WM_MDIACTIVATE: if (lp == (LPARAM)hWnd) SendMessage(hClient, WM_MDISETMENU, (WPARAM)hMenuDoc, (LPARAM)hMenuDocWnd); if (lp != (LPARAM)hWnd) SendMessage(hClient, WM_MDISETMENU, (WPARAM)hMenuFirst, (LPARAM)hMenuFirstWnd); DrawMenuBar(hFrame); return 0; } return (DefMDIChildProc(hWnd, msg, wp, lp)); }

さて、ドキュメントウィンドウのプロシージャですが、これができたらすぐに エジットコントロールウィンドウを貼り付けます。 第74章で作ったメモ帳と同じ要領です。

さて、ドキュメントウィンドウの大きさが変わったときは どうすればよいのでしょうか。実は、これが間違えやすいところです。 WM_SIZEメッセージを処理するときエジットウィンドウをドキュメント ウィンドウのクライアント領域と同じ大きさ、位置にすればよいのですが エジットウィンドウが複数存在するかもしれません。 そこで、GetWindow関数を使ってエジットウィンドウのハンドルを 取得してからMoveWindow関数を実行します。決してエジットウィンドウを 作ったときのハンドルをグローバル変数にして、それをそのまま使う ようなことをしてはいけません。(筆者はこれで失敗した)

別なドキュメントウィンドウがアクティブになるとき、アクティブ化される ウィンドウと非アクティブにされるウィンドウに対してWM_MDIACTIVATEメッセージが 送られます。

WM_MDIACTIVATE // ドキュメントウィンドウに送られるメッセージ hwndChildDeact = (HWND) wParam; // 非アクティブ化されるウィンドウ hwndChildAct = (HWND) lParam; // アクティブ化されるウィンドウ

このメッセージをドキュメントウィンドウのプロシージャで受けたとき もし、ドキュメントウィンドウが有効であるときはそれ用のメニューを出します。 また、ドキュメントウィンドウが向こう(ウィンドウがない)時は最初のメニュー を出させます。このメニューを出させるには、クライアントウィンドウに対して WM_MDISETMENUメッセージを送ります。

WM_MDISETMENU wParam = (WPARAM) (HMENU) hmenuFrame; lParam = (LPARAM) (HMENU) hmenuWindow;

さて、次にEnumChildWindows関数で指定したコールバック関数です。

BOOL CALLBACK CloseAllProc(HWND hWnd, LPARAM lp) { SendMessage(GetParent(hWnd), WM_MDIDESTROY, (WPARAM)hWnd, 0); return TRUE; }

子供ウィンドウ(ドキュメントウィンドウ)のハンドルが列挙されて そのたびにこのコールバック関数が呼ばれます。その都度ウィンドウを 破壊すれば、ドキュメントウィンドウはすべて消えてなくなります。

int MyRegisterWC(WNDPROC lpfnWndProc, LPCTSTR lpszClassName, HBRUSH hbrBack) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = lpfnWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = hbrBack; wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = lpszClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return(RegisterClassEx(&wc)); }

ウィンドウクラスの登録用関数です。

さて、これでMDIの骨組みができあがりました。あとは ドキュメントを読み出したり、ファイルに保存する メニューを作ってください。印刷メニューなども付けてみてください。 それに、ツールバーとかステータスバーも付ければ立派な アプリケーションになります。(本物のメモ帳はSDIなので それを越えたアプリケーションとなります)


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

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