windows sdk编程系列文章 ---- 多文档界面

时间:2023-02-02 12:09:41

http://hi.baidu.com/combojiang/item/0e4b7080dfe014efe496e00e

本教程告诉你怎样创建MDI应用程序.事实上并不是很困难.

理论:

多文档界面(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
);

假如你将 DefFrameProcDefWindowProc作一个对比,你将会注意到它们之间的唯一的不同在于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子窗口,然后恢复框架窗口的标题.