一、概述
MDI視窗包含一個架構視窗和若干子視窗。
實際上,架構視窗本身是一個普通主視窗,不過它的客戶去被一個特殊視窗覆蓋。
這個特殊視窗是系統預定義的“視窗類別”,類名稱為:"MDICLIENT"。它負責各個MDI子視窗的管理。
二、視窗建立
1.註冊一個MDI架構視窗類別,提供MDI架構視窗訊息處理函數
MDI架構視窗訊息處理函數中,將未處理訊息交由DefFrameProc處理
//MDI架構視窗訊息處理函數LRESULT CALLBACK MDIFrameWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ //... //其他訊息交給由系統提供的預設架構處理函數DefFrameProc //其中,第二個參數是客戶區視窗控制代碼 return ::DefFrameProc (hwnd,hwndClient, message, wParam, lParam) ;}
2.註冊多個MDI子視窗類、對應提供各MDI子視窗的訊息處理函數
子視窗訊息處理函數中,將未處理訊息交由MDIDefMDIChildProc處理
//MDI子視窗訊息處理函數LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ //... //... //其他訊息交給由系統提供的預設MDI子視窗處理函數 return ::DefMDIChildProc (hwnd, message, wParam, lParam) ;}
3.在架構視窗的客戶區建立MDI管理子視窗
MDI子視窗的管理實際上是由架構視窗客戶區的"MDILIENT"視窗完成的。
這是一個系統預定義的視窗。
在主視窗收到WM_CREATE訊息後:
case WM_CREATE:{ hinst=((LPCREATESTRUCT) lParam)->hInstance; //填充CLIENTCREATESTRUCT結構 CLIENTCREATESTRUCT clientcreate ; clientcreate.hWindowMenu = hMenuInitWindow ; //用於添加視窗列表的菜單控制代碼 clientcreate.idFirstChild = 50000 ; //起始ID hwndClient =CreateWindowEx(0, "MDICLIENT", //類名稱為"MDICLIENT" NULL, WS_CHILD |WS_CLIPCHILDREN| WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1,//ID hinst, //執行個體控制代碼 &clientcreate); //參數}return 0;
視窗的大小沒有關係,預設的架構視窗訊息處理函數為讓它覆蓋整個客戶區。
MDI客戶區視窗建立後,通過向它發送訊息管理子視窗的建立、銷毀、排列等等。
4.MDI子視窗的建立
可以在菜單中添加命令項,以建立子視窗。
架構視窗的訊息處理函數收到命令後,向MDI客戶區視窗發建立命令。
case ID_NEW:{ MDICREATESTRUCT mdicreate; mdicreate.szClass = szMDIChildName ; //MDI子視窗的類名稱 mdicreate.szTitle = TEXT ("Hello") ; mdicreate.hOwner = hinst ; mdicreate.x = CW_USEDEFAULT ; mdicreate.y = CW_USEDEFAULT ; mdicreate.cx = CW_USEDEFAULT ; mdicreate.cy = CW_USEDEFAULT ; mdicreate.style = 0 ; mdicreate.lParam = 0 ; SendMessage ( hwndClient, //MDI客戶區視窗控制代碼 WM_MDICREATE, //建立MDI子視窗 0, (LPARAM) (LPMDICREATESTRUCT) &mdicreate //建立參數 ) ;}break;
三、訊息迴圈中處理針對MDI的熱鍵
在訊息迴圈中,用TranslateMDISysAccel處理針對MDI的熱鍵。
while (GetMessage (&msg, NULL, 0, 0)){ if (!TranslateMDISysAccel (hwndClient, &msg) && !TranslateAccelerator (hwndFrame, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; }}
四、命令的流向
架構視窗在收到WM_COMMAND等通知訊息後,應該給當前啟用的MDI視窗提供處理機會。
case WM_COMMAND:switch (LOWORD (wParam)){ //針對架構的命令 case ID_ONE: //... return 0; //針對MDI子視窗管理的命令 case IDM_WINDOW_TILE: SendMessage (hwndClient, WM_MDITILE, 0, 0) ; return 0 ; //針對子視窗的命令又子視窗去處理 default: hwndChild = (HWND) SendMessage (hwndClient, WM_MDIGETACTIVE, 0, 0) ; if (IsWindow (hwndChild)) SendMessage (hwndChild, WM_COMMAND, wParam, lParam) ; break ; //..and then to DefFrameProc}break ; //跳出針對WM_COMMAND的case分支,又DefFrameProc處理剩下的命令
五、子視窗的管理
1.概述
給MDI客戶區視窗發控制訊息即可
如:
case WM_COMMAND:switch (LOWORD (wParam)){ case IDM_WINDOW_TILE: SendMessage (hwndClient, WM_MDITILE, 0, 0) ; return 0 ; case IDM_WINDOW_CASCADE: SendMessage (hwndClient, WM_MDICASCADE, 0, 0) ; return 0 ; case IDM_WINDOW_ARRANGE: SendMessage (hwndClient, WM_MDIICONARRANGE, 0, 0) ; return 0; //... //...}break;
2.當前子視窗的關閉
關閉當前啟用視窗時,先向該視窗發送查詢訊息:WM_QUERYENDSESSION。
子視窗的訊息處理迴圈中響應此訊息,作關閉前的一些處理,若能關閉,返回真
否則返回假。
架構視窗中根據此傳回值決定是否關閉視窗。
如果使用者直接按下子視窗的關閉按鈕,則WM_CLOSE訊息直接發送到了子視窗訊息處理函數。
例如:
架構視窗命令處理中:
case IDM_FILE_CLOSE: //獲得當前啟用視窗hwndChild = (HWND) SendMessage (hwndClient, WM_MDIGETACTIVE, 0, 0);//詢問通過後,銷毀視窗if (SendMessage (hwndChild, WM_QUERYENDSESSION, 0, 0)) SendMessage (hwndClient, WM_MDIDESTROY, (WPARAM) hwndChild, 0);return 0;
子視窗的訊息處理函數中:
LRESULT CALLBACK HelloWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) { //... //... case WM_QUERYENDSESSION: case WM_CLOSE: if (IDOK != MessageBox (hwnd, TEXT ("OK to close window?"), TEXT ("Hello"), MB_ICONQUESTION | MB_OKCANCEL)) return 0 ; break ; // i.e., call DefMDIChildProc } return DefMDIChildProc (hwnd, message, wParam, lParam) ;}
3.關閉所有子視窗
當使用命令方式關閉所有子視窗時,需要枚舉所有子視窗進行關閉。
例:
架構視窗響應命令:
case IDM_WINDOW_CLOSEALL: //針對所有子視窗執行CloseEnumProc EnumChildWindows (hwndClient, CloseEnumProc, 0) ; return 0 ;
枚舉函數:
BOOL CALLBACK CloseEnumProc (HWND hwnd, LPARAM lParam){ if (GetWindow (hwnd, GW_OWNER)) // Check for icon title return TRUE ; SendMessage (GetParent (hwnd), WM_MDIRESTORE, (WPARAM) hwnd, 0) ; if (!SendMessage (hwnd, WM_QUERYENDSESSION, 0, 0)) return TRUE ; SendMessage (GetParent (hwnd), WM_MDIDESTROY, (WPARAM) hwnd, 0) ; return TRUE ;}
六、菜單控制
在MDI程式中,可以根據啟用的子視窗而切換架構視窗的菜單。
並且,可以將視窗列表添加到菜單中去。所添加的功能表項目命令是又架構對應的預設訊息處理函數完成的。
1.為每種視窗類別準備一套菜單資源
2.裝載菜單,得到菜單控制代碼
3.架構在建立時,使用架構菜單的控制代碼作為參數。
4.子視窗在啟用時,載入自己菜單到架構視窗
失去焦點時,還原架構菜單。
使用向MDI客戶區視窗發送WM_MDISETMENU或WM_MDISETMENU訊息。
wParam為菜單控制代碼,lParam為欲添加視窗列表的子功能表控制代碼
case WM_MDIACTIVATE: //啟用時,設定架構菜單 if (lParam == (LPARAM) hwnd) SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuHello, (LPARAM) hMenuHelloWindow) ; //失去焦點時,將架構菜單還原 if (lParam != (LPARAM) hwnd) SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuInit, (LPARAM) hMenuInitWindow) ; DrawMenuBar (hwndFrame) ; //注: hwndFrame的得到方法: //hwndClient = GetParent (hwnd) ; //hwndFrame = GetParent (hwndClient) ; return 0 ;