MDI 创建
1 CilentWindow
1.1 CLIENTCREATESTRUCT
typedefstruct tagCLIENTCREATESTRUCT {
HANDLE hWindowMenu;
UINT idFirstChild;
} CLIENTCREATESTRUCT, *LPCLIENTCREATESTRUCT;
<1>hWindowMenu 多文档应用窗口程序的菜单
<2>idFirstChild 指定第一个创建的MDI子窗口的ID,其后创建的MDI子窗口ID会在此基础上增加,并且随着窗口的销毁会重新分配ID。它们主要用在MDI子窗口向MDIFrame窗口中发送消息。
1.2 ClientWindow的创建
case WM_CREATE: // Create the client window
clientcreate.hWindowMenu = hMenuInitWindow ;
clientcreate.idFirstChild = IDM_FIRSTCHILD ;
hwndClient = CreateWindow( TEXT("MDICLIENT"), NULL, WS_CHILD |WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1,hInst, (PSTR)&clientcreate) ;
return 0 ;
2 MDI Window
2.1 MDICREATESTRUCT
typedef struct tagMDICREATESTRUCT {
LPCTSTR szClass;
LPCTSTR szTitle;
HANDLE hOwner;
int x;
int y;
int cx;
int cy;
DWORD style;
LPARAM lParam;
} MDICREATESTRUCT,*LPMDICREATESTRUCT;
<1> hOwner 指创建MDI Client 窗口的hInstance
2.2 MDI child 窗口的创建
创建一个MDI child 窗口有三种方式
<1>通过向Client窗口发送WM_MDICREATE
case IDM_FILE_NEWHELLO: // Create a Hello child window
mdicreate.szClass = szHelloClass;
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 ;
hwndChild =(HWND)SendMessage(hwndClient, WM_MDICREATE, 0, (LPARAM) (LPMDICREATESTRUCT) &mdicreate) ;
return0 ;
<2>通过函数CreateMDIWindow
hwndChild=CreateMDIWindow(szHelloClass,TEXT("Hello"),0,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hwndClient,hInst,NULL);
<3>通过函数CreateWindowEx
hwndChild =CreateWindowEx(WS_EX_MDICHILD,szHelloClass,TEXT ("Hello"),0,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, ,hwndClient,hMenuInitWindow,hInst,NULL);
3 窗口消息处理
4 WM_MDIACTIVATE
发送方:
wParam
A handle to the MDI child window to beactivated.
lParam
This parameter is not used.
接受方:
wParam
A handle to the MDI child window beingdeactivated.
lParam
A handle to the MDI child window beingactivated.
通过跟踪消息可以发现在MDI child 窗口获得焦点以及失去焦点的时候,都会受到WM_MDIACTIVATE的消息。此时的lParam = NULL。
当子窗口获取焦点的时候,Client Window 会受到WM_GETACTIVE的消息。
5 WM_QUERYENDSESSION
当用户选择结束会话(session)的或者应用程序调用系统关闭函数的时候,就会发送WM_QUERYSESSION消息。当应用程序返回0 ,会话不会被终止。
wParam
This parameter is reserved for futureuse.
lParam
This parameter can be one or more of thefollowing values. If this parameter is 0, the system is shutting down orrestarting (it is not possible to determine which event is occurring).
<1>用户注销的方法
ExitWindows(0, 0);
或者
ExitWindowsEx(EWX_LOGOFF, 0);
这个时候一般会出现提示对话框
case WM_QUERYENDSESSION:
nOff = MessageBox(NULL,(LPCWSTR)L"Endsession?",(LPCWSTR)L"WM_QUERYENDSESSION",
MB_YESNO);
// ReturnTRUE to continue, FALSE to stop.
returnnOff == IDYES;
break;
<2> 用户重启
ExitWindowsEx(EWX_REBOOT,0);
<3> 用户关机
ExitWindowsEx(EWX_SHUTDOWN,0);
6 MDI窗口类别
一个MDI窗口应用程序包含三种窗口,Frame Window,Client Window ,Child Window。
Frame Window: 类似主窗口,它包含标题栏,菜单栏,边框,最小最大化按钮。
MDI Client Window: 属于已经注册好的窗口类,MDICLIENT,它是FrameWindow的子窗口,也是Child Window 的背景,对子窗口的操作也是通过Client Window 实现的。
Child Window :它是Client Window的子窗口。每个子窗口都有一个边框,标题栏,系统菜单,最小最大化按钮。Child Window 不能被拖到Client Window的外面。
7 MDI APP的菜单
MDI应用程序的菜单是属于FrameWindow的。即便通过
SendMessage(hwndClient, WM_MDISETMENU,(WPARAM)hMenuHello, (LPARAM) hMenuHelloWindow) ;
这样的Client Window 设置的菜单,它就是属于Frame Window的,所以在设计MDI的菜单的时候要注意:
<1> 应该设计多个菜单,Frame Window初始化菜单,各个子窗口打开时的专属菜单。
<2> 各个子窗口都要用到的菜单命令处理应该放在FrameWindow的窗口过程函数中,分属于子窗口独自的就放在子窗口对应的窗口过程函数中。
<3> Frame Window的菜单项中应该只包含新文档窗口的创建,以及整个APP 的关闭的菜单项。
7.2 关于Window Menu:
MSDN上的解释是:
The frame window of an MDI applicationshould include a menu bar with a window menu. The window menu should includeitems that arrange the child windows within the client window or that close allchild windows.
翻译:多文档应用程序的框架窗口应该包含一个带有窗口菜单的菜单栏。窗口才到那应该包含组织客户区窗口内子窗口的菜单项,或者关闭所有的子窗口。
可见:Window Menu指的是主要处理MDI多个子窗口之间排列组合以及关闭的菜单,它并不是什么特别的专业术语。
那么Window Menu 起初是怎么指定的呢?
我们要重新回到CLIENTCREATESTRUCT结构体中,
typedef struct tagCLIENTCREATESTRUCT {
HANDLE hWindowMenu;
UINT idFirstChild;
} CLIENTCREATESTRUCT,*LPCLIENTCREATESTRUCT;
其中 hWindowMenu的解释是:
Handle to the MDI application's windowmenu. An MDI application can retrieve this handle from the menu of the MDIframe window by using the GetSubMenufunction.
讲到这里,一切都清晰明了很多了。
7.3 关于 WM_MDISETMENU
这个子窗口发给Client窗口让其修改Frame Window的整个菜单或者 Window Menu的消息。
SendMessage (hwndClient,WM_MDISETMENU,(WPARAM)hMenuHello, (LPARAM)hMenuHelloWindow) ;
其中的wParam参数代表修改FrameWindow的 整个菜单,lParam参数代表修改Frame Window的WindowMenu。
hMenuHello菜单如下所示:
现在我们进行测试,观察不同参数下的不同效果:
<1> lParam 参数为NULL,wParam 不为NULL的时候:
我们观察到的3个弹出菜单:
<2> 两个参数都不为空的时候
并且令hMenuHelloWindow = GetSubMenu(hMenuHello, 2) ;
得到的结果如下所示:
令hMenuHelloWindow = GetSubMenu(hMenuHello, 1) ;
<3> 当wParam为空,lParam不为空的时候
没有打开新的文档的时候,Frame Window的界面如下所示:SendMessage(hwndClient,WM_MDISETMENU,NULL, (LPARAM) hMenuHelloWindow) ;
利用上面语句改变Frame Window的菜单后,如下所示:
当我们改变初始菜单中 Help 和File的顺序的时候:
<4> wParam ,lParam参数都为NULL的时候:
Window Menu 会被变成NULL,即便此后重新设置也无法改变。
总结:
1 创建MDI窗口程序,首先要创建一个FrameWindow,然后在WM_CREATE消息中创建Client Window。
2 在WM_COMMAND命令中创建 MDI Child Window。
3 在Frame Window的窗口过程函数中,处理各个子窗口的共同命令消息,对于某个窗口的个例消息,交给此窗口的过程函数进行处理。
4 在产生子窗口的时候,通常要改变主窗口的菜单。
程序下载地址: MDI源码