フレームウィンドウができたらすぐにクライアントウィンドウを 作ります。このときCLIENTCREATESTRUCT構造体のメンバを設定します。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); }
というようになっています。hWindowMenuは、「ウィンドウ」メニューの メニューハンドルを指定します。idFirstChildには最初に作成される ドキュメントウィンドウの識別子を指定します。この構造体のアドレスは クライアントウィンドウを作るときの最後の引数に使われます。typedef struct tagCLIENTCREATESTRUCT { // ccs HANDLE hWindowMenu; UINT idFirstChild; } CLIENTCREATESTRUCT;
次にクライアントウィンドウを作ります。これは、既製品のウィンドウで "MDICLIENT"ウィンドウクラスです。
メニューが選択されたらそれぞれの処理を実行するのは今までと同じです。
「新規作成」(IDM_NEW)が選択されたらドキュメントウィンドウを作成します。 ドキュメントウィンドウの作り方は独特で次のようにします。
これでドキュメントウィンドウができあがります。1.MDICREATESTRUCT構造体のメンバを設定(CreateWindow関数の引数とほとんど同じ) 2.クライアントウィンドウに対してWM_MDICREATEメッセージを送る
「(ドキュメントウィンドウを)閉じる」(IDM_CLOSE)メッセージが来たら アクティブなドキュメントウィンドウのハンドルを取得してこれに対して 閉じるメッセージを送ります。これは具体的にはクライアントウィンドウに対して WM_MDIDESTROYメッセージを送ります。このメッセージのWPARAMに閉じる ウィンドウのハンドルを指定します。
アプリケーションの終了(IDM_EXIT)が選択されたときは、フレームウィンドウに 対していつもと同じように処理をします。
「(ドキュメントウィンドウを)すべて閉じる」(IDM_CLOSEALL)が選択されたときは ちょっと面倒です。と、いうのも現在いくつドキュメントウィンドウが存在するか わからないからです。こんな時はEnumChildWindows関数を使って片っ端から 子供ウィンドウのハンドルを調べてコールバック関数に渡していきます。
最後の引数は使わないときは0にしておきます。この関数で 指定したコールバック関数のなかでドキュメントウィンドウを閉じていきます。BOOL EnumChildWindows( HWND hWndParent, // 親のハンドル WNDENUMPROC lpEnumFunc, // コールバック関数のアドレス LPARAM lParam // アプリケーション定義の値 );
並べて表示、重ねて表示、アイコンの整列などが選択されたら クライアントウィンドウに対してWM_MDITILE, WM_MDICASCADE, WM_MDIARRANG メッセージを送ります。これは、楽です。
自分で処理しないメッセージは DefFrameProcにさせます。
さて、ドキュメントウィンドウのプロシージャですが、これができたらすぐに エジットコントロールウィンドウを貼り付けます。 第74章で作ったメモ帳と同じ要領です。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)); }
さて、ドキュメントウィンドウの大きさが変わったときは どうすればよいのでしょうか。実は、これが間違えやすいところです。 WM_SIZEメッセージを処理するときエジットウィンドウをドキュメント ウィンドウのクライアント領域と同じ大きさ、位置にすればよいのですが エジットウィンドウが複数存在するかもしれません。 そこで、GetWindow関数を使ってエジットウィンドウのハンドルを 取得してからMoveWindow関数を実行します。決してエジットウィンドウを 作ったときのハンドルをグローバル変数にして、それをそのまま使う ようなことをしてはいけません。(筆者はこれで失敗した)
別なドキュメントウィンドウがアクティブになるとき、アクティブ化される ウィンドウと非アクティブにされるウィンドウに対してWM_MDIACTIVATEメッセージが 送られます。
このメッセージをドキュメントウィンドウのプロシージャで受けたとき もし、ドキュメントウィンドウが有効であるときはそれ用のメニューを出します。 また、ドキュメントウィンドウが向こう(ウィンドウがない)時は最初のメニュー を出させます。このメニューを出させるには、クライアントウィンドウに対して WM_MDISETMENUメッセージを送ります。WM_MDIACTIVATE // ドキュメントウィンドウに送られるメッセージ hwndChildDeact = (HWND) wParam; // 非アクティブ化されるウィンドウ hwndChildAct = (HWND) lParam; // アクティブ化されるウィンドウ
さて、次にEnumChildWindows関数で指定したコールバック関数です。WM_MDISETMENU wParam = (WPARAM) (HMENU) hmenuFrame; lParam = (LPARAM) (HMENU) hmenuWindow;
子供ウィンドウ(ドキュメントウィンドウ)のハンドルが列挙されて そのたびにこのコールバック関数が呼ばれます。その都度ウィンドウを 破壊すれば、ドキュメントウィンドウはすべて消えてなくなります。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なので
それを越えたアプリケーションとなります)
Update Oct/29/1997 By Y.Kumei