As far as coding an MDI application with Visual C++, there's little difference between creating an SDI and an MDI application. However, when you get deeper into the two application styles, you'll find quite a few differences. Although an SDI application allows the user to work on only one document at a time, it also normally limits the user to working on a specific type of document. MDI applications not only enable the user to work on multiple documents at the same time, but also MDI applications can allow the user to work on multiple types of documents.
An MDI application uses a window-in-a-window style, where there is a frame window around one or more child windows. This is a common application style with many popular software packages, including Word and Excel.
Architecturally, an MDI application is similar to an SDI application. In fact, with a simple MDI application, the only difference is the addition of a second frame class to the other classes that the AppWizard creates, as shown in Figure 11.1. As you can see, the Document/View architecture is still very much the approach you use for developing MDI applications as well as SDI applications.
FIGURE 11.1. The MDI Document/ View architecture.
When you create an MDI application, you will create just one more class than you created with an SDI application. The classes are
The two MDI derived classes, CMDIFrameWnd (the CMainFrame class in your project) and CMDIChildWnd (the CChildFrame class in your project), are the only two classes that are different from the SDI application that you created.
The first of these two classes, the CMDIFrameWnd-derived CMainFrame, is the main frame of the application. It provides an enclosed space on the desktop within which all application interaction takes place. This frame window is the frame to which the menu and toolbars are attached.
The second of these two classes, the CMDIChildWnd-derived CChildFrame class, is the frame that holds the CView class. It is the frame that passes messages and events to the view class for processing or display.
In a sense, the functionality of the frame class in the SDI application has been split into these two classes in an MDI application. There is additional support for running multiple child frames with their own document/view class instances at the same time.
To get a good understanding of just how alike the Document/View architectures are for the SDI and MDI applications, today you will implement that same drawing application that you created yesterday, only this time as an MDI application.
To create the application shell for today's application, follow these steps:
FIGURE 11.2. Specifying an MDI application.
Because you are creating the same application that you created yesterday, only as an MDI application this time, you need to add the same functionality to the application that you added yesterday. To save time, and to reemphasize how alike these two application architectures are, perform the same steps you did yesterday to create the CLine class and add the functionality to the CDay11Doc and CDay11View classes. Add the support into the CDay11Doc and CLine classes for selecting colors and widths, but do not add any menu event message handlers or create the color menu. When you finish adding all that functionality, you should have an application in which you can open multiple drawings, all drawing with only the color black.
CAUTION: Because you haven't created the menus yet, and the color initialization uses the color menu IDs, you will probably have to hard-code the initialization of the color to 0 to get your application to compile. Once you add the color menu, the menu IDs should have been added, so you will be able to return to using the IDs in your code. For the time being, change the line of code in the OnNewDocument function in the CDay11Doc class fromm_nColor = ID_COLOR_BLACK - ID_COLOR_BLACK;
to
m_nColor = 0;
You will also need to make the same sort of change to the GetColor function because it uses one of the color menu IDs also.
Now that you've got all the functionality in your application, you would probably like to add the color menu so you can use all those available colors in your drawings. When you expand the Resource View tree and look in the Menu folder, you'll find not one, but two menus defined. Which one do you add the color menu to?
The IDR_MAINFRAME menu is the menu that is available when no child windows are open. If you run your application and close all child windows, you'll see the menu change, removing all the menus that apply to child windows. Once you open another document, either by creating a new document or by opening an existing document, the menu changes back, returning all the menus that apply to the documents.
The IDR_DAY11TYPE menu is the menu that appears when a child window is open. This menu contains all the functions that apply to documents. Therefore, this is the menu that you need to add the color menu to. Add the color menu by following the same directions as yesterday, using the same menu properties.
Once you add all the menus, you need to add the menu event handlers. Today, you are going to take a different approach to implementing the menu event handlers than you did yesterday. The Q&A section at the end of yesterday's chapter had a discussion of using a single event-handler function for all the color menus. That is what you are going to implement today. Unfortunately, the Class Wizard doesn't understand how to route multiple menu event messages to the same function correctly, so you're going to implement this yourself by following these steps:
. . . 1: #ifdef _DEBUG 2: virtual void AssertValid() const; 3: virtual void Dump(CDumpContext& dc) const; 4: #endif 5: 6: protected: 7: 8: // Generated message map functions 9: protected: 10: afx_msg void OnColorCommand(UINT nID); 11: afx_msg void OnUpdateColorUI(CCmdUI* pCmdUI); 12: //{{AFX_MSG(CDay11Doc) 13: // NOTE - the ClassWizard will add and remove member functions Âhere. 14: // DO NOT EDIT what you see in these blocks of generated Âcode ! 15: //}}AFX_MSG 16: DECLARE_MESSAGE_MAP() 17: private: 18: UINT m_nColor; 19: CObArray m_oaLines;
20: };
1: ////////////////////////////////////////////////////////////////////// 2: // CDay11Doc 3: 4: IMPLEMENT_DYNCREATE(CDay11Doc, CDocument) 5: 6: BEGIN_MESSAGE_MAP(CDay11Doc, CDocument) 7: ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) 8: ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, ÂOnUpdateColorUI) 9: //{{AFX_MSG_MAP(CDay11Doc) 10: // NOTE - the ClassWizard will add and remove mapping macros Âhere. 11: // DO NOT EDIT what you see in these blocks of generated Âcode! 12: //}}AFX_MSG_MAP 13: END_MESSAGE_MAP() 14: 15: const COLORREF CDay11Doc::m_crColors[8] = { 16: RGB( 0, 0, 0), // Black 17: RGB( 0, 0, 255), // Blue 18: . 19: .
20: .
1: void CDay11Doc::OnColorCommand(UINT nID) 2: { 3: // Set the current color 4: m_nColor = nID - ID_COLOR_BLACK; 5: } 6: 7: void CDay11Doc::OnUpdateColorUI(CCmdUI* pCmdUI) 8: { 9: // Determine if the menu entry should be checked 10: pCmdUI->SetCheck(GetColor() == pCmdUI->m_nID ? 1 : 0);
11: }
In Listing 11.1, the two function declarations that you added are specified as event message handlers by the afx_msg function type declarations. These type of function declarations need to have protected access. Otherwise, they are virtually identical to any other class member function declaration.
In Listing 11.2, the two message map entries, ON_COMMAND_RANGE and ON_UPDATE_COMMAND_UI_RANGE, are standard message map entries, but the Class Wizard does not support or understand them. If you examine the message map entries from the previous day's applications, you will notice that there are ON_COMMAND and ON_UPDATE_COMMAND_UI message map entries. These macros have two arguments, the message ID and the event-handler function name that should be called for the event message. These new message map entries function in the same way, but they have two event ID arguments instead of one. The two event ID arguments mark the two ends of a range of event IDs that should be passed to the function specified. These two event IDs should be the first and last menu entries you created when building the color menu.
NOTE: The message map is a mechanism used by Visual C++ and MFC to easily specify event messages and the functions that should be called to handle the event. These message-map commands are converted by the Visual C++ compiler into a fast and efficient map for calling the appropriate event functions when a message is received by the application. Whenever you add a function through the Class Wizard, you are not only adding the function to the code, but you are also adding an entry into the message map for that class.
When you use the ON_COMMAND_RANGE message-map entry, the event message ID is automatically passed as an argument to the event-handler function. This allows you to create the function in Listing 11.3 to handle the color selection event messages. If you compile and run your application at this point, you should find that the color selection functionality is all working just as it did yesterday, as shown in Figure 11.3.
FIGURE 11.3. Running the MDI application.
In most Windows applications, you can right-click the mouse and what is known as a context menu, or pop-up menu, appears. Back on Day 6, "Creating Menus for Your Application," you implemented a simple pop-up menu. However, there is a mechanism for creating and using these context menus when Windows thinks that the menu should be opened. This process allows you to add context menus that behave more consistently with other Windows applications (and if Microsoft changes how the context menus are triggered with a new version of Windows, yours will still behave according to the Windows standard).
An event message WM_CONTEXTMENU is passed to the event queue when the right mouse button is released or when the context menu button is pressed (if you have a newer Windows-enabled keyboard with the context menu button). If you place an event-handler function on the WM_CONTEXTMENU event message, you can display a pop-up menu with confidence that you are showing it at the appropriate time.
To add the context menu to your application, you create a new menu for use as the context menu. To do this, follow these steps:
FIGURE 11.4. The context menu design.
1: void CDay11View::OnContextMenu(CWnd* pWnd, CPoint point) 2: { 3: // TODO: Add your message handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: CMenu menu; 10: 11: // Load the context menu 12: menu.LoadMenu(IDR_CONTEXTMENU); 13: // Get the first sub menu (the real menu) 14: CMenu *pContextMenu = menu.GetSubMenu(0); 15: 16: // Display the context menu for the user 17: pContextMenu->TrackPopupMenu(TPM_LEFTALIGN | 18: TPM_LEFTBUTTON | TPM_RIGHTBUTTON, 19: point.x, point.y, AfxGetMainWnd()); 20: 21: /////////////////////// 22: // MY CODE ENDS HERE 23: ///////////////////////
24: }
This code should all look familiar to you from what you learned on Day 6. If you compile and run your application now, you should be able to click your right mouse button on the child window and change your drawing color from the context menu that opened, as shown in Figure 11.5.
FIGURE 11.5. Using the context menu to change drawing colors.
That wasn't too bad; was it? After yesterday, you probably needed the easy day today, along with all the review of what you did yesterday to help it all sink in. But you did get to learn some new things today. You learned about MDI applications, what they are, and how they differ from SDI applications. You learned how you could take a series of menus and use a single event-handler function for all of them. You also learned how you can create a menu specifically for use as a pop-up context menu and how you can integrate it into an MDI application.
The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you've learned. The answers to the quiz questions and exercises are provided in Appendix B, "Answers."
Add the pull-down and context menus for the width, using the same pen widths as yesterday.
© Copyright, Macmillan Computer Publishing. All rights reserved.