多窗口的創建

多文檔界面(MDI)是同一時刻處理多個文檔的應用程序的一個規範. 你很熟悉記事本.它是單文檔界面(SDI)的一個例子.記事本在一個時候只能處理一個文檔.假如你希望打開另一個文檔,你首先必須關閉你前面打開的那一個.你可以想象這有多麻煩. 和Microsoft Word相比:Word可以隨心所欲的在同一時刻打開任意多個文檔,而且可以讓用戶選擇使用哪一個文檔.Microsoft Word 是多文檔界面(MDI)的一個例子.

MDI應用程序有幾個顯著的特徵:我列舉其中的一些:

  • 有主窗口,在客戶區可以有多個子窗口.所有的子窗口都位於客戶區.
  • 最小化一個子窗口,它最小化到了主窗口的客戶區的左下角.
  • 最大化一個子窗口,它的標題和主窗口的標題合併到了一起.
  • 你可以通過按Ctrl+F4鍵來關閉子窗口,還可以通過按Ctrl+Tab鍵在子窗口之間來切換.

包含子窗口的主窗口被稱爲框架窗口.主窗口的客戶區是子窗口活動的地方,因此有了'框架'這個名字.主窗口的任務要比普通窗口精細一些,因爲它需要爲MDI處理一些協調工作.

爲了在你的客戶區控制任意多個數目的子窗口,你需要一個特殊的窗口:客戶窗口.你可以把客戶窗口看成是覆蓋框架窗口的整個客戶區的一個透明的窗口.客戶窗口才是所有MDI子窗口的實際的父親.客戶窗口是MDI子窗口的真實的監督者.

框架窗口

|

客戶窗口

|


|

|

|

|

|

MDI 子窗口1

MDI 子窗口 2

MDI子窗口3

MDI子窗口4

MDI 子窗口 n

圖1.一個MDI應用程序的層次結構

創建框架窗口

現在我們將注意力放到細節上來.首先你需要創建框架窗口. 創建框架窗口的方法和普通窗口是相同的:調用CreateWindowEx. 和普通窗口相比,有兩個主要的不同.

第一個不同是你必須調用DefFrameProc來處理你的窗口不想處理的窗口信息而不是調用DefWindowProc.這是讓Windows爲你作的保持一個MDI應用程序的垃圾任務的一個方法.假如你忘記使用DefFrameProc,你的應用程序將不具有MDI的功能. DefFrameProc具有下列語法:

LRESULT DefFrameProc(      
     HWND hWnd,
     HWND hWndMDIClient,
     UINT uMsg,
     WPARAM wParam,
     LPARAM lParam
);

假如你將 DefFrameProc 和 DefWindowProc作一個對比,你將會注意到它們之間的唯一的不同在於DefFrameProc有5個參數,而DefWindowProc只有4個.這個增加的參數是客戶窗口的句柄.這個句柄是必須的,有了它Windows纔可以發送MDI-相關的消息給客戶窗口.

第二個不同是你必須在你的框架窗口的消息循環中調用 TranslateMDISysAccel .假如你希望Windows爲你處理MDI相關的加速鍵,比如Ctrl+F4,Ctrl+Tab,那麼這是必須的.它具有下列語法:

BOOL TranslateMDISysAccel(      
     HWND hWndClient,
     LPMSG lpMsg
);

第一個參數是客戶窗口的句柄.對此你應該不會覺得驚訝.因爲客戶窗口是所有MDI子窗口的父親. 第二個參數是你通過調用GetMessage獲得的MSG框架的地址. 我們的想法是傳遞MSG結構給客戶窗口,這樣客戶窗口可以檢測在MSG結構中所包含的MDI相關的按鍵是不是按下去了.假如是的話, 客戶窗口處理這個信息,然後返回一個非零值,否則返回一個假值..

創建框架窗口的步驟總結如下:

  1. 像平常一樣填寫 WNDCLASSEX 結構.
  2. 通過調用 RegisterClassEx註冊框架窗口類.
  3. 通過調用CreateWindowEx創建框架窗口.
  4. 在消息循環中調用TranslateMDISysAccel.
  5. 在窗口過程中,將未處理的消息傳遞給 DefFrameProc 而不是DefWindowProc.
創建客戶窗口

