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\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
MDIMENUINIT MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM_FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
END
MDIMENUHELLO MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM_FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM "&Close", IDM_FILE_CLOSE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
POPUP "&Color"
BEGIN
MENUITEM "&Black", IDM_COLOR_BLACK
MENUITEM "&Red", IDM_COLOR_RED
MENUITEM "&Green", IDM_COLOR_GREEN
MENUITEM "B&lue", IDM_COLOR_BLUE
MENUITEM "&White", IDM_COLOR_WHITE
END
POPUP "&Window"
BEGIN
MENUITEM "&Cascade\tShift+F5", IDM_WINDOW_CASCADE
MENUITEM "&Tile\tShift+F4", IDM_WINDOW_TILE
MENUITEM "Arrange &Icons", IDM_WINDOW_ARRANGE
MENUITEM "Close &All", IDM_WINDOW_CLOSEALL
END
END
MDIMENURECT MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM_FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM "&Close", IDM_FILE_CLOSE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
POPUP "&Window"
BEGIN
MENUITEM "&Cascade\tShift+F5", IDM_WINDOW_CASCADE
MENUITEM "&Tile\tShift+F4", IDM_WINDOW_TILE
MENUITEM "Arrange &Icon", IDM_WINDOW_ARRANGE
MENUITEM "Close &All", IDM_WINDOW_CLOSEALL
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Accelerator
//
MDIDEMO ACCELERATORS
BEGIN
VK_F4, IDM_WINDOW_TILE, VIRTKEY, SHIFT, NOINVERT
VK_F5, IDM_WINDOW_CASCADE, VIRTKEY, SHIFT, NOINVERT
END
#endif // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
来源:https://www.cnblogs.com/5iedu/p/4706356.html