複数ドキュメント インターフェイスの使用

このセクションでは、次のタスクを実行する方法について説明します。

これらのタスクを説明するために、このセクションには、一般的なマルチドキュメント インターフェイス (MDI) アプリケーションである Multipad の例が含まれています。

子ウィンドウ クラスとフレーム ウィンドウ クラスの登録

一般的な MDI アプリケーションでは、フレーム ウィンドウ用と子ウィンドウ用の 2 つのウィンドウ クラスを登録する必要があります。 アプリケーションで複数の種類のドキュメント (スプレッドシートやグラフなど) がサポートされている場合は、種類ごとにウィンドウ クラスを登録する必要があります。

フレーム ウィンドウのクラス構造は、MDI 以外のアプリケーションのメイン ウィンドウのクラス構造に似ています。 MDI 子ウィンドウのクラス構造は、MDI 以外のアプリケーションの子ウィンドウの構造と若干異なります。

  • 通常のアプリケーション ウィンドウのように MDI 子ウィンドウを最小限に抑えることができるため、クラス構造体にはアイコンが必要です。
  • MDI 子ウィンドウに独自のメニューを設定できないため、メニュー名は NULL にする必要があります。
  • クラス構造体では、ウィンドウ構造に余分な領域を予約する必要があります。 この領域を使用すると、アプリケーションはファイル名などのデータを特定の子ウィンドウに関連付けることができます。

次の例は、Multipad がフレーム および子ウィンドウ クラスを登録する方法を示しています。

BOOL WINAPI InitializeApplication() 
{ 
    WNDCLASS wc; 
 
    // Register the frame window class. 
 
    wc.style         = 0; 
    wc.lpfnWndProc   = (WNDPROC) MPFrameWndProc; 
    wc.cbClsExtra    = 0; 
    wc.cbWndExtra    = 0; 
    wc.hInstance     = hInst; 
    wc.hIcon         = LoadIcon(hInst, IDMULTIPAD); 
    wc.hCursor       = LoadCursor((HANDLE) NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1); 
    wc.lpszMenuName  = IDMULTIPAD; 
    wc.lpszClassName = szFrame; 
 
    if (!RegisterClass (&wc) ) 
        return FALSE; 
 
    // Register the MDI child window class. 
 
    wc.lpfnWndProc   = (WNDPROC) MPMDIChildWndProc; 
    wc.hIcon         = LoadIcon(hInst, IDNOTE); 
    wc.lpszMenuName  = (LPCTSTR) NULL; 
    wc.cbWndExtra    = CBWNDEXTRA; 
    wc.lpszClassName = szChild; 
 
    if (!RegisterClass(&wc)) 
        return FALSE; 
 
    return TRUE; 
} 

フレーム ウィンドウと子ウィンドウの作成

ウィンドウ クラスを登録すると、MDI アプリケーションでウィンドウを作成できます。 最初に、 CreateWindow 関数または CreateWindowEx 関数を使用してフレーム ウィンドウ を作成 します。 フレーム ウィンドウを作成した後、アプリケーションは CreateWindow または CreateWindowEx を使用してクライアント ウィンドウを再度 作成します。 アプリケーションでは、クライアント ウィンドウのクラス名として MDICLIENT を指定する必要があります。 MDICLIENT は、システムによって定義された事前登録されたウィンドウ クラスです。 CreateWindow または CreateWindowExlpvParam パラメーターは、CLIENTCREATESTRUCT 構造体を指す必要があります。 この構造体には、次の表で説明するメンバーが含まれています。

メンバー 説明
hWindowMenu MDI 子ウィンドウの制御に使用されるウィンドウ メニューへのハンドル。 子ウィンドウが作成されると、アプリケーションはメニュー項目としてウィンドウ メニューにタイトルを追加します。 その後、ユーザーは、ウィンドウ メニューのタイトルをクリックして、子ウィンドウをアクティブ化できます。
idFirstChild 最初の MDI 子ウィンドウの識別子を指定します。 作成された最初の MDI 子ウィンドウには、この識別子が割り当てられます。 ウィンドウ識別子をインクリメントして追加のウィンドウが作成されます。 子ウィンドウが破棄されると、ウィンドウ識別子が直ちに再割り当てされ、その範囲が連続した状態に保たれます。

 

