• 第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
  • 相关阅读:
    Jmeter测试接口详细步骤(三)跨线程组传值-BeanShell 后置处理程序
    Jmeter测试接口详细步骤(二)HTTP Cookie管理器
    Jmeter测试接口详细步骤(一)基础操作
    最优化学习3
    最优化学习2
    最优化学习---从解方程到最优化1
    最优化学习---从解方程到最优化
    博客园 文章和随笔区别 (转
    refinedet tensorRT实现
    crnn pytorch 训练、测试
  • 原文地址:https://www.cnblogs.com/5iedu/p/4706356.html
Copyright © 2020-2023  润新知