第84章 MDIでメモ帳もどきを作る その1


さて、今回は具体的にMDIで、メモ帳もどきを作ります。 これが完成すれば、実物のメモ帳より少しだけ格調高くなります。 では、さっそくサンプルを見てみましょう。

// mdi01.rcの一部 // 自分で作るときはwindows.hと自前のヘッダーファイルをインクルード ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "新規作成(&N)", IDM_NEW MENUITEM "終了(&X)", IDM_EXIT END END MYDOCUMENT MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "新規作成(&N)", IDM_NEW MENUITEM "閉じる(&C)", IDM_CLOSE MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_EXIT END POPUP "ウィンドウ(&W)" BEGIN MENUITEM "重ねて表示(&C)", IDM_CASCADE MENUITEM "並べて表示(&T)", IDM_TILE MENUITEM "すべて閉じる(&L)", IDM_CLOSEALL MENUITEM "アイコンの整列(&A)", IDM_ARRANGE END END

何の変哲もないメニューリソースです。MYMENUというのは ドキュメントウィンドウが1つもないときに出すメニューです。
MYDOCUMENTというのがドキュメントウィンドウが1つ以上ある時に 出すメニューです。

// mdi01.cpp #define STRICT #include <windows.h> #include "resource.h" #define IDM_FIRSTCHILD 100 LRESULT CALLBACK FrameWndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK DocProc(HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK CloseAllProc(HWND, LPARAM); int MyRegisterWC(WNDPROC, LPCTSTR, HBRUSH); char szFrameClassName[] = "mdi01"; //フレームウィンドウクラス char szChildDoc[] = "document"; //ドキュメント HMENU hMenuFirst, hMenuDoc; HMENU hMenuFirstWnd, hMenuDocWnd; HINSTANCE hInst; int doc_no; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; HWND hFrame, hClient; hInst = hCurInst; if(!MyRegisterWC(FrameWndProc, szFrameClassName,(HBRUSH)(COLOR_APPWORKSPACE + 1))) return FALSE; if(!MyRegisterWC(DocProc, szChildDoc, (HBRUSH)GetStockObject(WHITE_BRUSH))) return FALSE; hMenuFirst = LoadMenu(hInst, "MYMENU"); hMenuFirstWnd = GetSubMenu(hMenuFirst, 0); hMenuDoc = LoadMenu(hInst, "MYDOCUMENT"); hMenuDocWnd = GetSubMenu(hMenuDoc, 1); hFrame = CreateWindow( szFrameClassName, "猫でもわかるMDI", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenuFirst, hInst, NULL); hClient = GetWindow(hFrame, GW_CHILD); ShowWindow(hFrame, nCmdShow); UpdateWindow(hFrame); while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateMDISysAccel(hClient, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } DestroyMenu(hMenuDoc); return msg.wParam; }

まず、WinMain関数を眺めてみると、今までと同じようでいてやはり少し違っています。 順番に違いを見ていくことにします。

HACCEL hAccel;を定義して自前のアクセラレータを 付けることももちろん可能です。しかしMDIプログラミングでは自動的に メニューにいくつかのアクセラレータが付きます。 ドキュメントウィンドウのシステムメニューボタンを押すと 「閉じる Ctrl+F4」「次のウィンドウ Ctrl+F6」というのが自動的に 作られていることがわかります。(ただし、どれほどのユーザーが アクセラレーター・キーについて理解しているでしょうか?プログラマが あれこれ苦労していろいろな機能を付けても大部分のユーザーは使ってくれません!)

次にウィンドウクラスを定義するのは同じです。第81章 の注意を参照してください。このとき、フレームウィンドウクラスとドキュメント ウィンドウクラスのうちメンバの異なるものを引数にして、MyRegisterWCという関数 を作ってみました。それぞれアイコンも異なるものを作る場合は、これも引数に してください。(その他、もっと工夫したものを作ってみてください)それと 少し説明がいるのは「COLOR_APPWORKSPACE + 1」だと思います。VC++5.0のヘルプで WNDCLASS(WNDCLASSEX)を検索すると次のように書いてあります。

A color value must be one of the following standard system colors (the value 1 must be added to the chosen color)

これで、おわかりいただけたでしょうか。

さて、次にメニューをロードしておきます。メニューをロードするのは このタイミングでないとまずいです。LoadMenu関数などについては 第44章を参照してください。そして、必ず GetSubMenuも呼んでおきます。

ところで、GetSubMenuは何のために呼んでるの?

はい、実はMDIではドキュメントウィンドウを新規に 作成する度に勝手にメニューが増えていきます。

左の図を見てください。「アイコンの整列」メニュー項目の 下に3つほど自動的にメニューが増えています。 この「自動的に」増えるメニュー項目の位置を示します。

hMenuDocWnd = GetSubMenu(hMenuDoc, 1);

となっているので、これは2番目の位置に増やすという意味です。 これを0にすると左の追加メニューは「ファイル」の下に追加されます。 また、

hMenuFirstWnd = GetSubMenu(hMenuFirst, 0);

は、まだドキュメントウィンドウがないときなので2番目の引数は 0としておきます。これが結構わかりにくいところです。

次に親ウィンドウであるフレームウィンドウを作ります。 このときウィンドウスタイルにWS_CLIPCHILDRENを加えておきます。 これは、「親ウィンドウの内部を描画するとき子供がいるところを 描画しない」というスタイルです。ただし、この章のサンプルでは 省略しても特に問題は生じません。

CreateWindow関数でフレームウィンドウを作った後、クライアントウィンドウ のハンドルを取得しています(hClient = GetWindow(hFrame, GW_CHILD);)。

エッ!まだクライアントウィンドウを作ってないよ!

ハッハッハ(笑)。これがSDKの難しいところであり、また おもしろいところです。実はCreateWindow関数が実行されたということは このウィンドウのプロシージャでWM_CREATEが実行されたということになります。 WM_CREATEのところでクライアントウィンドウを作っているのです。 (プログラムを順番に見ていったのではわからなくなる。)

関係ないですが、筆者は以前これで大失敗をしたことがあります。 あるウィンドウを作ったあと、すぐにXという動作をさせたいと思いました。 また、このウィンドウのプロシージャはプログラマから見えないので、 まずサブクラス化をしてそのプロシージャの中でWM_CREATEメッセージ を待ちかまえて、動作Xをさせようと思いました。しかし、いつまで待っても WM_CREATEメッセージは来ませんでした。これ当たり前なんですね。 あるウィンドウをサブクラス化する場合、このウィンドウは完全に できてしまっています。すでにWM_CREATEメッセージは処理されているわけです。 ですから、サブクラス化したプロシージャにはいつまで待っても WM_CREATEは来ないわけです。(こんな間違いをするのは筆者だけでしょう・・)

次にフレームウィンドウを最新化してメッセージループに入ります。

ここもワンパターンの書き方です。また、自前のアクセラレータも付けた人は

while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateMDISysAccel(hClient, &msg) && !TranslateAccelerator(hwndFrame, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

というように書き換えてください。

BOOL TranslateMDISysAccel( HWND hWndClient, // クライアントウィンドウのハンドル LPMSG lpMsg // メッセージデータのアドレス

多分この関数は上のような使われ方しかしません。


今回は少し、細々と書きすぎたのでこの章はここまでとします。 次章以降プロシージャの説明です。


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

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