子ウィンドウのタイトルがウィンドウ メニューに追加されると、子ウィンドウに識別子が割り当てられます。 ユーザーが子ウィンドウのタイトルをクリックすると、フレーム ウィンドウは wParam パラメーターの識別子を含むWM_COMMAND メッセージを受け取ります。 フレーム ウィンドウのメニューで、メニュー項目識別子と競合しない idFirstChild メンバーの値を指定する必要があります。

マルチパッドのフレーム ウィンドウ プロシージャは、 WM_CREATE メッセージの処理中に MDI クライアント ウィンドウを作成します。 次の例は、クライアント ウィンドウの作成方法を示しています。

case WM_CREATE: 
    { 
        CLIENTCREATESTRUCT ccs; 
 
        // Retrieve the handle to the window menu and assign the 
        // first child window identifier. 
 
        ccs.hWindowMenu = GetSubMenu(GetMenu(hwnd), WINDOWMENU); 
        ccs.idFirstChild = IDM_WINDOWCHILD; 
 
        // Create the MDI client window. 
 
        hwndMDIClient = CreateWindow( "MDICLIENT", (LPCTSTR) NULL, 
            WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL, 
            0, 0, 0, 0, hwnd, (HMENU) 0xCAC, hInst, (LPSTR) &ccs); 
 
        ShowWindow(hwndMDIClient, SW_SHOW); 
    } 
    break; 

子ウィンドウのタイトルは、ウィンドウ メニューの下部に追加されます。 アプリケーションが AppendMenu 関数を使用してウィンドウ メニューに文字列を追加する場合、ウィンドウ メニューが再描画されるときに (子ウィンドウが作成または破棄されるたびに) これらの文字列を子ウィンドウのタイトルで上書きできます。 ウィンドウ メニューに文字列を追加する MDI アプリケーションでは 、InsertMenu 関数を使用し、子ウィンドウのタイトルによってこれらの新しい文字列が上書きされていないことを確認する必要があります。

WS_CLIPCHILDREN スタイルを使用して MDI クライアント ウィンドウを作成し、ウィンドウが子ウィンドウに描画されないようにします。

メイン メッセージ ループの書き込み

MDI アプリケーションのメイン メッセージ ループは、MDI 以外のアプリケーション処理アクセラレータ キーと似ています。 違いは、MDI メッセージ ループは、アプリケーション定義のアクセラレータ キーを確認する前、またはメッセージをディスパッチする前に TranslateMDISysAccel 関数を呼び出す点です。

次の例は、一般的な MDI アプリケーションのメッセージ ループを示しています。 エラーが発生した場合、 GetMessage は -1 を返す可能性があることに注意してください。

MSG msg;
BOOL bRet;

