使用多文件介面
本節說明如何執行下列工作:
為了說明這些工作,本節包含來自 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 應用程式可以建立其視窗。 首先,它會使用 CreateWindow 或 CreateWindowEx 函式來建立框架視窗。 建立框架窗口之後,應用程式會再次使用 CreateWindow 或 CreateWindowEx來建立其客戶端視窗。 應用程式應該將 MDICLIENT 指定為用戶端視窗的類別名稱;MDICLIENT 是系統定義的預先註冊窗口類別。 CreateWindow 或 CreateWindowEx 的 lpvParam 參數應該指向 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 應用程式主視窗的視窗程式。 差別在於框架視窗程式會將它未處理的所有訊息傳遞給 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_CHILDACTIVATE | 當調整、移動或顯示 MDI 子視窗時,執行激活處理。 必須傳遞此訊息。 |
WM_GETMINMAXINFO | 根據 MDI 用戶端視窗的目前大小,計算最大化 MDI 子視窗的大小。 |
WM_MENUCHAR | 將訊息傳遞至 MDI 框架視窗。 |
WM_MOVE | 如果 MDI 用戶端滾動條存在,請重新計算它們。 |
WM_SETFOCUS | 如果子視窗不是使用中的 MDI 子視窗,則會啟動子視窗。 |
WM_SIZE | 執行變更視窗大小所需的作業,特別是為了最大化或還原 MDI 子視窗。 無法將此訊息傳遞至 DefMDIChildProc 函式會產生非常不想要的結果。 |
WM_SYSCOMMAND | 處理視窗(先前稱為系統)功能表命令:SC_NEXTWINDOW、SC_PREVWINDOW、SC_MOVE、SC_SIZE和 SC_MAXIMIZE。 |
建立子視窗
若要建立 MDI 子視窗,應用程式可以呼叫 CreateMDIWindow 函式,或將 WM_MDICREATE 訊息傳送至 MDI 用戶端視窗。 (應用程式可以使用 CreateWindowEx 函式搭配 WS_EX_MDICHILD 樣式來建立 MDI 子視窗。單個線程 MDI 應用程式可以使用任一方法來建立子視窗。 多線程 MDI 應用程式中的線程必須使用 CreateMDIWindow 或 CreateWindowEx 函式,在不同的線程中建立子視窗。
WM_MDICREATE 訊息的 lParam 參數是 MDICREATESTRUCT 結構的遠指標。 結構包含四個維度成員:x 和 y,表示視窗的水準和垂直位置,以及 cx 和 cy,表示視窗的水準和垂直範圍。 這些成員可由應用程式明確指派,或者可能設定為 CW_USEDEFAULT,在此情況下,系統會根據級聯演算法選取位置、大小或兩者。 在任何情況下,都必須初始化這四個成員。 Multipad 會針對所有尺寸使用 CW_USEDEFAULT。
MDICREATESTRUCT 結構的最後一個成員是 樣式 成員,其中可能包含視窗的樣式位。 若要建立可以任意組合視窗樣式的 MDI 子視窗,請指定 MDIS_ALLCHILDSTYLES 視窗樣式。 未指定此樣式時,MDI 子視窗具有 WS_MINIMIZE、WS_MAXIMIZE、WS_HSCROLL和 WS_VSCROLL 樣式做為預設設定。
Multipad 會使用本機定義的 AddFile 函式建立其 MDI 子視窗(位於來源檔案 MPFILE 中。C). AddFile 函式會藉由將視窗的 MDICREATESTRUCT 結構中的 szTitle 成員指派給正在編輯的檔案名稱或「未命名」,來設定子視窗的標題。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 函式,並顯示為傳入 WM_CREATE 訊息 CREATESTRUCT 結構中的第一個成員。 在 Multipad 中,子視窗會在 WM_CREATE 訊息處理期間自行初始化,方法是在其額外數據中初始化檔變數,以及建立編輯控件的子視窗。