Teach Yourself Visual C++ 6 in 21 Days

Previous chapterNext chapterContents


- 11 -
Creating Multiple Document Interface Applications



Today, you will learn how to build Multiple Document Interface (MDI) applications using Visual C++. You will be able to build applications that allow users to work on multiple documents at one time, switching between the windows of the application to do their work. In this chapter, you will learn

What Is an MDI Application?

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.

Creating an MDI Drawing Program

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.

Building the Application Shell

To create the application shell for today's application, follow these steps:

1. Create a new AppWizard project. Name the project Day11.

2. On the first step of the AppWizard, select Multiple Documents, as shown in Figure 11.2.

FIGURE 11.2. Specifying an MDI application.

3. Use the default values on the second step of the AppWizard.

4. On the third step of the AppWizard, uncheck the support for ActiveX Controls.

5. On the fourth step of the AppWizard, leave all the default values. Click the Advanced button.

6. In the Advanced Options dialog, enter a three-letter file extension for the files that your application will generate (for example, dhc or dvp). Click the Close button to close the dialog and then click Next to move to the next step of the AppWizard.

7. Use the default settings on the fifth step of the AppWizard.

8. On the sixth and final AppWizard step, leave the base class as CView and click Finish. The AppWizard generates the application shell.

Building the Drawing Functionality

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 from
    m_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.


Adding Menu Handling Functionality

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. Open the Day11Doc.h header file.

2. Scroll down toward the bottom of the header file until you find the protected section where the AFX_MSG message map is declared (search for //{{AFX_MSG(CDay11Doc)).

3. Add the function declarations in Listing 11.1 before the line that you searched for. (The string that you searched for is the beginning marker for the Class Wizard maintained message map. Anything you place between it and the end marker, //}}AFX_MSG, is likely to be removed or corrupted by the Class Wizard.)

LISTING 11.1. THE EVENT-HANDLER DECLARATIONS IN Day11Doc.h.

.
.
.
 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: };

4. Open the Day11Doc.cpp source-code file.

5. Search for the line BEGIN_MESSAGE_MAP and add the lines in Listing 11.2 just after it. It's important that this code be between the BEGIN_MESSAGE_MAP line and the //{{AFX_MSG_MAP line. If these commands are between the //{{AFX_MSG_MAP and //}}AFX_MSG_MAP lines, then the Class Wizard will remove or corrupt them.

LISTING 11.2. THE EVENT-HANDLER MESSAGE MAP ENTRIES IN Day11Doc.cpp.

 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: .

6. Scroll to the bottom of the file and add the two event message handler functions in Listing 11.3.

LISTING 11.3. THE COLOR MENU EVENT-HANDLER FUNCTIONS.

 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.

Adding a Context Menu

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:

1. In the Resource View tab on the workspace pane, right-click the Menu folder.

2. Select Insert Menu from the pop-up menu (or should I say context menu).

3. Select the new menu (still in the workspace pane), open its properties dialog, and name the menu IDR_CONTEXTMENU.

4. In the Menu Designer, specify the top-level menu caption as a single space. This causes Visual C++ to add the first entry in the drop-down portion of the menu.

5. In the first drop-down menu entry, specify the caption as &Width and check the Pop-up check box. (This causes the ID combo box to be disabled and an arrow to display beside the caption, along with another menu entry to the right of the menu entry you are modifying.)

6. Do not add any menu entries into the Width cascading menu at this time (that is left for an exercise at the end of the chapter). Instead, select the menu entry below the Width entry and open its properties dialog. Specify the caption as &Colors and check the Pop-up check box.

7. In the colors cascading menu, add the color menu entries as you did for the IDR_DAY11TYPE menu, using the same property settings. You can select the ID from the drop-down list of IDs, if you would rather search for them instead of type. When you finish, your menu should look like the one in Figure 11.4.

8. Select the Class View tab in the workspace pane.

9. Select the CDay11View class. Open the Class Wizard by selecting View|ClassWizard from the menu.

FIGURE 11.4. The context menu design.

10. Add a function for the WM_CONTEXTMENU event message on the CDay11View class.

11. Edit the function, adding the code in Listing 11.4.

LISTING 11.4. THE CDay11View OnContextMenu FUNCTION.

 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.

Summary

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.

Q&A

Q Because it's basically the same code to create an MDI or SDI application, why would I want to create an SDI application? Why wouldn't I want to make all my applications MDI applications?

A It depends on the application and how it's going to be used. You probably use both types of applications on a daily basis. If you are writing a memo or working on a spreadsheet, you are probably using an MDI application. If you are browsing the World Wide Web, your Web browser is most likely an SDI application. A simple text editor such as Notepad would probably be more difficult for the user as an MDI style application, but as an SDI application, it's just about right (for the task it handles). Certain applications make more sense implemented as an SDI application than as an MDI application. You need to think through how your application is going to be used and determine which model it's more suited for.

Q Some entries on my color menu are changing to the wrong color. How can I determine the problem?

A The problem is that the color menu IDs are probably not in sequential order or are out of order. You can check them by right-clicking on the Day11 resources in the Resource View tab of the workspace pane. Select Resource Symbols from the pop-up menu to display a list of the IDs and the numbers assigned to them in alphabetical order. Start with the Black ID and make sure that the numbers increase by 1 without skipping any numbers. Be sure to check these IDs in the order that the colors appear on the menu (and in the color table in the Day11Doc.cpp file), not in the alphabetical order in which they are displayed in this list. If you find some errors, you have to close Visual C++ and open the Resource.h file in a text editor to renumber the IDs correctly. Once you make the corrections (be sure to delete any duplicates), save your corrections, restart Visual C++, and recompile your application. The color menu should work correctly.

Workshop

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

Quiz

1. What are the five base classes that are used in MDI applications?

2. Why do you have to place the ON_COMMAND_RANGE message map entry outside the section maintained by the Class Wizard?

3. What argument does ON_COMMAND_RANGE pass to the event function?

4. What event message should you use to display a pop-up menu?

Exercise

Add the pull-down and context menus for the width, using the same pen widths as yesterday.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.

Hosted by uCoz