while ((bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else 
    { 
        if (!TranslateMDISysAccel(hwndMDIClient, &msg) && 
                !TranslateAccelerator(hwndFrame, hAccel, &msg))
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

TranslateMDISysAccel 関数は、WM_KEYDOWNメッセージをWM_SYSCOMMANDメッセージに変換し、アクティブな MDI 子ウィンドウに送信します。 メッセージが MDI アクセラレータ メッセージでない場合、関数は FALSE を返します。この場合、アプリケーションは TranslateAccelerator 関数を使用して、アプリケーション定義のアクセラレータ キーのいずれかが押されたかどうかを判断します。 そうでない場合、ループはメッセージを適切なウィンドウ プロシージャにディスパッチします。

フレーム ウィンドウ プロシージャの作成

MDI フレーム ウィンドウのウィンドウ プロシージャは、MDI 以外のアプリケーションのメイン ウィンドウと似ています。 違いは、フレーム ウィンドウ プロシージャが処理しないすべてのメッセージを DefWindowProc 関数ではなく DefFrameProc 関数に渡す点です。 さらに、フレーム ウィンドウ プロシージャは、次の表に示すものも含め、処理するメッセージをいくつか渡す必要があります。

Message Response
WM_COMMAND ユーザーが選択した MDI 子ウィンドウをアクティブにします。 このメッセージは、ユーザーが MDI フレーム ウィンドウのウィンドウ メニューから MDI 子ウィンドウを選択したときに送信されます。 このメッセージに付随するウィンドウ識別子は、アクティブ化する MDI 子ウィンドウを識別します。
WM_MENUCHAR ユーザーが Alt + - (マイナス) キーの組み合わせを押すと、アクティブな MDI 子ウィンドウのウィンドウ メニューが開きます。
WM_SETFOCUS キーボード フォーカスを MDI クライアント ウィンドウに渡し、アクティブな MDI 子ウィンドウに渡します。
Wm_size 新しいフレーム ウィンドウのクライアント領域に収まるように MDI クライアント ウィンドウのサイズを変更します。 フレーム ウィンドウ プロシージャで MDI クライアント ウィンドウのサイズを別のサイズに設定する場合は、 DefWindowProc 関数にメッセージを渡さないでください。

 

Multipad のフレーム ウィンドウ プロシージャは MPFrameWndProc と呼ばれます。 MPFrameWndProc による他のメッセージの処理は、MDI 以外のアプリケーションの処理と似ています。 Multipad のWM_COMMAND メッセージは、ローカルに定義された CommandHandler 関数によって処理されます。 コマンド メッセージ Multipad が処理しない場合、CommandHandler は DefFrameProc 関数を呼び出します。 Multipad が既定で DefFrameProc を 使用しない場合、ウィンドウのメニュー項目をクリックして送信された WM_COMMAND メッセージが失われるため、ユーザーはウィンドウ メニューから子ウィンドウをアクティブ化できません。

子ウィンドウ プロシージャの作成

フレーム ウィンドウ プロシージャと同様に、MDI 子ウィンドウ プロシージャは、既定でメッセージを処理するために特別な関数を使用します。 子ウィンドウ プロシージャが処理しないすべてのメッセージは、DefWindowProc 関数ではなく DefMDIChildProc 関数に渡す必要があります。 さらに、MDI が正しく機能するためには、アプリケーションがメッセージを処理する場合でも、一部のウィンドウ管理メッセージを DefMDIChildProc に渡す必要があります。 アプリケーションが DefMDIChildProc に渡す必要があるメッセージを次に示します。

Message Response
WM_CHILDACTIVATE MDI 子ウィンドウのサイズ、移動、または表示時にアクティブ化処理を実行します。 このメッセージは渡す必要があります。
WM_GETMINMAXINFO MDI クライアント ウィンドウの現在のサイズに基づいて、最大化された MDI 子ウィンドウのサイズを計算します。
WM_MENUCHAR MDI フレーム ウィンドウにメッセージを渡します。
WM_MOVE MDI クライアントのスクロール バーが存在する場合は再計算します。
WM_SETFOCUS アクティブな MDI 子ウィンドウでない場合は、子ウィンドウをアクティブにします。
Wm_size 特に MDI 子ウィンドウを最大化または復元するために、ウィンドウのサイズを変更するために必要な操作を実行します。 このメッセージを DefMDIChildProc 関数に渡さなかった場合、非常に望ましくない結果が生成されます。
WM_SYSCOMMAND ウィンドウ (旧称システム) メニュー コマンドを処理します。 SC_NEXTWINDOWSC_PREVWINDOWSC_MOVESC_SIZESC_MAXIMIZE

 

子ウィンドウの作成

MDI 子ウィンドウを作成するには、アプリケーションで CreateMDIWindow 関数を呼び出すか、 WM_MDICREATE メッセージを MDI クライアント ウィンドウに送信します。 (アプリケーションでは、WS_EX_MDICHILD スタイルの CreateWindowEx 関数を使用して、MDI 子ウィンドウを作成できます)。シングルスレッド MDI アプリケーションでは、どちらのメソッドを使用して子ウィンドウを作成することもできます。 マルチスレッド MDI アプリケーションのスレッドでは、 CreateMDIWindow 関数または CreateWindowEx 関数を使用して、別のスレッドに子ウィンドウを作成する必要があります。

WM_MDICREATE メッセージの lParam パラメーターは、MDICREATESTRUCT 構造体への遠いポインターです。 構造体には、ウィンドウの水平方向と垂直方向の位置を示す xy、ウィンドウの水平方向と垂直方向のエクステントを示す cxcy の 4 つのディメンション メンバーが含まれています。 これらのメンバーは、アプリケーションによって明示的に割り当てることができるか、 CW_USEDEFAULTに設定できます。その場合、システムはカスケード アルゴリズムに従って位置、サイズ、またはその両方を選択します。 いずれの場合も、4 つのメンバーすべてを初期化する必要があります。 マルチパッドでは、すべてのディメンション にCW_USEDEFAULT が使用されます。

MDICREATESTRUCT 構造体の最後のメンバーは、ウィンドウのスタイル ビットを含むスタイル メンバーです。 ウィンドウ スタイルを任意に組み合わせることができる MDI 子ウィンドウを作成するには、 MDIS_ALLCHILDSTYLES ウィンドウ スタイルを指定します。 このスタイルを指定しない場合、MDI 子ウィンドウには、既定の設定として WS_MINIMIZEWS_MAXIMIZEWS_HSCROLLおよびWS_VSCROLL スタイルがあります。

マルチパッドは、ローカルで定義された AddFile 関数 (ソース ファイル MPFILE 内にあります) を使用して MDI 子ウィンドウを作成します。C). AddFile 関数は、ウィンドウの MDICREATESTRUCT 構造体の szTitle メンバーを編集するファイルの名前または "Untitled" に割り当てることで、子ウィンドウのタイトルを設定します。szClass メンバーは、Multipad の InitializeApplication 関数に登録されている MDI 子ウィンドウ クラスの名前に設定されます。 hOwner メンバーは、アプリケーションのインスタンス ハンドルに設定されます。

次の例は、Multipad の AddFile 関数を示しています。

HWND APIENTRY AddFile(pName) 
TCHAR * pName; 
{ 
    HWND hwnd; 
    TCHAR sz[160]; 
    MDICREATESTRUCT mcs; 
 
    if (!pName) 
    { 
 
        // If the pName parameter is NULL, load the "Untitled" 
        // string from the STRINGTABLE resource and set the szTitle 
        // member of MDICREATESTRUCT. 
 
        LoadString(hInst, IDS_UNTITLED, sz, sizeof(sz)/sizeof(TCHAR)); 
        mcs.szTitle = (LPCTSTR) sz; 
    } 
    else 
 
        // Title the window with the full path and filename, 
        // obtained by calling the OpenFile function with the 
        // OF_PARSE flag, which is called before AddFile(). 
 
        mcs.szTitle = of.szPathName; 
 
    mcs.szClass = szChild; 
    mcs.hOwner  = hInst; 
 
    // Use the default size for the child window. 
 
    mcs.x = mcs.cx = CW_USEDEFAULT; 
    mcs.y = mcs.cy = CW_USEDEFAULT; 
 
    // Give the child window the default style. The styleDefault 
    // variable is defined in MULTIPAD.C. 
 
    mcs.style = styleDefault; 
 
    // Tell the MDI client window to create the child window. 
 
    hwnd = (HWND) SendMessage (hwndMDIClient, WM_MDICREATE, 0, 
        (LONG) (LPMDICREATESTRUCT) &mcs); 
 
    // If the file is found, read its contents into the child 
    // window's client area. 
 
    if (pName) 
    { 
        if (!LoadFile(hwnd, pName)) 
        { 
 
            // Cannot load the file; close the window. 
 
            SendMessage(hwndMDIClient, WM_MDIDESTROY, 
                (DWORD) hwnd, 0L); 
        } 
    } 
    return hwnd; 
} 

WM_MDICREATE メッセージの lParam パラメーターで渡されたポインターは CreateWindow 関数に渡され、CREATESTRUCT 構造体の最初のメンバーとして表示され、WM_CREATE メッセージで渡されます。 Multipad では、追加のデータでドキュメント変数を初期化し、編集コントロール 子ウィンドウを作成することで、メッセージ処理WM_CREATE中に子ウィンドウが初期化されます。