19.1 概述——MDI层次结构
①框架窗口
A、本身是一个普通的主窗口,其客户区被特殊的窗口覆盖,并不直接显示程序的输出。其客户区也被称为“工作区”
B、默认的消息处理函数是DefFrameProc,而不是DefWindowProc。
②客户窗口:
A、系统预定义的窗口类,类名“MDICLIENT”,负责各个MDI子窗口的管理。
B、窗口过程系统己经预先注册,用户程序不需要窗口过程。
③文档窗口:也称为子窗口,用于显示一个文档。
19.2 窗口的建立
(1)框架窗口:先注册一个MDI框架窗口类,并提供MDI框架窗口的窗口过程。
//MDI框架窗口的消息处理函数 LRESULT CALLBACK FrameWndProc (HWND, UINT, WPARAM, LPARAM) ; { …… //其他消息交给MDI框架缺省的处理函数,第2个参数是客户窗口的句柄 return DefFrameProc(hwnd,hwndClient,message,wParam,lParam); }
(2)客户窗口的建立:在主框架窗口WM_CREATE消息中创建
case WM_CREATE: hInst = ((LPCREATESTRUCT)lParam)->hInstance; //填充CLIENTCREATESTRUCT结构体,并根据该结构体来创建客户窗口 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, (LPVOID)&clientcreate);
★注意:客户窗口的大小没有关系。MDI客户区窗口建立后,通过向它发送消息管理子窗口的建立、销毁、排列等。
(3)文档窗口(也叫子窗口)的建立:在主框架窗口的主菜单项中创建。
case IDM_FILE_NEWHELLO: //创建Hello子窗口 mdicreate.szClass = szHelloClass; //MDI子窗口的类名称 mdicreate.szTitle = TEXT("Hello"); //当最大化时该标题加在框架标题后面 mdicreate.hOwner = hInst; //注意,这里是hInst,而不是hwnd mdicreate.x = CW_USEDEFAULT; mdicreate.y = CW_USEDEFAULT; mdicreate.cx = CW_USEDEFAULT; mdicreate.cy = CW_USEDEFAULT; mdicreate.style = 0; mdicreate.lParam = 0; //发送WM_MDICREATE消息给“客户窗口”以便让其根据传入的mdicreate信息创建hello子窗口 hwndChild = (HWND)SendMessage(hwndClient, //MDI客户区窗口句柄 WM_MDICREATE, 0, (LPARAM)(LPMDICREATESTRUCT)&mdicreate);
19.3消息循环中处理针对MDI的加速键
while (GetMessage (&msg, NULL, 0, 0)) { //MDI加速键,如Ctrl+F6关闭当前活动窗口 if (!TranslateMDISysAccel (hwndClient, &msg) && !TranslateAccelerator (hwndFrame, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } }
19.4 命令的流向
框架窗口在收到WM_COMMAND等通知消息后,应该给当前激活的MDI窗口提供处理机会
case WM_COMMAND: switch (LOWORD (wParam)) { //针对框架的命令 case IDM_FILE_NEWHELLO: //... 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处理剩下的命令
19.5 子窗口的管理
(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。子窗口的消息处理循环中响应此消息,作关闭前的一些处理,若能关闭,返回真否则返回假。框架窗口中根据此返回值决定是否关闭窗口。
【框架窗口命令处理中】
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 ; }
19.6 菜单控制
(1)在MDI程序中,可以根据激活的子窗口而切换框架窗口的菜单。并且,可以将文档窗口列表添加到菜单中去。所添加的菜单项命令是又框架对应的缺省消息处理函数完成的。
①为每种窗口类准备一套菜单资源
②装载菜单,得到菜单句柄
③框架在建立时,使用框架菜单的句柄作为参数。
④子窗口在激活时,加载自己菜单到框架窗口
⑤失去焦点时,还原框架菜单。
(2)使用向MDI“客户窗口”发送WM_MDISETMENU或WM_MDISETMENU消息。
case WM_MDIACTIVATE: //wParam为菜单句柄,lParam为欲添加窗口列表的子菜单句柄 //激活时,设置框架菜单 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 ;
【MDIDemo程序】
/*------------------------------------------------------------ HELLOWIN.C -- Displays "Hello, Windows 98!" in client area (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #include "resource.h" /* 本例中主菜单将跟随当前活动的“文档窗口”而发生变化,如选择“Hello子窗口”或“Rect子窗口” 时的主菜单是不一样的。 MdiMenuInit菜单 ——没有文档窗口时的主菜单 MdiMenuHello菜单——跟随"Hello,World"文档窗口 MdiMenuRect菜单 ——跟随“随机矩形”文档窗口 以下三个常量指定了“Window”子菜单在上面三个菜单模板中的位置。程序需要这些信息来 告诉“客户窗口”在哪儿放置文档列表 */ #define INIT_MENU_POS 0 #define HELLO_MENU_POS 2 #define RECT_MENU_POS 1 #define IDM_FIRSTCHILD 50000 //文档列表初始ID LRESULT CALLBACK FrameWndProc (HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK CloseEnumProc(HWND, LPARAM); LRESULT CALLBACK HelloWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK RectWndProc (HWND, UINT, WPARAM, LPARAM); //每个Hello子窗口存储的私有数据的结构 typedef struct tagHELLODATA { UINT iColor; COLORREF clrText; }HELLODATA,*PHELLODATA; //每个Rect子窗口存储的私有数据的结构 typedef struct tagRECDATA { short cxClient; short cyClient; }RECTDATA,*PRECTDATA; TCHAR szAppName[] = TEXT("MDIDemo"); TCHAR szFrameClass[] = TEXT("MdiFrame"); TCHAR szHelloClass[] = TEXT("MdiHelloChild"); TCHAR szRectClass[] = TEXT("MdiRectChild"); HINSTANCE hInst; HMENU hMenuInit, hMenuHello, hMenuRect; HMENU hMenuInitWindow, hMenuHelloWindow, hMenuRectWindow; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwndFrame,hwndClient; HACCEL hAccel; MSG msg ; WNDCLASS wndclass ; hInst = hInstance; //注册框架窗口类 wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = FrameWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE+1) ; wndclass.lpszMenuName = NULL ;//菜单句柄先设为NULL,会在CreateWindow指定 wndclass.lpszClassName = szFrameClass ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } //注册Hello子窗口类 wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = HelloWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = sizeof(HANDLE); //这里要保存额外私有数据:HELLODATA结构 wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; //这里将菜单设为NULL,会在HelloWndProc的WM_MDIACTIVATE中指定 wndclass.lpszClassName = szHelloClass; RegisterClass(&wndclass); //注册Rect子窗口类 wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = RectWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = sizeof(HANDLE); //每个窗口要存储私有数据:RECTDATA结构体 wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; //这里将菜单设为NULL,会在RectWndProc的WM_MDIACTIVATE中指定 wndclass.lpszClassName = szRectClass; RegisterClass(&wndclass); //获取三个可能的菜单及“Window”子菜单 hMenuInit = LoadMenu(hInstance, TEXT("MdiMenuInit")); hMenuHello = LoadMenu(hInstance, TEXT("MdiMenuHello")); hMenuRect = LoadMenu(hInstance, TEXT("MdiMenuRect")); //上述三个菜单模板中的Window子菜单句柄,文档列表会被加在这些子菜单之下 hMenuInitWindow = GetSubMenu(hMenuInit, INIT_MENU_POS); hMenuHelloWindow = GetSubMenu(hMenuHello, HELLO_MENU_POS); hMenuRectWindow = GetSubMenu(hMenuRect, RECT_MENU_POS); //加载加速键 hAccel = LoadAccelerators(hInstance, szAppName); //创建主框架窗口 hwndFrame = CreateWindow (szFrameClass, // window class name TEXT ("MDI Demonstration"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle hMenuInit, //指定菜单为“hMenuInit” hInstance, // program instance handle NULL) ; // creation parameters //因为框架窗口中的WM_CREATE消息中,会创建客户窗口,因此这里获取到的是客户窗口的句柄。 hwndClient = GetWindow(hwndFrame, GW_CHILD); //获得hwndFrame上面最顶层的窗口,即客户窗口 ShowWindow (hwndFrame, iCmdShow) ; UpdateWindow (hwndFrame) ; while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateMDISysAccel(hwndClient,&msg) && !TranslateAccelerator(hwndFrame,hAccel,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } //通常菜单会被所属窗口自动销毁,但不依属某一窗口的菜单需要显式销毁 //本例中,当退出程序时,客户窗口会发关WM_MDIACTIVATE将主菜单设置为hMenuInit, //所以hMenuInit会自动销毁。但hMenuHello和hMenuRect需手动当销毁。 DestroyMenu(hMenuHello); DestroyMenu(hMenuRect); return msg.wParam ; } LRESULT CALLBACK FrameWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndClient; //客户窗口(静态的),因为默认的窗口过程需要传入此参数。 CLIENTCREATESTRUCT clientcreate; HWND hwndChild; MDICREATESTRUCT mdicreate; switch (message) { case WM_CREATE: //填充CLIENTCREATESTRUCT结构体,并根据该结构体来创建客户窗口 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, //菜单ID为1,这里没用。 (LPVOID)&clientcreate); return 0 ; case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_FILE_NEWHELLO: //创建Hello子窗口 mdicreate.szClass = szHelloClass; mdicreate.szTitle = TEXT("Hello"); //当子窗口最大化时,会把该标题加在框架窗口标题后面 mdicreate.hOwner = hInst; //注意,这里是hInst,而不是hwnd mdicreate.x = CW_USEDEFAULT; mdicreate.y = CW_USEDEFAULT; mdicreate.cx = CW_USEDEFAULT; mdicreate.cy = CW_USEDEFAULT; mdicreate.style = 0; mdicreate.lParam = 0; //这个lParam可以给框架窗口和子窗口提供共享某些变量的方法。 //用法:在HelloWndProc的WM_CREATE消息的lParam字段(是个CREATESTRUCT //结构),而这个结构的lpCreateParams字段是一个指向用来创建窗口的 //MDICREATESTRUCT结构的指针,可从指针中取出mdicreate.lParam出来。 //发送WM_MDICREATE消息给“客户窗口”以便让其根据传入的mdicreate信息创建hello子窗口 hwndChild = (HWND)SendMessage(hwndClient, WM_MDICREATE, 0, (LPARAM)(LPMDICREATESTRUCT)&mdicreate); return 0; case IDM_FILE_NEWRECT: mdicreate.szClass = szRectClass; mdicreate.szTitle = TEXT("Rectangles"); //当子窗口最大化时,会把该标题加在框架窗口标题后面 mdicreate.hOwner = hInst; //注意,这里是hInst,而不是hwnd mdicreate.x = CW_USEDEFAULT; mdicreate.y = CW_USEDEFAULT; mdicreate.cx = CW_USEDEFAULT; mdicreate.cy = CW_USEDEFAULT; mdicreate.style = 0; mdicreate.lParam = 0; //与Hello子窗口相同的处理 //发送WM_MDICREATE消息给“客户窗口”以便让其根据传入的mdicreate信息创建Rect子窗口 hwndChild = (HWND)SendMessage(hwndClient, WM_MDICREATE, 0, (LPARAM)(LPMDICREATESTRUCT)&mdicreate); return 0; case IDM_FILE_CLOSE: //关闭当前活动的“文档窗口”(单个),而不是所有文档窗口 //先获取当前活动的“文档窗口” hwndChild = (HWND)SendMessage(hwndClient, WM_MDIACTIVATE, 0, 0); //如果该文档窗口选择“确定”,表示允许关闭该窗口,则向客户窗口发送“销毁”消息。 if (SendMessage(hwndChild, WM_QUERYENDSESSION, 0, 0)) SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwndChild, 0);//wParam为要关闭的子窗口句柄 return 0; case IDM_APP_EXIT: SendMessage(hwnd, WM_CLOSE, 0, 0); return 0; 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; case IDM_WINDOW_CLOSEALL: //这里枚举各子窗口的目的是为了,确定是否关闭。 //这里的第1个参数为hwndClient,表示只枚举客户窗口的所有子窗口 EnumChildWindows(hwndClient, CloseEnumProc, 0); return 0; default: //将其他菜单消息传给当前活动的子窗口 hwndChild = (HWND)SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0); if (IsWindow(hwndChild)) //判断窗口是否存在 { //Color菜单中的消息,发送给Hello子窗口自己去处理, //本例中框架窗口不处理该消息,因为不同的窗口要显示不同颜色的字体 SendMessage(hwndChild, WM_COMMAND, wParam, lParam); } break; //然后,交给DefFrameProc去处理。 } break; case WM_QUERYENDSESSION: case WM_CLOSE: //试图关键所有的子窗口 SendMessage(hwnd, WM_COMMAND, IDM_WINDOW_CLOSEALL, 0); //如果客户窗口上仍有文档窗口,则return不去退出程序 if (NULL != GetWindow(hwndClient, GW_CHILD)) return 0; //这里return 0,表示不退出程序。 //如果客户窗口上己经没有文档窗口了,则break,交由DefFrameProc去最后销毁程序。 break; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } //将未处理的消息交给DefFrameProc(注意:不是DefWindowProc) //所有框架不处理的消息,都必须传给DefFrameProc。 //此外,WM_MENUCHAR、WM_SETFOCUS、WM_SIZE消息,在框架窗口过程中处理完后 //也必须再交给DefFrameProc去进一步处理。 return DefFrameProc (hwnd,hwndClient, message, wParam, lParam) ; } BOOL CALLBACK CloseEnumProc(HWND hwnd, LPARAM lParam) { //Owner(拥有者) ——负责子窗口销毁(内存) //Parent(父窗口) ——负责子窗口显示(显示) if (GetWindow(hwnd, GW_OWNER)) //图标标题窗口?源码的注释是如此的 //但这里,可理解为如果hwnd有拥有者,则 //其拥有者会负责hwnd的释放,这里就不必处理 //直接返回TRUE,表示处理过了。 return TRUE; //先发送消息,询问是否关闭。 //SendMessage返回值0表示不关闭,非0表示关闭。 if (!SendMessage(hwnd, WM_QUERYENDSESSION, 0, 0)) //返回0时,则直接返回TRUE,表示处理过了。 return TRUE; //向“客户窗口”发送销毁该文档窗口的消息 SendMessage(GetParent(hwnd), WM_MDIDESTROY, (WPARAM)hwnd, 0); //GetParent(hwnd)==hwndClient return TRUE; } LRESULT CALLBACK HelloWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static COLORREF clrTextArray[] = { RGB(0, 0, 0), RGB(255, 0, 0), RGB(0, 255, 0), RGB(0,0,255), RGB(255,255,255)}; static HWND hwndClient, hwndFrame; PHELLODATA pHelloData; HDC hdc; PAINTSTRUCT ps; RECT rect; HMENU hMenu; switch (message) { case WM_CREATE: //GlobalAlloc是为了兼容16位保留下来的,新版Windows建议使用HeapAlloc //GetProcessHeap是获取当前进行堆的句柄。HeapAlloc的内存是不能被移动的。 pHelloData = (PHELLODATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HELLODATA)); pHelloData->clrText = RGB(0, 0, 0); pHelloData->iColor = IDM_COLOR_BLACK; SetWindowLong(hwnd, 0, (long)pHelloData); //保存在cbWndExtra指定的额外空间中 //保存“客户窗口”和“框架窗口”的句柄 hwndClient = GetParent(hwnd); hwndFrame = GetParent(hwndClient); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_COLOR_BLACK: case IDM_COLOR_RED: case IDM_COLOR_GREEN: case IDM_COLOR_BLUE: case IDM_COLOR_WHITE: //改变颜色 pHelloData = (PHELLODATA)GetWindowLong(hwnd, 0); hMenu = GetMenu(hwndFrame); CheckMenuItem(hMenu, pHelloData->iColor, MF_UNCHECKED); pHelloData->iColor = LOWORD(wParam); CheckMenuItem(hMenu, pHelloData->iColor, MF_CHECKED); pHelloData->clrText = clrTextArray[pHelloData->iColor - IDM_COLOR_BLACK]; InvalidateRect(hwnd, NULL, FALSE); //只有文字,且输出位置不变,所以无须擦除背景 } break; case WM_PAINT: //绘制文字 hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); //获取该子窗口的私有数据 pHelloData = (PHELLODATA)GetWindowLong(hwnd, 0); SetTextColor(hdc, pHelloData->clrText); //改变文字颜色 DrawText(hdc, TEXT("Hello,World!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0; //因为子窗口不能拥有菜单,所以,这里提供了根据选择的文档来改变主菜单的机会 //该消息由客户窗口发送过来,当“文档窗口”变成活动或非活动时,都会收到 //该消息,wParam为正要变成非活动的窗口句柄,lParam为正要变成活动窗口的句柄 case WM_MDIACTIVATE: //如果本窗口是正要变为活动窗口的,则改变主菜单为hMenuHello //当发送WM_MDISETMENU时,活动窗口将“文档列表”从当前菜单中删除,并追加到新的菜单中去。 // if (lParam == (LPARAM)hwnd) SendMessage(hwndClient, WM_MDISETMENU, //这里要将消息发给“客户窗口”(原因见上面分析) (WPARAM)hMenuHello, //wParam为主菜单句柄 (LPARAM)hMenuHelloWindow); //lParam为显示文档列表的“Window菜单” //设置Color菜单下的选中状态 pHelloData = (PHELLODATA)GetWindowLong(hwnd, 0); CheckMenuItem(hMenuHello, pHelloData->iColor, (lParam ==(LPARAM)hwnd)?MF_CHECKED:MF_UNCHECKED); //如果本窗口正要失去焦点时(即,则将主菜单设为hMenuInit //以下也可以写成lParam!=(LPARAM)hwnd,表示别的窗口变活动窗口,当然就是本窗口变非活动 if (wParam==(WPARAM)hwnd) { SendMessage(hwndClient, WM_MDISETMENU, (WPARAM)hMenuInit, (LPARAM)hMenuInitWindow); } DrawMenuBar(hwndFrame); //改变了窗口以来,一定要重绘一下菜单,否则虽改变了,但不能看到。 //注意,这里要指定为hwndFrame,因为主菜单属于框架窗口的 return 0; case WM_QUERYENDSESSION: case WM_CLOSE: if (IDOK != MessageBox(hwnd, TEXT("OK to close window?"), TEXT("Hello"), MB_ICONQUESTION | MB_OKCANCEL)) return 0; break; //继续执行,调用默认的DefMDIChildProc case WM_DESTROY: pHelloData = (PHELLODATA)GetWindowLong(hwnd, 0); HeapFree(GetProcessHeap(), 0, pHelloData); return 0; } //未处理的信息交给DefMDIChildProc处理(注意:不是DefWindowProc) return DefMDIChildProc(hwnd, message, wParam, lParam); } LRESULT CALLBACK RectWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndClient, hwndFrame; PRECTDATA pRectData; HDC hdc; PAINTSTRUCT ps; HBRUSH hBrush; int xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; switch (message) { case WM_CREATE: //为窗口私有数据分配内存 pRectData = (PRECTDATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(RECTDATA)); SetWindowLong(hwnd, 0, (LONG)pRectData); //计时器开始工作 SetTimer(hwnd, 1, 250, NULL); //保存“客户窗口”和“框架窗口”的句柄 hwndClient = GetParent(hwnd); hwndFrame = GetParent(hwndClient); return 0; case WM_SIZE: //如果没有最小化,则保存窗口的大小 if (wParam !=SIZE_MINIMIZED) { pRectData = (PRECTDATA)GetWindowLong(hwnd, 0); pRectData->cxClient = LOWORD(lParam); pRectData->cyClient = HIWORD(lParam); } break; //WM_SIZE必须传递给DefMDIChildProc处理,除此,WM_CHILDACTIVATE、 //WM_GETMINMAXINFO、WM_MENUCHAR、WM_MOVE、WM_SETFOCUS、WM_SYSCOMMAND //处理完后,都必须传给DefMDIChildProc处理。(见MSDN说明) case WM_TIMER: pRectData = (PRECTDATA)GetWindowLong(hwnd, 0); //rand()产生0-RANDMAX之间的整数 xLeft = rand() % pRectData->cxClient; xRight = rand() % pRectData->cyClient; yTop = rand() % pRectData->cyClient; yBottom = rand() % pRectData->cyClient; nRed = rand() % 255; nGreen = rand() % 255; nBlue = rand() % 255; hdc = GetDC(hwnd); hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue)); SelectObject(hdc, hBrush); Rectangle(hdc, min(xLeft, xRight), min(yTop,yBottom) , max(xLeft, xRight), max(yTop, yBottom)); DeleteObject(hBrush); ReleaseDC(hwnd,hdc); return 0; case WM_PAINT: //清屏(注意:画矩形是在WM_TIMER中绘制,这里只做一件事,就是清屏) InvalidateRect(hwnd, NULL, TRUE); hdc = BeginPaint(hwnd, &ps); EndPaint(hwnd, &ps); return 0; case WM_MDIACTIVATE: //设置为相应的菜单 if (lParam == (LPARAM)hwnd) SendMessage(hwndClient, WM_MDISETMENU, (WPARAM)hMenuRect, (LPARAM)hMenuRectWindow); else SendMessage(hwndClient, WM_MDISETMENU, (WPARAM)hMenuInit,(LPARAM)hMenuInitWindow); DrawMenuBar(hwndFrame); return 0; case WM_DESTROY: pRectData = (PRECTDATA)GetWindowLong(hwnd, 0); HeapFree(GetProcessHeap(), 0, pRectData); KillTimer(hwnd, 1); return 0; } return DefMDIChildProc(hwnd, message, wParam, lParam); }
//resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 MDIDemo.rc 使用 // #define IDM_FILE_NEWHELLO 40001 #define IDM_FILE_NEWRECT 40002 #define IDM_APP_EXIT 40003 #define IDM_FILE_CLOSE 40004 #define IDM_COLOR_BLACK 40005 #define IDM_COLOR_RED 40006 #define IDM_COLOR_GREEN 40007 #define IDM_COLOR_BLUE 40008 #define IDM_COLOR_WHITE 40009 #define IDM_WINDOW_CASCADE 40010 #define IDM_WINDOW_TILE 40011 #define IDM_WINDOW_ARRANGE 40012 #define IDM_WINDOW_CLOSEALL 40013 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 105 #define _APS_NEXT_COMMAND_VALUE 40020 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//MDIDemo.rc
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h " END 2 TEXTINCLUDE BEGIN "#include ""winres.h"" " "