MDI のプログラム

MDI(Multi Document Interface) を使って複数のウインドウを表示します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

MDI(Multi Document Interface)

MDIとは、親ウインドウがあって、そのクライアント領域に複数の子供ウインドウがあります。
普通に親ウインドウとその子供ウインドウを作ったようにも見えますが、いろいろな点でかなり違います。
例を示すと、子供ウインドウを最大化すると子供ウインドウのタイトルバーがなくなり、そこに表示されていたファイル名などが親のタイトルバーに表示されます。
市販の有名ワープロはたいていMDIになっています。いろいろ観察してみてください。

まずMDIプログラミングで使う用語を説明します。
親ウインドウは フレームウインドウと呼ばれています。
子供ウインドウは ドキュメントウインドウなどと呼ばれます。
また、フレームウインドウのクライアント領域には実は クライアントウインドウが張り付いています。 (?_?;
つまり、フレームウインドウの子供が、クライアントウインドウで、クライアントウインドウの子供がドキュメントウインドウになります。
従ってフレームウインドウはドキュメントウインドウのおじいさんということになります。
この「親・子・孫」関係がメッセージを処理するときに非常に重要になります。

プログラムの説明

  1. プロジェクトのフォルダーが作成できたら、まず二種類のメニューを作成して下さい。
    ドキュメントウインドウが作成されていない状態の最初のメニューです。
    MYMENU MENU DISCARDABLE 
        POPUP "ファイル(&F)"
            MENUITEM "新規作成(&N)",                IDM_NEW
            MENUITEM "終了(&X)",                    IDM_EXIT
    
    ドキュメントウインドウが作成された状態のメニューです。
    MYDOCUMENT MENU DISCARDABLE 
        POPUP "ファイル(&F)"
            MENUITEM "新規作成(&N)",                IDM_NEW
            MENUITEM "閉じる(&C)",                  IDM_CLOSE
            MENUITEM SEPARATOR
            MENUITEM "終了(&X)",                    IDM_EXIT
        POPUP "ウインドウ(&W)"
            MENUITEM "重ねて表示(&C)",              IDM_CASCADE
            MENUITEM "並べて表示(&T)",              IDM_TILE
            MENUITEM "すべて閉じる(&L)",            IDM_CLOSEALL
            MENUITEM "アイコンの整列(&A)",          IDM_ARRANGE
    
  2. #include と #define と Global Area です。
    IDM_FIRSTCHILD はクライアントウインドウのIDです。
    FrameClassName はフレームウインドウの ClassName です。
    TITLE はフレームウインドウのタイトルです。
    ChildDoc はドキュメントウインドウの ClassName です。
    hMenuFirst, hMenuDoc はドキュメントウインドウが作成されたいない状態のメニューとサブメニューです。
    hMenuFirstWnd, hMenuDocWnd はドキュメントウインドウが作成された状態のメニューとサブメニューです。
    doc_no は次に作成するドキュメントウインドウの番号です。
        /*******************************************************/
        /*★ 「新規作成」でウインドウを次々と表示    前田 稔 ★*/
        /*******************************************************/
        #include    <windows.h>
        #include    "resource.h"
        #define     IDM_FIRSTCHILD 100
    
        #define     FrameClassName      "mdi01"
        #define     TITLE               "Multi Document Interface"
        #define     ChildDoc            "document"
    
        HINSTANCE   g_hInst;
        HMENU       hMenuFirst, hMenuDoc;
        HMENU       hMenuFirstWnd, hMenuDocWnd;
        int         doc_no;
        
  3. WinMain() 関数では、フレームとドキュメントの Window Class を登録します。
    ドキュメントウインドウが一つもないときと1つ以上存在するときのメニューを作っておきます。
    普通のウインドウと同じようにフレームウインドウを作成してメッセージループに入ります。
    クライアントウインドウはフレームウインドウの CALLBACK 関数の中で作成します。
    ドキュメントウインドウはメニューから選択されたときに作成します。
        int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
        {   MSG     msg;
            HWND    hFrame, hClient;
    
            g_hInst = hInst;
            //新しいウインドウのクラスを登録する
            if(!MyRegisterWC(FrameWndProc, FrameClassName,(HBRUSH)(COLOR_APPWORKSPACE+1)))
                return FALSE;
            if(!MyRegisterWC(DocProc, ChildDoc, (HBRUSH)GetStockObject(WHITE_BRUSH)))
                return FALSE;
    
            //メニューを設定する
            hMenuFirst = LoadMenu(g_hInst, "MYMENU");
            hMenuFirstWnd = GetSubMenu(hMenuFirst, 0);
            hMenuDoc = LoadMenu(g_hInst, "MYDOCUMENT");
            hMenuDocWnd = GetSubMenu(hMenuDoc, 1);
    
            //フレームウインドウを生成する
            hFrame = CreateWindow(FrameClassName,TITLE,
                     WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                     CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                     NULL, hMenuFirst, g_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(hMenuFirst);
            DestroyMenu(hMenuDoc);
            return msg.wParam;
        }
        
  4. フレームとドキュメントの WindowClass を登録する MyRegisterWC() 関数です。
        //新しいウインドウのクラスを登録する
        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 = g_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));
        }
        
  5. フレームウインドウの CALLBACK 関数です。
    WM_CREATE: でクライアントウインドウを生成します。
    新規作成メニューが呼び出されたときに、ドキュメントウインドウを作成します。
    WM_MDICREATE をクライアントウインドウに送ると、新しくドキュメントウインドウが作られます。
    閉じるメニューが呼び出されると、現在アクティブになっているドキュメントウインドウを閉じます。
    WM_MDIDESTROY をクライアントウインドウに送ると、ドキュメントウインドウが削除されます。
    他のメニューもソースコードを見れば説明しなくても解るでしょう。
        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,
                        g_hInst,
                        (LPSTR)&ccs);
                    return 0;
                case WM_COMMAND:
                    switch (LOWORD(wp))
                    {   case IDM_NEW:       //新規作成
                            //ドキュメントウインドウを作るための情報を設定する
                            doc_no++;
                            wsprintf(str, str_org, doc_no);
                            mdic.szClass = ChildDoc;
                            mdic.szTitle = str;
                            mdic.hOwner = g_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);
        }
        
  6. ドキュメントウインドウの CALLBACK 関数です。
    WM_CREATE: でウインドウを生成します。
    WM_MDIACTIVATE: メッセージが来たら条件に応じてクライアントウインドウのメニューを替えます。
        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(
                        NULL,NULL,WS_VISIBLE | WS_CHILD | ES_WANTRETURN | ES_MULTILINE,
                        rc.left, rc.top,
                        rc.right - rc.left,
                        rc.bottom - rc.top,
                        hWnd, NULL,
                        g_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));
        }
        
  7. IDM_CLOSEALL:(すべて閉じる) から呼ばれる CALLBACK 関数です。
    列挙されたドキュメントウインドウ(hWnd) をクローズします。
    WM_MDIDESTROY をクライアントウインドウ(hWnd の親)に送ると、ドキュメントウインドウが削除されます。
        BOOL CALLBACK CloseAllProc(HWND hWnd, LPARAM lp)
        {
            SendMessage(GetParent(hWnd), WM_MDIDESTROY, (WPARAM)hWnd, 0);
            return TRUE;
        }
        
  8. クライアントウインドウのプロシージャは既製品のウインドウクラスなので自分で書く必要はありません。
  9. メニューから「新規作成」を選ぶと、次々とウインドウが作成されます。
    今回はウインドウが作成されるだけで画像は表示されません。

超初心者の方のために全ソースコードを掲載します。 (^_^;)
全ソースコード

超初心者のプログラム入門(Win32API C++)