現在我們有了框架窗口,我們可以開始創建客戶窗口了. 客戶窗口類是由Windows預先註冊的. 類的名稱爲"MDICLIENT". 你同樣也需要將 CLIENTCREATESTRUCT 的地址傳遞給 CreateWindowEx. 這個結構具有以下定義:

typedef struct {
HANDLE hWindowMenu;
UINT idFirstChild;
} CLIENTCREATESTRUCT, *LPCLIENTCREATESTRUCT;ds

hWindowMenu 是子菜單的句柄,這個子菜單顯示Windows將要添加的MDI子窗口名稱列表. 我們需要對這一功能進行一點解釋.假如你以前曾經使用過類似Microsoft Word的MDI 應用程序,你將會注意到有一個名稱爲"窗口"的子菜單. 這個菜單一旦激活的話,將會在底部顯示出和窗口管理相關的各種各樣的菜單項, 還有當前打開的MDI子窗口的列表. 這個列表是由Windows自己內部保持的. 你不需要爲它作任何特殊的事情. 僅僅只在需要在hWindowMenu 中傳遞你所希望顯示列表的子菜單的句柄, Windows 將會處理剩下的事情. 注意這個子菜單可以是任何的子菜單:它並不一定要是名稱爲"窗口"的子菜單. 重要的是你應該傳遞你希望顯示窗口列表的子菜單的句柄. 假如你不想要這個列表,你就給 hWindowMenu 賦一個NULL的值就行了. 你可以通過調用GetSubMenu來獲得子菜單的句柄.

idFirstChild 是第一個MDI子窗口的標識號. Windows爲應用程序所創建的每一個新的MDI子窗口相應的增加標識號. 舉個例子, 假如你傳遞100給這個域, 第一個MDI子窗口將會有一個值爲100的標識符, 那麼第二個MDI子窗口也就會有一個值爲101的標識符, 如此這樣下去. 當從窗口列表中選擇MDI子窗口時, 被選擇的MDI子窗口的標識符通過WM_COMMAND傳遞給框架窗口. 正常情況下,你將傳遞"未處理"的WM_COMMAND消息給DefFrameProc. 我用"未處理"這個詞語,是因爲窗口列表中的菜單項不是由你的應用程序創建的, 這樣你的應用程序不知道它們的標識符,而且也沒有它們的句柄. 這是MDI框架窗口又一個特殊的地方. 假如你有窗口列表的話,你必須像這樣來修改你的WM_COMMAND句柄:

case WM_COMMAND:
if(lParam == 0)
{
switch(LOWORD(wParam))
{
case IDM_EXIT:
SendMessage(hWnd,WM_CLOSE,0,0);
break;
case IDM_TILEHORZ:
SendMessage(hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0);
break;
case IDM_TILEVERT:
SendMessage(hwndClient,WM_MDITILE,MDITILE_VERTICAL,0);
break;
case IDM_CASCADE:
SendMessage(hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0);
break;
case IDM_NEW:
SendMessage(hwndClient,WM_MDICREATE,0,(LPARAM)&mdicreate);
break;
case IDM_CLOSE:
{
HWND hTmp = (HWND)SendMessage(hwndClient,WM_MDIGETACTIVE,0,0);
SendMessage(hTmp,WM_CLOSE,0,0);
}
break;
default:
return DefFrameProc(hWnd,hwndClient,Msg,wParam,lParam);
}
}
break;

一般來說,你可以忽略未處理的消息. 但是在MDI的情況下,如果你忽略它們, 當用戶點擊窗口列表中的一個MDI子窗口的名稱時,,這個窗口不會被激活. 你需要將這些消息傳遞給DefFrameProc 這樣它們纔會得到適當的處理.

idFirstChild 賦值的注意之處: 你不能使用0. 你窗口列表將會表現的不正常. 舉個例子, 即使某一個MDI子窗口被激活的話, 窗口列表中的這個MDI子窗口名字前的複選標記也不會顯現. 我們可以選擇一個安全的值,比如100或是一個比100大的值.

給 CLIENTCREATESTRUCT 結構賦值後,你可以通過調用 CreateWindowEx 用預先註冊好的類名"MDICLIENT", 在lParam中傳遞CLIENTCREATESTRUCT結構的地址來創建客戶窗口. 你同樣需要在hWndParent參數中指定框架窗口的句柄, 這樣Windows纔可以知道框架窗口和客戶窗口之間的父-子關係. 你可以使用的窗口風格有:WS_CHILD        ,WS_VISIBLEHE WS_CLIPCHILDREN . 假如你忘了WS_VISIBLE的話, 即使MDI子窗口成功地創建了,你也看不到它們.

