第19章 多文档界面

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""
"
    ""
END

3 TEXTINCLUDE 
BEGIN
    "
"
    ""
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	Shift+F5",          IDM_WINDOW_CASCADE
        MENUITEM "&Tile	Shift+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	Shift+F5",          IDM_WINDOW_CASCADE
        MENUITEM "&Tile	Shift+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