概述
http://www-user.tu-chemnitz.de/~heha/petzold/
A Sample MDI Implementation
The MDIDEMO program, shown in Figure 19-2, demonstrates the basics of writing an MDI application.
Figure 19-2. The MDIDEMO program.
MDIDEMO.C/*-------------------------------------------------------- MDIDEMO.C -- Multiple-Document Interface Demonstration (c) Charles Petzold, 1998 --------------------------------------------------------*/ #include <windows.h> #include "resource.h" #define INIT_MENU_POS 0 #define HELLO_MENU_POS 2 #define RECT_MENU_POS 1 #define IDM_FIRSTCHILD 50000 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) ; // structure for storing data unique to each Hello child window typedef struct tagHELLODATA { UINT iColor ; COLORREF clrText ; } HELLODATA, * PHELLODATA ; // structure for storing data unique to each Rect child window typedef struct tagRECTDATA { short cxClient ; short cyClient ; } RECTDATA, * PRECTDATA ; // global variables 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) { HACCEL hAccel ; HWND hwndFrame, hwndClient ; MSG msg ; WNDCLASS wndclass ; hInst = hInstance ; // Register the frame window class 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 ; wndclass.lpszClassName = szFrameClass ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } // Register the Hello child window class wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = HelloWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = sizeof (HANDLE) ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szHelloClass ; RegisterClass (&wndclass) ; // Register the Rect child window class wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = RectWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = sizeof (HANDLE) ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szRectClass ; RegisterClass (&wndclass) ; // Obtain handles to three possible menus & submenus hMenuInit = LoadMenu (hInstance, TEXT ("MdiMenuInit")) ; hMenuHello = LoadMenu (hInstance, TEXT ("MdiMenuHello")) ; hMenuRect = LoadMenu (hInstance, TEXT ("MdiMenuRect")) ; hMenuInitWindow = GetSubMenu (hMenuInit, INIT_MENU_POS) ; hMenuHelloWindow = GetSubMenu (hMenuHello, HELLO_MENU_POS) ; hMenuRectWindow = GetSubMenu (hMenuRect, RECT_MENU_POS) ; // Load accelerator table hAccel = LoadAccelerators (hInstance, szAppName) ; // Create the frame window hwndFrame = CreateWindow (szFrameClass, TEXT ("MDI Demonstration"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenuInit, hInstance, NULL) ; hwndClient = GetWindow (hwndFrame, GW_CHILD) ; ShowWindow (hwndFrame, iCmdShow) ; UpdateWindow (hwndFrame) ; // Enter the modified message loop while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateMDISysAccel (hwndClient, &msg) && !TranslateAccelerator (hwndFrame, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } // Clean up by deleting unattached menus 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: // Create the client window clientcreate.hWindowMenu = hMenuInitWindow ; clientcreate.idFirstChild = IDM_FIRSTCHILD ; hwndClient = CreateWindow (TEXT ("MDICLIENT"), NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU) 1, hInst, (PSTR) &clientcreate) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_NEWHELLO: // Create a Hello child window mdicreate.szClass = szHelloClass ; mdicreate.szTitle = TEXT ("Hello") ; mdicreate.hOwner = hInst ; mdicreate.x = CW_USEDEFAULT ; mdicreate.y = CW_USEDEFAULT ; mdicreate.cx = CW_USEDEFAULT ; mdicreate.cy = CW_USEDEFAULT ; mdicreate.style = 0 ; mdicreate.lParam = 0 ; hwndChild = (HWND) SendMessage (hwndClient, WM_MDICREATE, 0, (LPARAM) (LPMDICREATESTRUCT) &mdicreate) ; return 0 ; case IDM_FILE_NEWRECT: // Create a Rect child window mdicreate.szClass = szRectClass ; mdicreate.szTitle = TEXT ("Rectangles") ; mdicreate.hOwner = hInst ; mdicreate.x = CW_USEDEFAULT ; mdicreate.y = CW_USEDEFAULT ; mdicreate.cx = CW_USEDEFAULT ; mdicreate.cy = CW_USEDEFAULT ; mdicreate.style = 0 ; mdicreate.lParam = 0 ; hwndChild = (HWND) SendMessage (hwndClient, WM_MDICREATE, 0, (LPARAM) (LPMDICREATESTRUCT) &mdicreate) ; return 0 ; case IDM_FILE_CLOSE: // Close the active window hwndChild = (HWND) SendMessage (hwndClient, WM_MDIGETACTIVE, 0, 0) ; if (SendMessage (hwndChild, WM_QUERYENDSESSION, 0, 0)) SendMessage (hwndClient, WM_MDIDESTROY, (WPARAM) hwndChild, 0) ; return 0 ; case IDM_APP_EXIT: // Exit the program SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; // messages for arranging windows 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: // Attempt to close all children EnumChildWindows (hwndClient, CloseEnumProc, 0) ; return 0 ; default: // Pass to active child... hwndChild = (HWND) SendMessage (hwndClient, WM_MDIGETACTIVE, 0, 0) ; if (IsWindow (hwndChild)) SendMessage (hwndChild, WM_COMMAND, wParam, lParam) ; break ; // ...and then to DefFrameProc } break ; case WM_QUERYENDSESSION: case WM_CLOSE: // Attempt to close all children SendMessage (hwnd, WM_COMMAND, IDM_WINDOW_CLOSEALL, 0) ; if (NULL != GetWindow (hwndClient, GW_CHILD)) return 0 ; break ; // i.e., call DefFrameProc case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } // Pass unprocessed messages to DefFrameProc (not DefWindowProc) return DefFrameProc (hwnd, hwndClient, message, wParam, lParam) ; } 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 ; } 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 ; HDC hdc ; HMENU hMenu ; PHELLODATA pHelloData ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: // Allocate memory for window private data pHelloData = (PHELLODATA) HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY, sizeof (HELLODATA)) ; pHelloData->iColor = IDM_COLOR_BLACK ; pHelloData->clrText = RGB (0, 0, 0) ; SetWindowLong (hwnd, 0, (long) pHelloData) ; // Save some window handles 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: // Change the text color pHelloData = (PHELLODATA) GetWindowLong (hwnd, 0) ; hMenu = GetMenu (hwndFrame) ; CheckMenuItem (hMenu, pHelloData->iColor, MF_UNCHECKED) ; pHelloData->iColor = wParam ; CheckMenuItem (hMenu, pHelloData->iColor, MF_CHECKED) ; pHelloData->clrText = clrTextArray[wParam - IDM_COLOR_BLACK] ; InvalidateRect (hwnd, NULL, FALSE) ; } return 0 ; case WM_PAINT: // Paint the window hdc = BeginPaint (hwnd, &ps) ; pHelloData = (PHELLODATA) GetWindowLong (hwnd, 0) ; SetTextColor (hdc, pHelloData->clrText) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, TEXT ("Hello, World!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_MDIACTIVATE: // Set the Hello menu if gaining focus if (lParam == (LPARAM) hwnd) SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuHello, (LPARAM) hMenuHelloWindow) ; // Check or uncheck menu item pHelloData = (PHELLODATA) GetWindowLong (hwnd, 0) ; CheckMenuItem (hMenuHello, pHelloData->iColor, (lParam == (LPARAM) hwnd) ? MF_CHECKED : MF_UNCHECKED) ; // Set the Init menu if losing focus if (lParam != (LPARAM) hwnd) SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuInit, (LPARAM) hMenuInitWindow) ; DrawMenuBar (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 ; // i.e., call DefMDIChildProc case WM_DESTROY: pHelloData = (PHELLODATA) GetWindowLong (hwnd, 0) ; HeapFree (GetProcessHeap (), 0, pHelloData) ; return 0 ; } // Pass unprocessed message to DefMDIChildProc return DefMDIChildProc (hwnd, message, wParam, lParam) ; } LRESULT CALLBACK RectWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndClient, hwndFrame ; HBRUSH hBrush ; HDC hdc ; PRECTDATA pRectData ; PAINTSTRUCT ps ; int xLeft, xRight, yTop, yBottom ; short nRed, nGreen, nBlue ; switch (message) { case WM_CREATE: // Allocate memory for window private data pRectData = (PRECTDATA) HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY, sizeof (RECTDATA)) ; SetWindowLong (hwnd, 0, (long) pRectData) ; // Start the timer going SetTimer (hwnd, 1, 250, NULL) ; // Save some window handles hwndClient = GetParent (hwnd) ; hwndFrame = GetParent (hwndClient) ; return 0 ; case WM_SIZE: // If not minimized, save the window size if (wParam != SIZE_MINIMIZED) { pRectData = (PRECTDATA) GetWindowLong (hwnd, 0) ; pRectData->cxClient = LOWORD (lParam) ; pRectData->cyClient = HIWORD (lParam) ; } break ; // WM_SIZE must be processed by DefMDIChildProc case WM_TIMER: // Display a random rectangle pRectData = (PRECTDATA) GetWindowLong (hwnd, 0) ; xLeft = rand () % pRectData->cxClient ; xRight = rand () % pRectData->cxClient ; 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)) ; ReleaseDC (hwnd, hdc) ; DeleteObject (hBrush) ; return 0 ; case WM_PAINT: // Clear the window InvalidateRect (hwnd, NULL, TRUE) ; hdc = BeginPaint (hwnd, &ps) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_MDIACTIVATE: // Set the appropriate menu 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 ; } // Pass unprocessed message to DefMDIChildProc return DefMDIChildProc (hwnd, message, wParam, lParam) ; } |
MDIDEMO.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" / // Menu MDIMENUINIT MENU DISCARDABLE 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 DISCARDABLE 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 "&CascadetShift+F5", IDM_WINDOW_CASCADE MENUITEM "&TiletShift+F4", IDM_WINDOW_TILE MENUITEM "Arrange &Icons", IDM_WINDOW_ARRANGE MENUITEM "Close &All", IDM_WINDOW_CLOSEALL END END MDIMENURECT MENU DISCARDABLE 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 "&CascadetShift+F5", IDM_WINDOW_CASCADE MENUITEM "&TiletShift+F4", IDM_WINDOW_TILE MENUITEM "Arrange &Icons", IDM_WINDOW_ARRANGE MENUITEM "Close &All", IDM_WINDOW_CLOSEALL END END / // Accelerator MDIDEMO ACCELERATORS DISCARDABLE BEGIN VK_F4, IDM_WINDOW_TILE, VIRTKEY, SHIFT, NOINVERT VK_F5, IDM_WINDOW_CASCADE, VIRTKEY, SHIFT, NOINVERT END |
RESOURCE.H (excerpts)// Microsoft Developer Studio generated include file. // Used by 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 |
MDIDEMO supports two types of extremely simple document windows: one displays "Hello, World!" in the center of its client area, and the other displays a series of random rectangles. (In the source code listings and identifier names, these are referred to as the Hello document and the Rect document.) Different menus are associated with these two types of document windows. The document window that displays "Hello, World!" has a menu that allows you to change the color of the text.
Three Menus
Let's turn first to the MDIDEMO.RC resource script. The resource script defines three menu templates used by the program.
The program displays the MdiMenuInit menu when no document windows are present. This menu simply allows creating a new document or exiting the program.
The MdiMenuHello menu is associated with the document window that displays "Hello, World!" The File submenu allows opening a new document of either type, closing the active document, and exiting the program. The Color submenu lets you set the text color. The Window submenu has options for arranging the document windows in a cascaded or tiled fashion, arranging the document icons, and closing all the windows. This submenu will also list all the document windows as they are created.
The MdiMenuRect menu is associated with the random rectangle document. This is the same as the MdiMenuHello menu except that it does not include the Color submenu.
As usual, the RESOURCE.H header file defines all the menu identifiers. In addition, the following three constants are defined in MDIDEMO.C:
#define INIT_MENU_POS 0 #define HELLO_MENU_POS 2 #define RECT_MENU_POS 1
These identifiers indicate the position of the Window submenu in each of the three menu templates. This information is needed by the program to inform the client window where the document list is to appear. Of course, the MdiMenuInit menu doesn't have a Window submenu, so I've indicated that the list should be appended to the first submenu (position 0). The list will never actually be viewed there, however. (You'll see why this is needed when I discuss the program later.)
The IDM_FIRSTCHILD identifier defined in MDIDEMO.C doesn't correspond to a menu item. This is the identifier that will be associated with the first document window in the list that will appear in the Window submenu. This identifier should be greater than all the other menu IDs.
Program Initialization
In MDIDEMO.C, WinMain begins by registering window classes for the frame window and the two child windows. The window procedures are calledFrameWndProc,HelloWndProc, and RectWndProc. Normally, different icons should be associated with these window classes. For the purpose of simplicity, I've used the standard IDI_APPLICATION icon for the frame and child.
Note that I've defined the hbrBackground field of the WNDCLASS structure for the frame window class to be the COLOR_APPWORKSPACE system color. This is not entirely necessary because the client area of the frame window is covered up by the client window, and the client window has this color anyway. However, using this color looks a little better when the frame window is first displayed.
The lpszMenuName field is set to NULL for each of these three window classes. For the Hello and Rect child window classes, this is normal. For the frame window class, I've chosen to indicate the menu handle in theCreateWindow function when creating the frame window.
The window classes for the Hello and Rect child windows allocate extra space for each window using a nonzero value as thecbWndExtra field of the WNDCLASS structure. This space will be used to store a pointer that will reference a block of memory (the size of the HELLODATA or RECTDATA structures defined near the top of MDIDEMO.C) used to store information unique to each document window.
Next, WinMain uses LoadMenu to load the three menus and save their handles in global variables. Three calls to theGetSubMenu function obtain handles to the Window submenu to which the document list will be appended. These are also saved in global variables. TheLoadAccelerators function loads the accelerator table.
A call to CreateWindow in WinMain creates the frame window. During the WM_CREATE processing inFrameWndProc, the frame window creates the client window. This involves another call toCreateWindow. The window class is set to MDICLIENT, which is the preregistered class for MDI client windows. Much of the support in Windows for MDI is encapsulated in the MDICLIENT window class. The client window procedure serves as an intermediary layer between the frame window and the various document windows. When calling CreateWindow to create the client window, the last argument must be set to a pointer to a structure of type CLIENTCREATESTRUCT. This structure has two fields,
- hWindowMenu is the handle of the submenu to which the document list will be appended. In MDIDEMO, this ishMenuInitWindow, which was obtained duringWinMain. You'll see later how the menu is changed.
- idFirstChild is the menu ID to be associated with the first document window in the document list. This is simply IDM_FIRSTCHILD.
Back in WinMain, MDIDEMO displays the newly created frame window and enters the message loop. The message loop differs a little from a normal loop: after obtaining a message from the message queue with a call toGetMessage, an MDI program passes the message to TranslateMDISysAccel (and toTranslateAccelerator if, like the MDIDEMO program, the program also has menu accelerators).
The TranslateMDISysAccel function translates any keystrokes that may correspond to the special MDI accelerators (Ctrl-F6, for example) into a WM_SYSCOMMAND message. If eitherTranslateMDISysAccel orTranslateAccelerator returns TRUE (indicating that a message was translated by one of these functions), do not callTranslateMessage andDispatchMessage.
Notice the two window handles passed to TranslateMDISysAccel and TranslateAccelerator: hwndClient and hwndFrame, respectively. TheWinMain function obtains thehwndClient window handle by callingGetWindow with the GW_CHILD argument.
Creating the Children
The bulk of FrameWndProc is devoted to processing WM_COMMAND messages that signal menu selections. As usual, the low word of thewParam parameter toFrameWndProc contains the menu ID number.
For menu ID values of IDM_FILE_NEWHELLO and IDM_FILE_NEWRECT, FrameWndProc must create a new document window. This involves initializing the fields of an MDICREATESTRUCT structure (most of which correspond toCreateWindow arguments) and sending the client window a WM_MDICREATE message withlParam set to a pointer to this structure. The client window then creates the child document window. (Another possibility is using theCreateMDIWindow function.)
Normally the szTitle field of the MDICREATESTRUCT structure would be the filename corresponding to the document. The style field can be set to the window styles WS_HSCROLL or WS_VSCROLL or both to include scroll bars in the document window. The style field can also include WS_MINIMIZE or WS_MAXIMIZE to initially display the document window in a minimized or maximized state.
The lParam field of the MDICREATESTRUCT structure provides a way for the frame window and the child window to share some variables. This field could be set to a pointer to a memory block containing a structure. During the WM_CREATE message in the child document window, lParam is a pointer to a CREATESTRUCT structure and thelpCreateParams field of this structure is a pointer to the MDICREATESTRUCT structure used to create the window.
On receipt of the WM_MDICREATE message, the client window creates the child document window and adds the title of the window to the bottom of the submenu specified in the MDICLIENTSTRUCT structure used to create the client window. When the MDIDEMO program creates its first document window, this is the File submenu of the MdiMenuInit menu. We'll see later how this document list gets moved to the Window submenu of theMdiMenuHello andMdiMenuRect menus.
Up to nine documents can be listed on the menu, each preceded by an underlined number from 1 to 9. If more than nine document windows are created, this list is followed by a More Windows item on the menu. This item invokes a dialog box with a list box that lists all the document windows. The maintenance of this document list is one of the nicest features of the Windows MDI support.
More Frame Window Message Processing
Let's continue with FrameWndProc message processing before turning our attention to the child document windows.
When you select Close from the File menu, MDIDEMO closes the active child window. It obtains the handle to the active child window by sending the client window a WM_MDIGETACTIVE message. If the child window responds affirmatively to a WM_QUERYENDSESSION message, then MDIDEMO sends the client window a WM_MDIDESTROY message to close the child window.
Processing the Exit option from the File menu requires only that the frame window procedure send itself a WM_CLOSE message.
Processing the Tile, Cascade, and Arrange Icons options from the Window submenu is a snap, requiring only that the WM_MDITILE, WM_MDICASCADE, and WM_MDIICONARRANGE messages be sent to the client window.
The Close All option is a little more complex. FrameWndProc calls EnumChildWindows, passing a pointer referencing the CloseEnumProc function. This function sends a WM_MDIRESTORE message to each child window, followed by a WM_QUERYENDSESSION and, possibly, a WM_MDIDESTROY message. This is not done for the icon title window, indicated by a non-NULL return value from GetWindow with the GW_OWNER argument.
You'll notice that FrameWndProc does not process any of the WM_COMMAND messages that signal one of the colors being selected from the Color menu. These messages are really the responsibility of the document window. For this reason,FrameWndProc sends all unprocessed WM_COMMAND messages to the active child window so that the child window can process those messages that pertain to its window.
All messages that the frame window procedure chooses not to process must be passed toDefFrameProc. This function replacesDefWindowProc in the frame window procedure. Even if a frame window procedure traps the WM_MENUCHAR, WM_SETFOCUS, or WM_SIZE messages, these also must be passed toDefFrameProc.
Unprocessed WM_COMMAND messages must also be passed to DefFrameProc. In particular,FrameWndProc does not process any of the WM_COMMAND messages resulting from the user selecting one of the documents from the list in the Window submenu. (ThewParam values for these options begin with IDM_FIRSTCHILD.) These messages are passed toDefFrameProc and processed there.
Notice that the frame window does not need to maintain a list of window handles of the document windows that it creates. If ever these handles are needed (such as when processing the Close All option from the menu), they can be obtained usingEnumChildWindows.
The Child Document Windows
Now let's look at HelloWndProc, which is the window procedure used for the child document windows that display "Hello, World!"
As with any window class used for more than one window, static variables defined in the window procedure (or any function called from the window procedure) are shared by all windows created based on that window class.
Data that is unique to each window must be stored using a method other than static variables. One such technique involves window properties. Another approach—the one I used—uses memory space reserved by defining a nonzero value in thecbWndExtra field of the WNDCLASS structure used to register the window class.
In MDIDEMO, I use this space to store a pointer that references a block of memory the size of the HELLODATA structure.HelloWndProc allocates this memory during the WM_CREATE message, initializes the two fields (which indicate the currently checked menu item and the text color), and stores the pointer usingSetWindowLong.
When processing a WM_COMMAND message for changing the text colors (recall that these messages originate in the frame window procedure),HelloWndProc usesGetWindowLong to obtain the pointer to the memory block containing the HELLODATA structure. Using this structure,HelloWndProc unchecks the checked menu item, checks the selected menu item, and saves the new color.
A document window procedure receives the WM_MDIACTIVATE message whenever the window becomes active or inactive (indicated by whether or notlParam holds the window's handle). You'll recall that the MDIDEMO program has three different menus: MdiMenuInit for when no documents are present, MdiMenuHello for when a Hello document window is active, and MdiMenuRect for when a Rect document window is active.
The WM_MDIACTIVATE message provides an opportunity for the document window to change the menu. IflParam contains the window's handle (meaning the window is becoming active),HelloWndProc changes the menu to MdiMenuHello. IflParam holds the handle of another window,HelloWndProc changes the menu to MdiMenuInit.
HelloWndProc changes the menu by sending a WM_MDISETMENU message to the client window. The client window processes this message by removing the document list from the current menu and appending it to the new menu. This is how the document list is transferred from the MdiMenuInit menu (which is in effect when the first document is created) to the MdiMenuHello menu. Do not use theSetMenu function to change a menu in an MDI application.
Another little chore involves the check marks on the Color submenu. Program options such as this should be unique to each document. For example, you should be able to set black text in one window and red text in another. The menu check marks should reflect the option chosen in the active window. For this reason, HelloWndProc unchecks the selected menu item when the window is becoming inactive and checks the appropriate item when the window is becoming active.
The wParam and lParam values of the WM_MDIACTIVATE message are the handles of the windows being deactivated and activated, respectively. The window procedure gets the first WM_MDIACTIVATE message withlParam set to the window's handle. The window procedure gets the last message withlParam set to another value when the window is destroyed. When the user switches from one document to another, the first document window receives a WM_MDIACTIVATE message withlParam set to the handle of the first window, at which time the window procedure sets the menu to MdiMenuInit. The second document window receives a WM_MDIACTIVATE message withlParam set to the handle of the second window, at which time the window procedure sets the menu to MdiMenuHello or MdiMenuRect as appropriate. If all the windows are closed, the menu is left as MdiMenuInit.
You'll recall that FrameWndProc sends the child window a WM_QUERYENDSESSION message when the user selects Close or Close All from the menu.HelloWndProc processes the WM_QUERYENDSESSION and WM_CLOSE messages by displaying a message box and asking the user whether the window can be closed. (In a real program, this message box might ask whether a file needed to be saved.) If the user indicates that the window should not be closed, the window procedure returns 0.
During the WM_DESTROY message, HelloWndProc frees the memory block allocated during the WM_CREATE message.
All unprocessed messages must be passed on to DefMDIChildProc (not DefWindowProc) for default processing. Several messages must be passed to DefMDIChildProc whether or not the child window procedure does something with them. These are WM_CHILDACTIVATE, WM_GETMINMAXINFO, WM_MENUCHAR, WM_MOVE, WM_SETFOCUS, WM_SIZE, and WM_SYSCOMMAND.
RectWndProc is fairly similar to HelloWndProc in much of the overhead involved, but it's a little simpler (that is, no menu options are involved and the window does not verify with the user whether it can be closed), so I needn't discuss it. But note that RectWndProc breaks after processing WM_SIZE, so the message is passed toDefMDIChildProc.
Cleaning Up
In WinMain, MDIDEMO uses LoadMenu to load the three menus defined in the resource script. Normally Windows will destroy a menu at the time the window to which the menu is attached is destroyed. That takes care of the Init menu. However, menus that are not attached to a window should be destroyed explicitly. For this reason, MDIDEMO callsDestroyMenu twice at the end ofWinMain to get rid of the Hello and Rect menus.
最后
以上就是壮观百合为你收集整理的Win32 Series - A Sample MDI ImplementationA Sample MDI Implementation的全部内容,希望文章能够帮你解决Win32 Series - A Sample MDI ImplementationA Sample MDI Implementation所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复