MDI(Multiple Document Interface)視窗包含一個框架視窗與多個子視窗。框架視窗本質是普通主視窗,其客戶區被系統預定義類別 "MDICLIENT" 覆蓋,該類別負責管理所有 MDI 子視窗。
// MDI 框架視窗訊息處理函式
LRESULT CALLBACK MDIFrameWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// 處理自訂訊息...
// 未處理訊息交由 DefFrameProc 處理
// 第二參數為客戶區視窗控制代碼
return ::DefFrameProc(hwnd, hwndClient, message, wParam, lParam);
}
// MDI 子視窗訊息處理函式
LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// 處理自訂訊息...
// 未處理訊息交由 DefMDIChildProc 處理
return ::DefMDIChildProc(hwnd, message, wParam, lParam);
}
於主視窗 WM_CREATE 訊息中建立:
case WM_CREATE:
{
hinst = ((LPCREATESTRUCT)lParam)->hInstance;
CLIENTCREATESTRUCT clientcreate;
clientcreate.hWindowMenu = hMenuInitWindow; // 用於新增視窗清單的功能表控制代碼
clientcreate.idFirstChild = 50000; // 子視窗起始 ID
hwndClient = CreateWindowEx(0,
"MDICLIENT", // 系統預定義類別名稱
NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, 0, 0, 0,
hwnd,
(HMENU)1, // 控制 ID
hinst,
&clientcreate); // 建立參數
}
return 0;
視窗大小無關緊要,預設框架處理函式會使其覆蓋整個客戶區。建立後透過發送訊息管理子視窗建立、銷毀、排列等操作。
在功能表加入建立命令,框架視窗收到命令後發送建立訊息:
case ID_NEW:
{
MDICREATESTRUCT mdicreate;
mdicreate.szClass = szMDIChildName; // 子視窗類別名稱
mdicreate.szTitle = TEXT("Hello");
mdicreate.hOwner = hinst;
mdicreate.x = mdicreate.y = CW_USEDEFAULT;
mdicreate.cx = mdicreate.cy = CW_USEDEFAULT;
mdicreate.style = 0;
mdicreate.lParam = 0;
SendMessage(hwndClient, WM_MDICREATE, 0,
(LPARAM)(LPMDICREATESTRUCT)&mdicreate);
}
break;
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;
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; // 交由 DefFrameProc 處理剩餘命令
}
break;
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;
}
先發送 WM_QUERYENDSESSION 查詢,子視窗回應後決定是否銷毀:
// 框架視窗命令處理
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;
// 子視窗訊息處理
case WM_QUERYENDSESSION:
case WM_CLOSE:
if (IDOK != MessageBox(hwnd, TEXT("確定關閉視窗?"), TEXT("Hello"),
MB_ICONQUESTION | MB_OKCANCEL))
return 0;
break; // 呼叫 DefMDIChildProc
case IDM_WINDOW_CLOSEALL:
EnumChildWindows(hwndClient, CloseEnumProc, 0);
return 0;
// 列舉回呼函式
BOOL CALLBACK CloseEnumProc(HWND hwnd, LPARAM lParam)
{
if (GetWindow(hwnd, GW_OWNER)) 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;
}
根據啟用的子視窗切換框架視窗功能表,並將視窗清單加入功能表:
使用 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;
| 操作 | API / 訊息 | 說明 |
|---|---|---|
| 建立 MDI 客戶區 | CreateWindowEx("MDICLIENT") | 框架視窗 WM_CREATE 中呼叫 |
| 建立子視窗 | WM_MDICREATE | 發送至 hwndClient |
| 銷毀子視窗 | WM_MDIDESTROY | 參數為子視窗控制代碼 |
| 取得啟用子視窗 | WM_MDIGETACTIVE | 回傳子視窗控制代碼 |
| 平鋪排列 | WM_MDITILE | 水平/垂直由參數決定 |
| 階層疊放 | WM_MDICASCADE | 標準疊放效果 |
| 圖示排列 | WM_MDIICONARRANGE | 最小化視窗整理 |
| 設定功能表 | WM_MDISETMENU | 動態切換功能表 |
| 熱鍵轉譯 | TranslateMDISysAccel | 訊息迴路中呼叫 |