以下是創建客戶窗口的步驟:

  1. 獲取你所希望顯示窗口列表的子菜單的句柄.
  2. 將這個菜單句柄的值和你希望作爲第一個MDI子窗口標識符的值一起傳送給CLIENTCREATESTRUCT 結構.
  3. 調用 CreateWindowEx 用類名"MDICLIENT" ,lParam參數爲CLIENTCREATESTRUCT結構的地址,
創建MDI子窗口

現在我們既有了框架窗口,也有了客戶窗口. 下一階段可以開始創建MDI子窗口了.有兩種方法:

  • 你可以發送 WM_MDICREATE消息給客戶窗口,在wParam參數中傳遞類型MDICREATESTRUCT的結構的地址. 這是常用的也是最簡單的MDI子窗口的創建方法.
    case IDM_NEW:
    SendMessage(hwndClient,WM_MDICREATE,0,(LPARAM)&mdicreate);
    break;

    假如創建成功的話, SendMessage 將會返回新創建的MDI子窗口的句柄. 你並不需要保存這個句柄. 如果你需要的話, 你可以通過其它的方法來獲得它. MDICREATESTRUCT有如下定義.

    typedef struct {
    LPCTSTR szClass;
    LPCTSTR szTitle;
    HANDLE hOwner;
    int x;
    int y;
    int cx;
    int cy;
    DWORD style;
    LPARAM lParam;
    } MDICREATESTRUCT, *LPMDICREATESTRUCT;
szClass 你作爲MDI自窗口模板的窗口類的地址 szTitle 你希望出現在子窗口的標題欄的文本的地址 hOwner 應用程序的例程句柄 x,y,lx,ly 子窗口的左上角的座標以及寬度和高度 style 子窗口風格. 假若你用MDIS_ALLCHILDSTYLES創建子窗口的話,你可以使用任何窗口風格. lParam 一個應用程序定義的32位值. 這是在MDI窗口中共享值的一種方法. 如果你不需要它, 將它設爲NULL.
  • 你可以調用 CreateMDIWindow. 這一個功能具有下列語法:
HWND CreateMDIWindow(      
    LPCTSTR lpClassName,
     LPCTSTR lpWindowName,
     DWORD dwStyle,
     int X,
     int Y,
     int nWidth,
     int nHeight,
     HWND hWndParent,
     HINSTANCE hInstance,
     LPARAM lParam
);

如果你仔細地看一下這些參數, 你將會發現它們和MDICREATESTRUCT結構的成員是相同的, 除了 hWndParent.以外. 本質上它和你用WM_MDICREATE傳送的參數數目是相同的. MDICREATESTRUCT不需要hWndParent 域, 因爲你必須用Sendmessage傳送整個結構給正確的子窗口. .

在這裏,你也許會有一些問題: 我應該使用哪一種方法? 在這兩者之間有什麼區別? 答案如下:

WM_MDICREATE方法創建的MDI子窗口作爲調用代碼是同一個線程.這意味這假如這個應用程序只有一個主線程的話, 所有的MDI子窗口都在這個主線程中運行. 這並不是一個大的問題. 但是如果一個或是多個你的MDI子窗口執行一些較長的操作的話, 問題就來了. 想象一下你的整個的應用程序突然之間停止了,對任何事情都沒有反應, 一直持續到MDI子窗口的操作結束.

這個問題正是CreateMDIWindow 設計了所要解決的. CreateMDIWindow 爲每一個MDI子窗口創建了一個單獨的線程. 這樣假如一個MDI子窗口忙的話, 它不會拖累整個應用程序..

有關MDI子窗口的窗口過程還有一點需要說明的地方. 對於框架窗口, 你不能調用DefWindowProc來處理未處理的消息. 與之相反你必須在自窗口的窗口過程中使用DefMDIChildProc . 這個函數具有和DefWindowProc相同的參數.

除了WM_MDICREATE以外,還有其它的MDI相關的窗口消息. 列表如下:

