使用多個檔介面

本節說明如何執行下列工作:

為了說明這些工作,本節包含來自 Multipad 的範例,這是 MDI) 應用程式的一般多文檔介面 (。

註冊子視窗和框架視窗類別

典型的 MDI 應用程式必須註冊兩個視窗類別:一個用於其框架視窗,另一個用於其子視窗。 例如,如果應用程式支援一種以上的檔 (,試算表和圖表) ,則必須為每個類型註冊視窗類別。

框架視窗的類別結構類似于非 MDI 應用程式中主視窗的類別結構。 MDI 子視窗的類別結構與非 MDI 應用程式中子視窗的結構稍有不同,如下所示:

  • 類別結構應該有圖示,因為使用者可以將 MDI 子視窗最小化,就像是一般應用程式視窗一樣。
  • 功能表名稱應該是 Null,因為 MDI 子視窗不能有自己的功能表。
  • 類別結構應該在視窗結構中保留額外的空間。 透過此空間,應用程式可以將檔案名之類的資料與特定的子視窗產生關聯。

下列範例示範 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 應用程式可以建立其視窗。 首先,它會使用 CreateWindowCreateWindowEx 函式來建立框架視窗。 建立框架視窗之後,應用程式會再次使用 CreateWindowCreateWindowEx建立其用戶端視窗。 應用程式應該將 MDICLIENT 指定為用戶端視窗的類別名稱; MDICLIENT 是系統定義的預先註冊視窗類別。 CreateWindowCreateWindowExlpvParam參數應該指向CLIENTCREATESTRUCT結構。 此結構包含下表所述的成員:

member 描述
hWindowMenu 用於控制 MDI 子視窗之視窗功能表的控制碼。 建立子視窗時,應用程式會將其標題新增至視窗功能表做為功能表項目。 然後,使用者可以按一下視窗功能表上的標題來啟動子視窗。
idFirstChild 指定第一個 MDI 子視窗的識別碼。 第一個建立的 MDI 子視窗會指派此識別碼。 系統會使用遞增的視窗識別碼來建立其他視窗。 當子視窗終結時,系統會立即重新指派視窗識別碼,讓範圍保持連續。

 

將子視窗的標題新增至視窗功能表時,系統會將識別碼指派給子視窗。 當使用者按一下子視窗的標題時,框架視窗會收到 WM_COMMAND 訊息,其中包含 wParam 參數中的識別碼。 您應該為 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 函式,再檢查應用程式定義的快速鍵,或在分派訊息之前呼叫 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 應用程式的主視窗。 差別在於框架視窗程式會將它未處理的所有訊息傳遞給 DefFrameProc 函式,而不是 傳遞給 DefWindowProc 函 式。 此外,框架視窗程式也必須傳遞它所處理的某些訊息,包括下表所列的訊息。

訊息 回應
WM_COMMAND 啟動使用者選擇的 MDI 子視窗。 當使用者從 MDI 框架視窗的視窗功能表選擇 MDI 子視窗時,就會傳送此訊息。 此訊息隨附的視窗識別碼會識別要啟動的 MDI 子視窗。
WM_MENUCHAR 當使用者按下 ALT+ – (減號) 鍵組合時,開啟使用中 MDI 子視窗的視窗功能表。
WM_SETFOCUS 將鍵盤焦點傳遞至 MDI 用戶端視窗,接著會將它傳遞給作用中的 MDI 子視窗。
WM_SIZE 調整 MDI 用戶端視窗的大小,以符合新框架視窗的工作區。 如果框架視窗程式將 MDI 用戶端視窗的大小調整為不同的大小,則不應該將訊息傳遞至 DefWindowProc 函 式。

 

Multipad 中的框架視窗程式稱為 MPFrameWndProc。 MPFrameWndProc 處理其他訊息的方式與非 MDI 應用程式類似。 WM_COMMAND Multipad 中的訊息是由本機定義的 CommandHandler 函式處理。 對於命令訊息 Multipad 無法處理,CommandHandler 會呼叫 DefFrameProc 函 式。 如果 Multipad 預設不使用 DefFrameProc ,使用者就無法從視窗功能表啟動子視窗,因為按一下視窗的功能表項目所傳送 的WM_COMMAND 訊息將會遺失。

撰寫子視窗程式

如同框架視窗程式,MDI 子視窗程式預設會使用特殊函式來處理訊息。 子視窗程式未處理的所有訊息都必須傳遞至 DefMDIChildProc 函式,而不是 DefWindowProc 函 式。 此外,某些視窗管理訊息必須傳遞至 DefMDIChildProc,即使應用程式處理訊息,MDI 才能正常運作。 以下是應用程式必須傳遞至 DefMDIChildProc 的訊息。

訊息 回應
WM_CHILDACTI加值稅E 當 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 用戶端視窗。 (應用程式可以使用 CreateWindowEx 函式搭配 WS_EX_MDICHILD 樣式來建立 MDI 子視窗.) 單線程 MDI 應用程式可以使用任一方法來建立子視窗。 多執行緒 MDI 應用程式中的執行緒必須使用 CreateMDIWindowCreateWindowEx 函式,在不同的執行緒中建立子視窗。

WM_MDICREATE訊息的lParam參數是MDICREATESTRUCT結構的遠指標。 結構包含四個維度成員: xy,表示視窗的水準和垂直位置,而 cxcy則表示視窗的水準和垂直範圍。 這些成員可由應用程式明確指派,或者可能會設定為 CW_USEDEFAULT,在此情況下,系統會根據串聯演算法選取位置、大小或兩者。 在任何情況下,都必須初始化所有四個成員。 多板會針對所有維度使用 CW_USEDEFAULT

MDICREATESTRUCT結構的最後一個成員是樣式成員,其可能包含視窗的樣式位。 若要建立可具有任意視窗樣式組合的 MDI 子視窗,請指定 MDIS_ALLCHILDSTYLES 視窗樣式。 未指定此樣式時,MDI 子視窗具有 WS_MINIMIZEWS_MAXIMIZEWS_HSCROLLWS_VSCROLL 樣式作為預設設定。

Multipad 會使用本機定義的 AddFile 函式建立其 MDI 子視窗, (位於來源檔案 MPFILE 中。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 訊息處理期間初始化本身,方法是在其額外資料中初始化檔變數,以及建立編輯控制項的子視窗。