WM_MDIACTIVATE 這條消息由應用程序發送給客戶窗口,告訴客戶窗口激活所選擇的MDI子窗口. 當客戶窗口受到消息後, 它將激活所選擇的MDI子窗口和發送WM_MDIACTIVATE消息給將被激活的子窗口和將變爲非活動窗口的子窗口. 這條消息的用途是雙方面的:應用程序可以用它來激活所希望的子窗口.同時它又可以被MDI子窗口本身用作活動/非活動窗口的指示器.舉個例子,假如每一個MDI子窗口都有不同的菜單, 那麼當它變爲活動或是非活動窗口的時候,它可以利用這個機會來改變框架窗口的菜單 WM_MDICASCADE
WM_MDITILE
WM_MDIICONARRANGE 這些消息處理如何排列MDI子窗口. 舉個例子, 假如你希望MDI子窗口排列成層疊的樣式,發送WM_MDICASCADE消息給客戶窗口. WM_MDIDESTROY 發送這條消息給客戶窗口來關閉一個MDI子窗口. 你應該使用這條消息而不是調用DestroyWindow 因爲假如這個MDI子窗口最大化的話, th這條消息將會恢復框架窗口的標題. 假如你使用DestroyWindow, 框架窗口的標題將不會被恢復. WM_MDIGETACTIVE 發送這條消息檢索當前活動MDI子窗口的句柄. WM_MDIMAXIMIZE
WM_MDIRESTORE 發送 WM_MDIMAXIMIZE來最大化MDI子窗口和WM_MDIRESTORE來將它恢復成以前的狀態. 對於這些操作總是使用這些消息. 假如你使用參數爲SW_MAXIMIZE來調用ShowWindow時,MDI子窗口最大化並沒有問題, 但是當你試圖將它恢復成以前的狀態時,問題就來了. 但是你可以用調用ShowWindow來最小化MDI子窗口. WM_MDINEXT 發送這條消息給客戶窗口,根據wParam和lParam裏相應的值來激活下一個或是前一個MDI子窗口. WM_MDIREFRESHMENU 發送這條消息給客戶窗口來刷新框架窗口的菜單. 注意在發送了這條消息之後, 你必須調用DrawMenuBar 來更新菜單條. WM_MDISETMENU 發送這條消息給客戶窗口來取代框架窗口的整個菜單或是窗口子菜單. 你必須使用這條消息而不是用SetMenu. 在發送了這條消息之後, 你必須調用DrawMenuBar來更新菜單條. 正常情況下當活動的MDI子窗口有它自己的菜單而且你希望用這個活動的子窗口自身的菜單來取代框架窗口的菜單時,你將使用這條消息.

我們將創建一個MDI應用程序的步驟回顧一遍.

  1. 註冊窗口類, 既有框架窗口類也有MDI子窗口類.
  2. 調用CreateWindowEx創建框架窗口.
  3. 在消息循環中調用TranslateMDISysAccel 來處理MDI相關的加速鍵.
  4. 在框架窗口的窗口過程中, 調用DefFrameProc 處理所有你的代碼沒有處理的消息.
  5. 用預選定義好的窗口類名 "MDICLIENT"調用CreateWindowEx來創建客戶窗口, 在lParam參數中傳遞CLIENTCREATESTRUCT結構的地址. 正常情況下,你可以用框架窗口過程中的WM_CREATE句柄來創建一個客戶窗口.
  6. 相應的要創建MDI子窗口,你可以通過調用CreateMDIWindow 來發送WM_MDICREATE消息給客戶窗口.
  7. 在MDI子窗口的窗口過程中, 我們把所有未處理的消息發送給傳遞給DefMDIChildProc.
  8. 假如某一條消息有它的MDI的版本,那我們就使用它的MDI版本. 舉個例子, 我們使用WM_MDIDESTORY消息, 而不是使用DestroyWindow來關閉一個MDI子窗口.
例子:見光盤FirstWindow30 #include "Windows.h"
#include "tchar.h"

#define IDR_MAINMENU 101
#define IDR_CHILDMENU 102
#define IDM_EXIT 40001
#define IDM_TILEHORZ 40002
#define IDM_TILEVERT 40003
#define IDM_CASCADE 40004
#define IDM_NEW 40005
#define IDM_CLOSE 40006

TCHAR ClassName[] = _T("MDIASMClass");
TCHAR AppName[] = _T("Win32 MDI Demo");
TCHAR MDIClientName[] = _T("MDICLIENT");
TCHAR MDIChildClassName[] = _T("Win32asmMDIChild");
TCHAR MDIChildTitle[] = _T("MDI Child");
TCHAR ClosePromptMessage[] = _T("Are you sure you want to close this window?");

MDICREATESTRUCT mdicreate;
HWND hwndFrame;
HINSTANCE g_hInstance;
HWND hwndClient;
HMENU hMainMenu;
HMENU hChildMenu;

LONG CALLBACK ChildProc( HWND hWnd, 
UINT Msg, 
WPARAM wParam, 
LPARAM lParam 
)
{
switch(Msg)
{
case WM_MDIACTIVATE:
{
if((HWND)lParam == hWnd)
{
HMENU hTmpMenu = GetSubMenu(hChildMenu,1);
SendMessage(hwndClient,WM_MDISETMENU,(WPARAM)hChildMenu,(LPARAM)hTmpMenu);
}
else
{
HMENU hTmpMenu = GetSubMenu(hMainMenu,1);
SendMessage(hwndClient,WM_MDISETMENU,(WPARAM)hMainMenu,(LPARAM)hTmpMenu);

}
DrawMenuBar(hwndFrame);
}
break;

case WM_CLOSE:
if(MessageBox(hWnd,ClosePromptMessage,AppName,MB_YESNO) == IDYES)
SendMessage(hwndClient,WM_MDIDESTROY,(WPARAM)hWnd,0);
break;
default:
return DefMDIChildProc(hWnd,Msg,wParam,lParam);
}
return 0;
}


LONG CALLBACK ProcWinMain( HWND hWnd, 
UINT Msg, 
WPARAM wParam, 
LPARAM lParam 
)
{
CLIENTCREATESTRUCT ClientStruct;
switch(Msg)
{
case WM_CREATE:
{
hMainMenu = GetMenu(hWnd);
HMENU hTmpMenu = GetSubMenu(hMainMenu,1);
ClientStruct.hWindowMenu = hTmpMenu;
ClientStruct.idFirstChild = 100;
hwndClient = CreateWindowEx(NULL,MDIClientName,NULL,WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,g_hInstance,&ClientStruct);

mdicreate.szClass = MDIChildClassName;
mdicreate.szTitle = MDIChildTitle;
mdicreate.hOwner = g_hInstance;
mdicreate.x = CW_USEDEFAULT;
mdicreate.y = CW_USEDEFAULT;
mdicreate.cx = CW_USEDEFAULT;
mdicreate.cy = CW_USEDEFAULT;

}
break;

case WM_DESTROY:
PostQuitMessage(0);
break;

case WM_COMMAND:
if(lParam == 0)
{
switch(LOWORD(wParam))
{
case IDM_EXIT:
SendMessage(hWnd,WM_CLOSE,0,0);
break;
case IDM_TILEHORZ:
SendMessage(hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0);
break;
case IDM_TILEVERT:
SendMessage(hwndClient,WM_MDITILE,MDITILE_VERTICAL,0);
break;
case IDM_CASCADE:
SendMessage(hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0);
break;
case IDM_NEW:
SendMessage(hwndClient,WM_MDICREATE,0,(LPARAM)&mdicreate);
break;
case IDM_CLOSE:
{
HWND hTmp = (HWND)SendMessage(hwndClient,WM_MDIGETACTIVE,0,0);
SendMessage(hTmp,WM_CLOSE,0,0);

}
break;
default:
return DefFrameProc(hWnd,hwndClient,Msg,wParam,lParam);
}
}
break;


default:
return DefFrameProc(hWnd,hwndClient,Msg,wParam,lParam);
}
return 0;
}

int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
WNDCLASSEX wc;
MSG msg;

g_hInstance = hInstance;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = ProcWinMain;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE);
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU);
wc.lpszClassName = ClassName;
wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
RegisterClassEx(&wc);

wc.lpfnWndProc = ChildProc;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = MDIChildClassName;
RegisterClassEx(&wc);

hwndFrame = CreateWindowEx(NULL,ClassName,AppName,WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,hInstance,NULL);

hChildMenu = LoadMenu(hInstance,MAKEINTRESOURCE(IDR_CHILDMENU));
ShowWindow(hwndFrame,SW_SHOWNORMAL);
UpdateWindow(hwndFrame);

while(GetMessage(&msg,NULL,0,0))
{
if(!TranslateMDISysAccel(hwndClient,&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

DestroyMenu(hChildMenu);
return msg.wParam;

} 分析:

程序所做的第一件事情就是註冊框架窗口類和MDI子窗口類. 作完這個以後, 程序調用CreateWindowEx來創建框架窗口.用框架窗口的WM_CREATE句柄來創建客戶窗口:

    CLIENTCREATESTRUCT ClientStruct;
   switch(Msg)
   {
   case WM_CREATE:
   {
       hMainMenu = GetMenu(hWnd);
       HMENU hTmpMenu = GetSubMenu(hMainMenu,1);
       ClientStruct.hWindowMenu = hTmpMenu;
       ClientStruct.idFirstChild = 100;
       hwndClient = CreateWindowEx(NULL,MDIClientName,NULL,WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN,CW_USEDEFAULT,          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,g_hInstance,&ClientStruct);

我們調用GetMenu來獲得框架窗口的菜單的句柄, 這個句柄在調用GetSubMenu時將會被用到. 注意我們將1傳遞給 GetSubMenu ,因爲我們希望顯示窗口列表的子菜單是第二個子菜單. 然後我們給CLIENTCREATESTRUCT結構的成員賦值.
下一步我們初始化MDICLIENTSTRUCT 結構. 注意我們不需要在這裏做.要初始化MDICLIENTSTRUCT結構的話,用WM_CREATE消息比較方便.

       mdicreate.szClass = MDIChildClassName;
       mdicreate.szTitle = MDIChildTitle;
       mdicreate.hOwner = g_hInstance;
       mdicreate.x = CW_USEDEFAULT;
       mdicreate.y = CW_USEDEFAULT;
       mdicreate.cx = CW_USEDEFAULT;
       mdicreate.cy = CW_USEDEFAULT;   
    }
    break;

在框架窗口創建之後(也包括客戶窗口), 我們調用LoadMenu從資源中獲取子窗口的菜單.我們需要獲取這個菜單的句柄,這樣當一個MDI子菜單出現時,我們就可以用這個句柄來取代框架窗口的菜單. 在這個應用程序退出到Windows之前不要忘記調用DestroyMenu來去掉這個句柄. 正常情況下當一個應用程序退出的時候,Windows將會自動釋放和窗口相關的菜單. 但是在這種情況下, 子窗口的菜單沒有和任何窗口相關聯, 這樣即使當應用程序退出後半部 子窗口的菜單仍然會佔用寶貴的內存資源.

    hChildMenu = LoadMenu(hInstance,MAKEINTRESOURCE(IDR_CHILDMENU));
    ShowWindow(hwndFrame,SW_SHOWNORMAL);
    UpdateWindow(hwndFrame);

    while(GetMessage(&msg,NULL,0,0))
   {
       if(!TranslateMDISysAccel(hwndClient,&msg))
       {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
   }
    DestroyMenu(hChildMenu);

假如TranslateMDISysAccel 返回一個非零值, 它以爲着Windows已經在處理這條消息.這樣你就不需要爲這條消息做任何事情了.假如返回的值爲零, 那麼這條消息就不是MDI相關的消息, 因此就必須按照通常情況來處理.

LONG CALLBACK ProcWinMain(   HWND hWnd, 
                       UINT Msg, 
                       WPARAM wParam, 
                       LPARAM lParam 
)
{
   CLIENTCREATESTRUCT ClientStruct;
   switch(Msg)
   {
   case WM_CREATE:
    ......
    default:
        return DefFrameProc(hWnd,hwndClient,Msg,wParam,lParam);
    }
    return 0;
}

注意到在框架窗口的窗口過程中, 我們調用DefFrameProc來處理我們不感興趣的消息.

窗口過程的重要之處在 WM_COMMAND句柄. 當用戶從文件菜單中選擇 "New"時, 我們就創建了一個MDI子窗口.

      case IDM_NEW:
               SendMessage(hwndClient,WM_MDICREATE,0,(LPARAM)&mdicreate);
               break;

在我們的例子中,我們通過發送WM_MDICREATE消息給客戶窗口, 同時還要在lParam參數中傳遞MDICREATESTRUCT結構的地址來創建一個MDI子窗口.

LONG CALLBACK ChildProc(   HWND hWnd, 
                       UINT Msg, 
                       WPARAM wParam, 
                       LPARAM lParam 
)
{
   switch(Msg)
   {
   case WM_MDIACTIVATE:
   {
       if((HWND)lParam == hWnd)
       {
           HMENU hTmpMenu = GetSubMenu(hChildMenu,1);
           SendMessage(hwndClient,WM_MDISETMENU,(WPARAM)hChildMenu,(LPARAM)hTmpMenu);
       }
       else
       {
           HMENU hTmpMenu = GetSubMenu(hMainMenu,1);
           SendMessage(hwndClient,WM_MDISETMENU,(WPARAM)hMainMenu,(LPARAM)hTmpMenu);

       }
       DrawMenuBar(hwndFrame);
   }
   break;

   case WM_CLOSE:
       if(MessageBox(hWnd,ClosePromptMessage,AppName,MB_YESNO) == IDYES)
           SendMessage(hwndClient,WM_MDIDESTROY,(WPARAM)hWnd,0);
       break;
   default:
       return DefMDIChildProc(hWnd,Msg,wParam,lParam);
   }
   return 0;
}

當MDI子窗口創建後, 我們可以監視 WM_MDIACTIVATE以察看它是不是一個活動窗口.具體的方法是比較將某一個MDI子窗口的句柄和參數lParam的值進行比較, 參數lParam中包含的是活動子窗口的句柄. 這樣如果兩者匹配的話, 證明這個MDI子窗口就是活動子窗口. 下一步就是將框架窗口的菜單替換成MDI子窗口自身的菜單. 因爲最初的菜單將會被取代, 你比學趕幫超再一次告訴Windows窗口列表將顯示在哪一個子菜單. 這就是我們必須再一次調用GetSubMenu 來檢索子菜單的句柄的原因. 我們發送 WM_MDISETMENU消息給客戶窗口來獲得想要的結果. WM_MDISETMENU中的wParam參數包含了你希望取代最初的菜單的菜單句柄. lParam參數包含的是你希望用來顯示窗口列表的子菜單的句柄.在發送了WM_MDISETMENU之後, 我們調用l DrawMenuBar 來刷新菜單, 否則的話你的菜單將會是一片混亂.

    default:
        return DefMDIChildProc(hWnd,Msg,wParam,lParam);

在MDI子窗口的窗口過程中, 你必須傳送所有未處理的消息給DefMDIChildProc而不是DefWindowProc
           case IDM_TILEHORZ:
               SendMessage(hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0);
               break;
           case IDM_TILEVERT:
               SendMessage(hwndClient,WM_MDITILE,MDITILE_VERTICAL,0);
               break;
           case IDM_CASCADE:
               SendMessage(hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0);
               break;

當用戶在窗口子菜單中選擇一個菜單項時, 我們發送相應的消息給客戶窗口. 假如用戶選擇平鋪窗口, 我們發送 WM_MDITILE 給客戶窗口, 在wParam參數中指定哪一種類型的平鋪. 選擇重疊的話是類似的, 相應的發送WM_MDICASCADE..

               case IDM_CLOSE:
               {
                   HWND hTmp = (HWND)SendMessage(hwndClient,WM_MDIGETACTIVE,0,0);
                   SendMessage(hTmp,WM_CLOSE,0,0);
               }
               break;

假如用戶選擇 "Close" 菜單項, 我們首先必須通過發送WM_MDIGETACTIVE給客戶窗口來獲得當前活動的MDI子窗口的句柄, 返回的值保存在eax寄存器中, 這個值就是當前活動MDI子窗口的句柄. 獲得句柄之後, 我們就可以發送WM_CLOSE給那個窗口了.

case WM_CLOSE:

       if(MessageBox(hWnd,ClosePromptMessage,AppName,MB_YESNO) == IDYES)
           SendMessage(hwndClient,WM_MDIDESTROY,(WPARAM)hWnd,0);
       break;

在MDI子窗口的窗口過程中, 當收到WM_CLOSE的消息時, 就會顯示一個消息框詢問用戶是否確實想關閉着這個窗口. 假如回答是"是"的話, 我們發送WM_MDIDESTROY給客戶窗口. WM_MDIDESTROY關閉MDI子窗口,然後恢復框架窗口的標題.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章