0% found this document useful (0 votes)
448 views

7 - Part III MFC's Document-View Architecture

This document provides an overview of MFC's document-view architecture, including menus, keyboard accelerators, command processing, and update command user interface handlers. Key points covered include: - MFC applications use a main frame window that contains child windows like toolbars and the view window. - Menus are defined using resources and menu commands are associated with IDs. Keyboard accelerators provide shortcut keys for commands. - Command messages are routed from frames to documents and views, allowing commands to be handled in these classes. - Update command user interface handlers allow enabling/disabling commands based on application state.

Uploaded by

Jacob Mellado
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
448 views

7 - Part III MFC's Document-View Architecture

This document provides an overview of MFC's document-view architecture, including menus, keyboard accelerators, command processing, and update command user interface handlers. Key points covered include: - MFC applications use a main frame window that contains child windows like toolbars and the view window. - Menus are defined using resources and menu commands are associated with IDs. Keyboard accelerators provide shortcut keys for commands. - Command messages are routed from frames to documents and views, allowing commands to be handled in these classes. - Update command user interface handlers allow enabling/disabling commands based on application state.

Uploaded by

Jacob Mellado
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 136

Part III: MFC's Document-View Architecture

Chapter List
Chapter 12: Menus, Keyboard Accelerators, the Rich Edit Control, and Property Sheets
Chapter 13: Toolbars and Status Bars
Chapter 14: A Reusable Frame Window Base Class
Chapter 15: Separating the Document from Its View
Chapter 16: Reading and Writing Documents
Chapter 17: Printing and Print Preview
Chapter 18: Splitter Windows and Multiple Views
Chapter 19: Context-Sensitive Help
Chapter 20: Dynamic-Link Libraries
Chapter 21: MFC Programs Without Document or View Classes
Chapter 12: Menus, Keyboard Accelerators, the Rich Edit Control,
and Property Sheets
Overview
In the book's examples so far, mouse clicks have triggered most program activity. Even though menu commands might have been
more appropriate, we've used mouse clicks because mouse-click messages are handled simply and directly within the Microsoft
Foundation Class (MFC) library view window. If you want program activity to be triggered when the user chooses a command from a
menu, you must first become familiar with the other application framework elements.
This chapter concentrates on menus and the command routing architecture. Along the way, I'll introduce frames and documents and
explain the relationships between these new application framework elements and the already familiar view element. You'll use the
menu editor to lay out a menu visually, and you'll use the code wizards available from Class View to link document and view
member functions to menu commands. You'll learn how to use special update command user interface member functions to enable
and disable menu commands, and you'll learn how to use keyboard accelerators as menu shortcut keys.
Because you're probably tired of circles and dialog boxes, we'll first examine two new MFC building blocks: the rich edit common
control, which can add powerful text editing features to your application, and property sheets, which are ideal for setting edit options.
The Main Frame Window and Document Classes
Up to now, we've been using a view window as if it were the application's only window. In a Single Document Interface (SDI)
application, the view window sits inside another windowthe application's main frame window. The main frame window has the title
bar and the menu bar. Various child windows, including the toolbar window, the view window, and the status bar window, occupy the
main frame window's client area, as shown in Figure 12-1. The application framework controls the interaction between the frame and
the view by routing messages from the frame to the view.

Figure 12-1: The child windows within an SDI main frame window.
Look again at any project files generated by the MFC Application Wizard. The MainFrm.h and MainFrm.cpp files contain the










Page 1 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
code for the application's main frame window class, which is derived from the class CFrameWnd. Other files, with names such as
Ex12aDoc.h and Ex12aDoc.cpp, contain code for the application's document class, which is derived from CDocument. In this
chapter, you'll begin working with the MFC document class. You'll start by learning that each view object has exactly one document
object attached and that the view's inherited GetDocument member function returns a pointer to that object. You'll learn much more
about the document-view interactions in Chapter 15.
Windows Menus
A Microsoft Windows menu is a familiar application element that consists of a top-level horizontal list of menus with submenus that
appear when the user selects a top-level command. Most of the time, you define for a frame window a default menu resource that
loads when the window is created. You can also define a menu resource independent of a frame window. In that case, your program
must call the functions necessary to load and activate the menu.
A menu resource completely defines the initial appearance of a menu. Menu commands can be grayed out or have check marks,
and bars can separate groups of menu commands. Multiple levels of associated menus are possible. If a first-level menu command
is associated with a submenu, the menu command carries a right-pointing arrow symbol, as shown next to the Windows menu
command in Figure 12-2.

Figure 12-2: Submenus (shown in Microsoft Visual C++ .NET).
Visual C++ .NET includes an easy-to-use menu-resource editing tool. This tool lets you edit menus in a WYSIWYG environment.
Each menu command has a properties dialog box that defines all the characteristics of that command. The resulting resource
definition is stored in the application's resource script (RC) file. Each command is associated with an ID, such as ID_FILE_OPEN,
that is defined in the Resource.h file.
The MFC library extends the functionality of the standard menus for Windows. Each menu command can have a prompt string that
appears in the frame's status bar when the command is highlighted. These prompts are really Windows string resource elements
linked to the menu command by a common ID. From the point of view of the menu editor and your program, the prompts appear to
be part of the menu command definition.
Keyboard Accelerators
You've probably noticed that most menu commands contain an underlined letter. In Visual C++ .NET (and most other applications),
pressing Alt+F,S activates the File Save menu command. This shortcut system is the standard Windows method of using the
keyboard to choose commands from menus. If you look at an application's menu resource script (or the menu editor's properties
dialog box), you'll see an ampersand (&) preceding the character that is underlined in each of the application's menu commands.
Windows offers an alternative way of linking keystrokes to menu commands. The keyboard accelerator resource consists of a table
of key combinations with associated command IDs. The Edit Copy command (with the command ID ID_EDIT_COPY), for example,
might be linked to the Ctrl+C key combination through a keyboard accelerator entry. A keyboard accelerator entry does not have to
be associated with a menu command. If no Edit Copy command were present, the Ctrl+C key combination would nevertheless
activate the ID_EDIT_COPY command.
Command Processing
As you saw in Chapter 2, the application framework provides a sophisticated routing system for command messages. These
messages originate from menu commands, keyboard accelerators, and toolbar and dialog box button clicks. Command messages
can also be sent by calls to the CWnd::SendMessage or PostMessage function. Each message is identified by a #define constant
that is often assigned by a resource editor. The application framework has its own set of internal command message IDs, such as
ID_FILE_PRINT and ID_FILE_OPEN. Your project's Resource.h file contains IDs that are unique to your application.








Note If a keyboard accelerator is associated with a menu command or toolbar button, the accelerator key will be disabled when
the command or button is disabled.




Page 2 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Most command messages originate in the application's frame window, and without the application framework in the picture, that's
where you would put the message handlers. With command routing, however, you can handle a message almost anywhere. When
the application framework sees a frame window command message, it starts looking for message handlers in one of the sequences
listed here.
Most applications have a particular command handler in only one class, but suppose your one-view application has an identical
handler in both the view class and the document class. Because the view is higher in the command route, only the view's command
handler function will be called.
What is required to install a command handler function? The installation requirements are similar to those of the window message
handlers you've already seen. You need the function itself, a corresponding message map entry, and the function prototype.
Suppose you have a menu command named Zoom (with IDM_ZOOM as the associated ID) that you want your view class to handle.
You first add the following code to your view implementation file:
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND(IDM_ZOOM, OnZoom)
END_MESSAGE_MAP()

void CMyView::OnZoom()
{
// command message processing code
}
Next, add the following function prototype to the CMyView class header file (before the DECLARE_MESSAGE_MAP macro):
afx_msg void OnZoom();
Of course, Visual Studio .NET automates the process of inserting command message handlers the same way it facilitates the
insertion of window message handlers. You'll learn how this works in the next example, Ex12a.
Command Message Handling in Derived Classes
The command routing system is one dimension of command message handling. The class hierarchy is a second dimension. If you
look at the source code for the MFC library classes, you'll see lots of ON_COMMAND message map entries. When you derive a
class from one of these base classesfor example, CView the derived class inherits all the CView message map functions,
including the command message functions. To override one of the base class message map functions, you must add both a function
and a message map entry to your derived class.
Update Command User Interface Handlers
You often need to change the appearance of a menu command to match the internal state of your application. If your application's
Edit menu includes a Clear All command, for example, you might want to disable that command if there's nothing to clear. You've
undoubtedly seen such grayed-out commands in Windows-based applications, and you've probably also seen check marks next to
commands.
With Win32 programming, it's difficult to keep menu commands synchronized with the application's state. Every piece of code that
changes the internal state must contain statements to update the menu. The MFC library takes a different approach by calling a
special update command user interface handler function whenever a submenu is first displayed. The handler function's argument is
a CCmdUI object, which contains a pointer to the corresponding command. The handler function can then use this pointer to modify
the command's appearance. Update command user interface handlers apply only to commands on submenus, not to top-level menu
commands that are permanently displayed. For example, you can't use an update command user interface handler to disable a File
menu command.
The update command user interface coding requirements are similar to those for commands. You need the function itself, a special
message map entry, and of course the prototype. The associated IDin this case, IDM_ZOOMis the same constant used for the
command. Here is an example of the necessary additions to the view class code file:
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_UPDATE_COMMAND_UI(IDM_ZOOM, OnUpdateZoom)
END_MESSAGE_MAP()

void CMyView::OnUpdateZoom(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_bZoomed); // m_bZoomed is a class data member
}
SDI Application MDI Application
View View
Document Document
SDI main frame window MDI child frame window
Application MDI main frame window application
Page 3 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Here is the function prototype that you must add to the class header (before the DECLARE_MESSAGE_MAP macro):
afx_msg void OnUpdateZoom(CCmdUI* pCmdUI);
Needless to say, the code wizards available from Class View's Properties window automate the process of inserting update
command user interface handlers.
Commands That Originate in Dialog Boxes
Suppose you have a pop-up dialog box with buttons, and you want a particular button to send a command message. Command IDs
must be in the range 0x8000 to 0xDFFF, the same ID range that the resource editor uses for your menu commands. If you assign
an ID in this range to a dialog box button, the button will generate a routable command. The application framework first routes this
command to the main frame window because the frame window owns all pop-up dialog boxes. The command routing then proceeds
normally; if your view has a handler for the button's command, that's where it will be handled. To ensure that the ID is in the range
0x8000 to 0xDFFF, you must use Visual C++ .NET's Resource Symbols dialog box to enter the ID before you assign the ID to a
button.
The Application Framework's Built-in Menu Commands
You don't have to start each frame menu from scratchthe MFC library defines some useful menu commands for you, along with all
the command handler functions, as shown in Figure 12-3.

Figure 12-3: The standard SDI frame menus.
The menu commands and command message handlers that you get depend on the options you select in the MFC Application
Wizard. If you deselect Printing And Print Preview, for example, the Print and Print Preview commands won't appear. Because
printing is optional, the message map entries are not defined in the CView class but are generated in your derived view class. That's
why entries such as the following are defined in the CMyView class instead of in the CView class:
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
Enabling and Disabling Menu Commands
The application framework can disable a menu command if it does not find a command message handler in the current command
route. This feature saves you the trouble of having to write ON_UPDATE_COMMAND_UI handlers. You can disable the feature if
you set the CFrameWnd data member m_bAutoMenuEnable to FALSE.
Suppose you have two views for one document but only the first view class has a message handler for the IDM_ZOOM command.
The Zoom command on the frame menu will be enabled only when the first view is active. Or consider the Edit Cut, Copy, and Paste
commands, which are supplied with the application framework. These will be disabled if you haven't provided message handlers in
your derived view or document class.
MFC Text Editing Options
Windows itself supplies two text editing tools: the edit control and the rich edit common control. Both can be used as controls within
dialog boxes, but both can also be made to look like view windows. The MFC library supports this versatility with the CEditView and
CRichEditView classes.
The CEditView Class
This class is based on the Windows edit control. The MFC Application Wizard gives you the option of making CEditView the base
class of your view class. When the framework gives you an edit view object, it has all the functionality of both CView and CEdit.
There's no multiple inheritance here, just some magic that involves window subclassing. The CEditView class implements and maps
the Clipboard cut, copy, and paste functions, so they appear active on the Edit menu. The default character limit for CEditView is
1,048,575. You can change the character limit by sending the EM_LIMITTEXT message to the underlying edit control. However, the
limits are different depending on the operating system and the type of edit control (single or multi-line). See the MSDN Library for








Page 4 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
more information on these limits.
The CRichEditView Class
This class uses the rich edit control, so it supports mixed formats and large quantities of text. The CRichEditView class is designed
to be used with the CRichEditDoc and CRichEditCntrItem classes to implement a complete ActiveX container application.
The CRichEditCtrl Class
This class wraps the rich edit control, and you can use it to make a fairly decent text editor. That's exactly what we'll do in the
upcoming Ex12a example. We'll use an ordinary view class derived from CView, and we'll cover the view's client area with a big rich
edit control that resizes itself when the view size changes. The CRichEditCtrl class has dozens of useful member functions, and it
picks up other functions from its CWnd base class. The functions we'll use in this chapter are listed in Table 12-1.
The Ex12a Example
This example illustrates the routing of menu and keyboard accelerator commands to both documents and views. The application's
view class is derived from CView and contains a rich edit control. View-directed menu commands, originating from a new submenu
named Transfer, move data between the view object and the document object, and a Clear Document command erases the
document's contents. On the Transfer menu, the Store Data In Document command is grayed out if the view hasn't been modified
since the last time the data was transferred. The Clear Document command, located on the Edit menu, is grayed out when the
document is empty. Figure 12-4 shows the first version of the Ex12a program in use.

Figure 12-4: The Ex12a program in use.
Table 12-1: Commonly Used CRichEditCtrl Functions
Function Description
Create Creates the rich edit control window (which is called from the parent's WM_CREATE
handler).
SetWindowPos Sets the size and position of the edit window (sizes the control to cover the view's
client area).
GetWindowText Retrieves plain text from the control. (CRichEditCtrl includes other functions for
retrieving the text using rich text formatting codes.)
SetWindowText Stores plain text in the control.
GetModify Gets a flag that is TRUE if the text has been modified (when the user types in the
control or the program calls SetModify(TRUE)).
SetModify Sets the modify flag to TRUE or FALSE.
GetSel Gets a flag that indicates whether the user has selected text
SetDefaultCharFormat Sets the control's default format characteristics.
SetSelectionCharFormat Sets the format characteristics of the selected text.
Note If you use the dialog editor to add a rich edit control to a dialog resource, your application class InitInstance member
function must call the function AfxInitRichEdit.




Page 5 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
If we were to exploit the document-view architecture fully, we would tell the rich edit control to keep its text inside the document, but
that's rather difficult to do. Instead, we'll define a document CString data member named m_strText, the contents of which the user
can transfer to and from the control. The initial value of m_strText is a Hello message; choosing Clear Document from the Edit menu
sets it to empty. By running this example, you'll start to understand the separation of the document and the view.
The first part of the Ex12a example uses Visual C++ .NET's WYSIWYG menu editor and keyboard accelerator editor along with the
code wizards available from Class View's Properties window. You'll need to do very little C++ coding. Simply follow these steps:
1. Run the MFC Application Wizard to generate the Ex12a project. Accept all the default settings but two: Select Single
Document and deselect Printing And Print Preview.
2. Use the resource editor to edit the application's main menu. In Resource View, edit the IDR_MAINFRAME menu
resource to add a separator and a Clear Document command to the Edit menu, as shown here:

Now add a Transfer menu, and then define the underlying commands:

The MFC library has defined the following command IDs for your new menu commands in the Resource Symbols dialog box.
(Note that \t is a tab characterbut type \t; don't press the Tab key.)
After you add the commands, right-click on each of them and choose Properties from the shortcut menu. Type an appropriate
prompt string in each command's Properties window. These prompts will appear in the application's status bar window when
the command is highlighted.
3. Use the resource editor to add keyboard accelerators. Open the IDR_MAINFRAME accelerator table by double-clicking
on its icon in Resource View, and then click on the empty row entry at the bottom of the table to add the following items.
Tip The resource editor's menu resource editor is intuitive, but you might need some help the first time you insert a
command in the middle of a menu. Just right-click where you want to insert the command and choose Insert New
from the shortcut menu. You'll automatically see where to add the command. To insert a separator, choose Insert
Separator from the shortcut menu.
Menu Caption Command ID
Edit Clear &Document ID_EDIT_CLEARDOCUMENT
Transfer &Get Data From Document\tF2 ID_TRANSFER_GETDATAFROMDOCUMENT
Transfer &Store Data In Document\tF3 ID_TRANSFER_STOREDATAINDOCUMENT
Accelerator ID Key
Page 6 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Be sure to select None from the drop-down list in the Modifier box to turn off the Ctrl, Alt, and Shift modifiers.
4. Use Class View's Properties window for the CEx12aView class to add the view class command and update command
user interface message handlers. Select the CEx12aView class, and then add the following member functions:
5. Use Class View's Properties window for the CEx12aDoc class to add the document class command and update
command user interface message handlers. Select the CEx12aDoc class, and then add the following member functions:
6. Insert the following line in the Ex12aDoc.cpp file:
#include "Ex12aView.h"
7. Add a CString data member to the CEx12aDoc class. Edit the file Ex12aDoc.h or use Class View.
public:
CString m_strText;
8. Edit the document class member functions in Ex12aDoc.cpp. The OnNewDocument function was generated by
Visual Studio .NET. The framework calls this function after it first constructs the document and when the user chooses New
from the File menu. Your version sets some text in the string data member. Add the following boldface code:
BOOL CEx12aDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_strText = "Hello (from CEx12aDoc::OnNewDocument)";
return TRUE;
}
The Edit Clear Document message handler sets m_strText to empty, and the update command user interface handler grays
out the command if the string is already empty. Remember that the framework calls OnUpdateEditCleardocument when the
Edit menu is displayed. Add the following boldface code:
void CEx12aDoc::OnEditCleardocument()
{
m_strText.Empty();
//reflect changes to the views
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
CEx12aView* pView = (CEx12aView*) GetNextView(pos);
pView->m_rich.SetWindowText(m_strText);
}
}

void CEx12aDoc::OnUpdateEditCleardocument(CCmdUI *pCmdUI)
{
pCmdUI->Enable(!m_strText.IsEmpty());
}
9. Add a CRichEditCtrl data member to the CEx12aView class. Edit the file Ex12aView.h or use Class View.
public:
CRichEditCtrl m_rich;
10. Use Class View's Properties window to map the WM_CREATE and WM_SIZE messages in the CEx12aView class.The
OnCreate function creates the rich edit control. The control's size is 0 here because the view window doesn't have a size yet.
ID_TRANSFER_GETDATAFROMDOCUMENT VK_F2
ID_TRANSFER_STOREDATAINDOCUMENT VK_F3
Object ID Event Member Function
ID_TRANSFER_GETDATAFROMDOCUMENT COMMAND OnTransferGetdatafromdocument
ID_TRANSFER_STOREDATAINDOCUMENT COMMAND OnTransferStoredataindocument
ID_TRANSFER_STOREDATAINDOCUMENT UPDATE_COMMAND_UI OnUpdateTransferStoredataindocument
Object ID Event Member Function
ID_EDIT_CLEARDOCUMENT COMMAND OnEditCleardocument
ID_EDIT_CLEARDOCUMENT UPDATE_
COMMAND_UI
OnUpdateEditCleardocument
Page 7 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Add the following boldface code:
int CEx12aView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CRect rect(0, 0, 0, 0);
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_rich.Create(ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN |
WS_CHILD | WS_VISIBLE | WS_VSCROLL, rect, this, 1);
return 0;
}
Windows sends the WM_SIZE message to the view as soon as the view's initial size is determined and again each time the
user changes the frame size. This handler simply adjusts the rich edit control's size to fill the view client area. Add the
following boldface code:
void CEx12aView::OnSize(UINT nType, int cx, int cy)
{
CRect rect;
CView::OnSize(nType, cx, cy);
GetClientRect(rect);
m_rich.SetWindowPos(&wndTop, 0, 0, rect.right - rect.left,
rect.bottom - rect.top, SWP_SHOWWINDOW);
}
11. Edit the menu command handler functions in Ex12aView.cpp. Visual Studio .NET generated these skeleton functions
when you mapped the menu commands in step 4. The OnTransferGetdatafromdocument function gets the text from the
document data member and puts it in the rich edit control. The function then clears the control's modified flag. There is no
update command user interface handler. Add the following boldface code:
void CEx12aView::OnTransferGetdatafromdocument()
{
CEx12aDoc* pDoc = GetDocument();
m_rich.SetWindowText(pDoc->m_strText);
m_rich.SetModify(FALSE);
}
The OnTransferStoredataindocument function copies the text from the view's rich edit control to the document string and
resets the control's modified flag. The corresponding update command user interface handler grays out the command if the
control has not been changed since it was last copied to or from the document. Add the following boldface code:
void CEx12aView::OnTransferStoredataindocument()
{
CEx12aDoc* pDoc = GetDocument();
m_rich.GetWindowText(pDoc->m_strText);
m_rich.SetModify(FALSE);
}

void CEx12aView::OnUpdateTransferStoredataindocument(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_rich.GetModify());
}
12. Build and test the Ex12a application. When the application starts, the Clear Document command on the Edit menu should
be enabled. Choose Get Data From Document from the Transfer menu. Some text should appear. Edit the text, and then
choose Store Data In Document. That command should now appear gray. Try choosing the Clear Document command, and
then choose Get Data From Document again.
Property Sheets
You've already seen property sheets in Visual C++ .NET and in many other modern Windows-based programs. A property sheet is a
nice user interface element that allows you to cram lots of categorized information into a small dialog box. The user selects pages by
clicking on their tabs. Windows offers a tab control that you can insert in a dialog box, but it's more likely that you'll want to put dialog
boxes inside the tab control. The MFC library supports this, and the result is called a property sheet. The individual dialog boxes are
called property pages.
Building a Property Sheet
Follow these general steps to build a property sheet using the Visual C++ .NET tools:
1. Use the resource editor to create a series of dialog templates that are all approximately the same size. The captions are the
strings that you want to display on the tabs.
2. Use the MFC Class Wizard to generate a class for each template. Select CPropertyPage as the base class. Add data
members for the controls.




Page 8 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
3. Use the MFC Class Wizard to generate a single class derived from CPropertySheet.
4. To the sheet class, add one data member for each page class.
5. In the sheet class constructor, call the AddPage member function for each page, specifying the address of the embedded
page object.
6. In your application, construct an object of the derived CPropertySheet class, and then call DoModal. You must specify a
caption in the constructor call, but you can change the caption later by calling CPropertySheet::SetTitle.
7. Take care of programming for the Apply button.
Property Sheet Data Exchange
The framework puts three buttons on a property sheet (as in Figure 12-5 in the next section.) Be aware that the framework calls the
Dialog Data Exchange (DDX) code for a property page each time the user switches to and from that page. As you would expect, the
framework calls the DDX code for a page when the user clicks OK, thus updating that page's data members. From these
statements, you can conclude that all data members for all pages are updated when the user clicks OK to exit the sheet. All this with
no C++ programming on your part!

Figure 12-5: The property sheet from Ex12a.
What does the Apply button do? Nothing at all if you don't write some code. It won't even be enabled. To enable it for a given page,
you must set the page's modified flag by calling SetModified(TRUE) when you detect that the user has made changes on the page.
If you've enabled the Apply button, you can write a handler function for it in your page class by overriding the virtual
CPropertyPage::OnApply function. Don't try to understand property page message processing in the context of normal modal dialog
boxes; it's quite different. The framework gets a WM_NOTIFY message for all button clicks. It calls the DDX code for the page if the
OK or Apply button was clicked. It then calls the virtual OnApply functions for all the pages, and it resets the modified flag, which
disables the Apply button. Don't forget that the DDX code has already been called to update the data members in all pages, so you
need to override OnApply in only one page class.
What you put in your OnApply function is your business, but one option is to send a user-defined message to the object that created
the property sheet. The message handler can get the property page data members and process them. Meanwhile, the property
sheet stays on the screen.
The Ex12a Example Revisited
Now we'll add a property sheet to Ex12a that allows the user to change the rich edit control's font characteristics. Of course, we
could use the standard MFC CFontDialog function, but then you wouldn't learn how to create property sheets. Figure 12-5 shows the
property sheet that you'll build as you continue with Ex12a.
If you haven't built Ex12a, follow the instructions that begin on page 285 to build it. If you already have Ex12a working with the
Transfer menu commands, just continue on with these steps:
1. Use the resource editor to edit the application's main menu. In Resource View, edit the IDR_MAINFRAME menu
resource to add a Format menu that looks like this.
Note With a normal modal dialog box, if the user clicks the Cancel button, the changes will be discarded and the dialog class
data members will remain unchanged. With a property sheet, however, the data members will be updated if the user
changes one page and then moves to another, even if the user exits by clicking the Cancel button.




Page 9 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

The MFC library has defined the following command IDs for the new Format menu commands.
Add appropriate prompt strings for the two menu commands using the Properties window.
2. Use Class View's Properties window to add the view class command and update command user interface message
handlers. Select the CEx12aView class in Class View, and then add the following member functions.
3. Use the resource editor to add four property page dialog templates. Right-click on the RC file in Resource View and
choose Add Resource from the shortcut menu. In the Add Resource dialog box, select the small property page template. The
templates are shown here with their associated IDs:

Use the IDs listed below for the controls in the dialog boxes. Set the Auto Buddy and the Set Buddy Integer properties for the
Spin control, and set the Group property for the IDC_FONT and IDC_COLOR radio buttons. Set the minimum value of
IDC_FONTSIZE to 8 and its maximum value to 24.
Use the MFC Class Wizard to create the classes CPage1, CPage2, CPage3, and CPage4. In each case, select
CPropertyPage as the base class. Have the MFC Class Wizard generate the code for all these classes in the files
Property.h and Property.cpp by changing the filenames within the text boxes for the header file and the CPP file. When
Visual Studio .NET asks you whether you want to merge the files, click Yes. Then add the data members shown here:
Caption Command ID
&Default ID_FORMAT_DEFAULT
&Selection ID_FORMAT_SELECTION
Object ID Event Member Function
ID_FORMAT_DEFAULT COMMAND OnFormatDefault
ID_FORMAT_SELECTION COMMAND OnFormatSelection
ID_FORMAT_SELECTION UPDATE_
COMMAND_UI
OnUpdateFormatSelection
Dialog Box Control ID Type Data Member
Page 10 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Finally, use Class View's Properties window to override the OnInitDialog virtual function for CPage4.
4. Use the MFC Class Wizard to create a class derived from CPropertySheet. Select the name CFontSheet. Generate the
code in the files Property.h and Property.cpp, the same files you used for the property page classes. The following code
shows these files with the added code in boldface:
Property.h
#pragma once
// Property.h : header file
//

#define WM_USERAPPLY WM_USER + 5
extern CView* g_pView;

////////////////////////////////////////////////////////////////////
// CPage1 dialog

class CPage1 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage1)

public:
CPage1();
virtual ~CPage1();

// Dialog Data
enum { IDD = IDD_PAGE1 };
int m_nFont;


protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
virtual BOOL OnApply();
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

DECLARE_MESSAGE_MAP()

};

////////////////////////////////////////////////////////////////////
// CPage2 dialog

class CPage2 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage2)

public:
CPage2();
virtual ~CPage2();

// Dialog Data
enum { IDD = IDD_PAGE2 };
BOOL m_bBold;
BOOL m_bItalic;
IDD_PAGE1 First radio button IDC_FONT int m_nFont
IDD_PAGE2 Bold check
box
IDC_BOLD BOOL m_bBold
IDD_PAGE2 Italic check
box
IDC_ITALIC BOOL m_bItalic
IDD_PAGE2 Underline
check box
IDC_UNDERLINE BOOL m_bUnderline
IDD_PAGE3 First radio button IDC_COLOR int m_nColor
IDD_PAGE4 Edit control IDC_FONT
SIZE
int m_nFontSize
IDD_PAGE4 Spin control IDC_SPIN1

Page 11 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
BOOL m_bUnderline;

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

DECLARE_MESSAGE_MAP()

};

////////////////////////////////////////////////////////////////////
// CPage3 dialog

class CPage3 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage3)
public:
CPage3();
virtual ~CPage3();
// Dialog Data
enum { IDD = IDD_PAGE3 };
int m_nColor;

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};

////////////////////////////////////////////////////////////////////
// CPage4 dialog

class CPage4 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage4)
public:
CPage4();
virtual ~CPage4();
// Dialog Data
enum { IDD = IDD_PAGE4 };
int m_nFontSize;

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
// support
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
public:
virtual BOOL OnInitDialog();
};

////////////////////////////////////////////////////////////////////
// CFontSheet

class CFontSheet : public CPropertySheet
{
DECLARE_DYNAMIC(CFontSheet)
public:
CPage1 m_page1;
CPage2 m_page2;
CPage3 m_page3;
CPage4 m_page4;

public:
CFontSheet(UINT nIDCaption, CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
virtual ~CFontSheet();

protected:
DECLARE_MESSAGE_MAP()
Page 12 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
};
Property.cpp
// Property.cpp : implementation file

#include "stdafx.h"
#include "Ex12a.h"
#include "Property.h"

CView* g_pView;

////////////////////////////////////////////////////////////////////
// CPage1 dialog

IMPLEMENT_DYNCREATE(CPage1, CPropertyPage)

CPage1::CPage1() : CPropertyPage(CPage1::IDD)
{
m_nFont = -1;
}

CPage1::~CPage1()
{
}

BOOL CPage1::OnApply()
{
TRACE("CPage1::OnApply\n");
g_pView->SendMessage(WM_USERAPPLY);
return TRUE;
}

BOOL CPage1::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}

void CPage1::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage1::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
DDX_Radio(pDX, IDC_FONT, m_nFont);
}


BEGIN_MESSAGE_MAP(CPage1, CPropertyPage)
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////
// CPage1 message handlers

////////////////////////////////////////////////////////////////////
// CPage2 dialog

IMPLEMENT_DYNCREATE(CPage2, CPropertyPage)

CPage2::CPage2() : CPropertyPage(CPage2::IDD)
{
m_bBold = FALSE;
m_bItalic = FALSE;
m_bUnderline = FALSE;
}

CPage2::~CPage2()
{
}

Page 13 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
BOOL CPage2::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}

void CPage2::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage2::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
DDX_Check(pDX, IDC_BOLD, m_bBold);
DDX_Check(pDX, IDC_ITALIC, m_bItalic);
DDX_Check(pDX, IDC_UNDERLINE, m_bUnderline);
}

BEGIN_MESSAGE_MAP(CPage2, CPropertyPage)
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////
// CPage2 message handlers

////////////////////////////////////////////////////////////////////
// CPage3 dialog

IMPLEMENT_DYNCREATE(CPage3, CPropertyPage)

CPage3::CPage3() : CPropertyPage(CPage3::IDD)
{
m_nColor = -1;
}

CPage3::~CPage3()
{
}

BOOL CPage3::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}

void CPage3::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage3::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
DDX_Radio(pDX, IDC_COLOR, m_nColor);
}

BEGIN_MESSAGE_MAP(CPage3, CPropertyPage)
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////
// CPage3 message handlers

////////////////////////////////////////////////////////////////////
// CPage4 dialog

IMPLEMENT_DYNCREATE(CPage4, CPropertyPage)

CPage4::CPage4() : CPropertyPage(CPage4::IDD)
{
m_nFontSize = 0;
}

CPage4::~CPage4()
{
}

BOOL CPage4::OnCommand(WPARAM wParam, LPARAM lParam)
{
Page 14 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}

void CPage4::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage4::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
DDX_Text(pDX, IDC_FONTSIZE, m_nFontSize);
DDV_MinMaxInt(pDX, m_nFontSize, 8, 24);
}

BEGIN_MESSAGE_MAP(CPage4, CPropertyPage)
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////
// CPage4 message handlers

BOOL CPage4::OnInitDialog()
{
CPropertyPage::OnInitDialog();
((CSpinButtonCtrl*) GetDlgItem(IDC_SPIN1))->SetRange(8, 24);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}

////////////////////////////////////////////////////////////////////
// CFontSheet

IMPLEMENT_DYNAMIC(CFontSheet, CPropertySheet)
CFontSheet::CFontSheet(UINT nIDCaption, CWnd* pParentWnd,
UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
}

CFontSheet::CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd,
UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
AddPage(&m_page4);
}

CFontSheet::~CFontSheet()
{
}
BEGIN_MESSAGE_MAP(CFontSheet, CPropertySheet)
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////
// CFontSheet message handlers
5. Insert the following line in the Ex12aView.h file:
#include "Property.h"
6. Add two data members and two prototypes to the CEx12aView class:
private:
CFontSheet m_sh;
BOOL m_bDefault; // TRUE default format, FALSE selection
Now add the prototype for the private function Format:
void Format(CHARFORMAT &cf);
Insert the prototype for the protected function OnUserApply before the DECLARE_MESSAGE_MAP macro:
afx_msg LRESULT OnUserApply(WPARAM wParam, LPARAM lParam);
Page 15 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
7. Edit and add code in the file Ex12aView.cpp.Map the user-defined WM_USERAPPLY message, as shown here:
ON_MESSAGE(WM_USERAPPLY, OnUserApply)
Add the following lines to the OnCreate function, just before the return 0 statement:
CHARFORMAT cf;
Format(cf);
m_rich.SetDefaultCharFormat(cf);
Edit the view constructor to set default values for the property sheet data members, as follows:
CEx12aView::CEx12aView() : m_sh("")
{
m_sh.m_page1.m_nFont = 0;
m_sh.m_page2.m_bBold = FALSE;
m_sh.m_page2.m_bItalic = FALSE;
m_sh.m_page2.m_bUnderline = FALSE;
m_sh.m_page3.m_nColor = 0;
m_sh.m_page4.m_nFontSize = 12;
g_pView = this;
m_bDefault = TRUE;
}
Edit the format command handlers, as shown here:
void CEx12aView::OnFormatDefault()
{
m_sh.SetTitle("Default Format");
m_bDefault = TRUE;
m_sh.DoModal();
}

void CEx12aView::OnFormatSelection()
{
m_sh.SetTitle("Selection Format");
m_bDefault = FALSE;
m_sh.DoModal();
}

void CEx12aView::OnUpdateFormatSelection(CCmdUI* pCmdUI)
{
long nStart, nEnd;
m_rich.GetSel(nStart, nEnd);
pCmdUI->Enable(nStart != nEnd);
}
Add the following handler for the user-defined WM_USERAPPLY message:
LRESULT CEx12aView::OnUserApply(WPARAM wParam, LPARAM lParam)
{
TRACE("CEx12aView::OnUserApply -- wParam = %x\n", wParam);
CHARFORMAT cf;
Format(cf);
if (m_bDefault) {
m_rich.SetDefaultCharFormat(cf);
}
else {
m_rich.SetSelectionCharFormat(cf);
}
return 0;
}
Add the Format helper function, as shown below, to set a CHARFORMAT structure based on the values of the property sheet
data members:
void CEx12aView::Format(CHARFORMAT& cf)
{
cf.cbSize = sizeof(CHARFORMAT);
cf.dwMask = CFM_BOLD | CFM_COLOR | CFM_FACE |
CFM_ITALIC | CFM_SIZE | CFM_UNDERLINE;
cf.dwEffects = (m_sh.m_page2.m_bBold ? CFE_BOLD : 0) |
(m_sh.m_page2.m_bItalic ? CFE_ITALIC : 0) |
(m_sh.m_page2.m_bUnderline ? CFE_UNDERLINE : 0);
cf.yHeight = m_sh.m_page4.m_nFontSize * 20;
switch(m_sh.m_page3.m_nColor) {
Page 16 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
case -1:
case 0:
cf.crTextColor = RGB(0, 0, 0);
break;
case 1:
cf.crTextColor = RGB(255, 0, 0);
break;
case 2:
cf.crTextColor = RGB(0, 255, 0);
break;
}
switch(m_sh.m_page1.m_nFont) {
case -1:
case 0:
strncpy(cf.szFaceName, "Times New Roman" ,LF_FACESIZE);
break;
case 1:
strncpy(cf.szFaceName, "Arial" ,LF_FACESIZE);
break;
case 2:
strncpy(cf.szFaceName, "Courier New" ,LF_FACESIZE);
break;
}
cf.bCharSet = 0;
cf.bPitchAndFamily = 0;
}
8. Build and test the enhanced Ex12a application. Type some text, and then choose Default from the Format menu.
Observe the TRACE messages in the Debug window as you click on property sheet tabs and click the Apply button. Try
highlighting some text and then formatting the selection.
Apply Button Processing
You might be curious about the way the property sheet classes process the Apply button. In all the page classes, the overridden
OnCommand functions enable the Apply button whenever a control sends a message to the page. This works fine for pages 1
through 3 in Ex12a, but for page 4, OnCommand is called during the initial conversation between the Spin control and its buddy.
The OnApply virtual override in the CPage1 class sends a user-defined message to the view. The function finds the view in an
expedient wayby using a global variable set by the view class. A better approach would be to pass the view pointer to the sheet
constructor and then to the page constructor.
The view class calls the property sheet's DoModal function for both default formatting and selection formatting. It sets the
m_bDefault flag to indicate the mode. We don't need to check the return from DoModal because the user-defined message is sent
for both the OK button and the Apply button. If the user clicks Cancel, no message is sent.
The CMenu Class
Up to this point, the application framework and the menu editor have shielded you from the menu class, CMenu. A CMenu object
can represent each Windows menu, including the top-level menu commands and submenus. Most of the time, the menu's resource
is directly attached to a frame window when the window's Create or LoadFrame function is called, and a CMenu object is never
explicitly constructed. The CWnd member function GetMenu returns a temporary CMenu pointer. Once you have this pointer, you
can freely access and update the menu object.
Suppose you want to switch menus after the application starts. IDR_MAINFRAME always identifies the initial menu in the resource
script. If you want a second menu, you use the menu editor to create a menu resource with your own ID. Then, in your program, you
construct a CMenu object, use the CMenu::LoadMenu function to load the menu from the resource, and call the CWnd::SetMenu
function to attach the new menu to the frame window. You then call the Detach member function to separate the object's HMENU
handle so the menu is not destroyed when the CMenu object goes out of scope.
You can use a resource to define a menu, and then your program can modify the commands at run time. If necessary, however, you
can build the whole menu at run time, without benefit of a resource. In either case, you can use CMenu member functions such as
ModifyMenu, InsertMenu, and DeleteMenu. Each of these functions operates on an individual command identified by ID or by a
relative position index.
A menu object is actually composed of a nested structure of submenus. You can use the GetSubMenu member function to get a
CMenu pointer to a submenu contained in the main CMenu object. The CMenu::GetMenuString function returns the menu command
string corresponding to either a zero-based index or a command ID. If you use the command ID option, the menu is searched,
together with any submenus.
Creating Floating Shortcut Menus








Page 17 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Floating shortcut menus are one of the latest trends in user interface design. The user clicks the right mouse button and a floating
menu offers commands that relate to the current selection. It's easy to create these menus using the resource editor and the MFC
library CMenu::TrackPopupMenu function. Just follow these steps:
1. Use the menu editor to insert a new, empty menu in your project's resource file.
2. Type some characters in the left top-level command, and then add commands in the resulting shortcut menu.
3. Use Class View's Properties window to add a WM_CONTEXTMENU message handler in your view class or in some other
window class that receives mouse-click messages. Code the handler as shown here:
void CMyView::OnContextMenu(CWnd *pWnd, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MYFLOATINGMENU);
menu.GetSubMenu(0)
->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
point.x, point.y, this);
}
You can use Class View's Properties window to map the floating menu's command IDs in the same way you would map the
frame menu's command IDs.
Extended Command Processing
In addition to the ON_COMMAND message map macro, the MFC library provides an extended variation, ON_COMMAND_EX. The
extended command message map macro provides two features not supplied by the regular command messagea command ID
function parameter and the ability to reject a command at run time, sending it to the next object in the command route. If the
extended command handler returns TRUE, the command goes no further; if it returns FALSE, the application framework looks for
another command handler.
The command ID parameter is useful when you want one function to handle several related command messages. You might invent
some of your own uses for the rejection feature.
The code wizards available from Class View's Properties window can't help you with extended command handlers, so you have to
do the coding yourself, outside the AFX_MSG_MAP brackets. Assume that IDM_ZOOM_1 and IDM_ZOOM_2 are related command
IDs defined in Resource.h. Here's the class code you need to process both messages with one function, OnZoom:
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND_EX(IDM_ZOOM_1, OnZoom)
ON_COMMAND_EX(IDM_ZOOM_2, OnZoom)
END_MESSAGE_MAP()

BOOL CMyView::OnZoom(UINT nID)
{
if (nID == IDM_ZOOM_1) {
// code specific to first zoom command
}
else {
// code specific to second zoom command
}
// code common to both commands
return TRUE; // Command goes no further
}
Here's the function prototype:
afx_msg BOOL OnZoom(UINT nID);
Other MFC message map macros are helpful for processing ranges of commands, as you might see in dynamic menu applications.
These macros include ON_COMMAND_RANGE, ON_COMMAND_EX_RANGE, and ON_UPDATE_COMMAND_UI_RANGE.
If the values of IDM_ZOOM_1 and IDM_ZOOM_2 were consecutive, you could rewrite the CMyView message map as follows:
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND_EX_RANGE(IDM_ZOOM_1, IDM_ZOOM_2, OnZoom)
END_MESSAGE_MAP()
Now OnZoom is called for both menu commands, and the handler can determine the command from the integer parameter.
Chapter 13: Toolbars and Status Bars
Overview








Page 18 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
All of the book's Microsoft Visual C++ examples up to this point have included toolbars and status bars. The MFC Application Wizard
generates the code that initializes these application framework elements if you accept the wizard's default Standard Docking Toolbar
and Initial Status Bar user interface features. The default toolbar provides graphics equivalents for many of the standard application
framework menu commands, and the default status bar displays menu prompts together with the keyboard state indicators CAP,
NUM, and SCRL.
This chapter shows you how to customize the toolbar and the status bar for your application. You can add your own toolbar
graphical buttons and control their appearance. You can also disable the status bar's normal display of menu prompts and keyboard
indicators so that your application can take over the status bar for its own use.
Control Bars and the Application Framework
The toolbar is an object of class CToolBar, and the status bar is an object of class CStatusBar. Both of these classes are derived
from class CControlBar, which is itself derived from CWnd. The CControlBar class supports control bar windows that are positioned
inside frame windows. These control bar windows resize and reposition themselves as the parent frame moves and changes size.
The application framework takes care of the construction and destruction of the control bar objects and window creation. The MFC
Application Wizard generates control bar code for its derived frame class located in the files MainFrm.cpp and MainFrm.h.
In a typical Single Document Interface (SDI) application, a CToolBar object occupies the top portion of the CMainFrame client area
and a CStatusBar object occupies the bottom portion. The view occupies the remaining (middle) part of the frame.
Beginning with version 4.0 of the Microsoft Foundation Class (MFC) library, the toolbar has been built around the toolbar common
control that was first introduced with Microsoft Windows 95. Thus the toolbar is fully dockable. The programming interface is much
the same as it was in earlier versions of the MFC library, however. The button images are easy to work with because a special
resource type is supported by the resource editor.
Assuming that MFC Application Wizard has generated the control bar code for your application, the user can enable and disable the
toolbar or the status bar individually by choosing commands from the application's View menu. When a control bar is disabled, it
disappears and the view size is recalculated. Apart from the common behavior just described, toolbar and status bar objects operate
independently of each other and have rather different characteristics.
Version 6.0 of the MFC library, introduced a new MFC toolbar called the rebar. The rebar is based on the controls that come with the
common controls and provides a Microsoft Internet Explorerstyle "sliding" toolbar. I'll cover the rebar later in this chapter.
Toolbars
A toolbar consists of a number of horizontally (or vertically) arranged graphical buttons that might be clustered in groups. The
programming interface determines the grouping. The graphical images for the buttons are stored in a single bitmap that is attached
to the application's resource file. When a button is clicked, it sends a command message, as menus and keyboard accelerators do.
An update command user interface message handler is used to update the button's state, which in turn is used by the application
framework to modify the button's graphical image.
The Toolbar Bitmap
Each button on a toolbar appears to have its own bitmap, but actually a single bitmap serves the entire toolbar. The toolbar bitmap
has a tile, 15 pixels high and 16 pixels wide, for each button. The application framework supplies the button borders, and it modifies
these borders, together with the button's bitmap tile color, to reflect the current button state. Figure 13-1 shows the relationship
between the toolbar bitmap and the corresponding toolbar.

Figure 13-1: A toolbar bitmap and an actual toolbar.
The toolbar bitmap is stored in the file Toolbar.bmp in the application's \res subdirectory. The bitmap is identified in the resource
script (RC) file as IDR_MAINFRAME. You don't edit the toolbar bitmap directly; instead, you use Microsoft Visual Studio's special
toolbar editing facility.
Toolbar Button States
Each toolbar button can assume the states listed in Table 13-1. (There are additional states for later toolbar versions.)








Table 13-1: Toolbar States
State Description
0 Normal, unpressed state.
Page 19 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
A toolbar button can be a pushbutton, which is down only when currently clicked by the mouse, or it can be a check box button,
which can be toggled up and down with mouse clicks. All toolbar buttons in the standard application framework toolbar are
pushbuttons.
The Toolbar and Command Messages
When the user clicks a toolbar button with the mouse, a command message is generated. This message is routed like the menu
command messages you saw in Chapter 12. Most of the time, a toolbar button matches a menu command. In the standard
application framework toolbar, for example, the Disk button is equivalent to the File Save menu commandboth generate the
ID_FILE_SAVE command. The object receiving the command message doesn't need to know whether the message was produced
by a button click or by the menu command.
A toolbar button doesn't have to mirror a menu command. If you don't provide an equivalent menu command, however, you should
define a keyboard accelerator for the button so the user can activate the command with the keyboard or with a keyboard macro
product for Windows. You can use Class View and the Properties window to define commands and update command user interface
message handlers for toolbar buttons, whether or not they have corresponding menu commands.
A toolbar has an associated bitmap resource and, in the RC file, a companion toolbar resource that defines the menu commands
associated with the buttons. Both the bitmap and the toolbar resource have the same ID, typically IDR_MAINFRAME. The text of the
toolbar resource generated by the MFC Application Wizard is shown here:
IDR_MAINFRAME TOOLBAR 16, 15
BEGIN
BUTTON ID_FILE_NEW
BUTTON ID_FILE_OPEN
BUTTON ID_FILE_SAVE
SEPARATOR
BUTTON ID_EDIT_CUT
BUTTON ID_EDIT_COPY
BUTTON ID_EDIT_PASTE
SEPARATOR
BUTTON ID_FILE_PRINT
BUTTON ID_APP_ABOUT
END
The SEPARATOR constants serve to group the buttons by inserting corresponding spaces on the toolbar. If the number of toolbar
bitmap panes exceeds the number of resource elements (excluding separators), the extra buttons are not displayed.
When you edit the toolbar using the resource editor, you're editing both the bitmap resource and the toolbar resource. You select a
button image, and then you edit the properties, including the button's ID, in the Properties window.
Toolbar Update Command User Interface Message Handlers
You'll recall from Chapter 12 that update command user interface message handlers are used to disable or add check marks to
menu commands. These same message handlers apply to toolbar buttons. If your update command user interface message handler
calls the CCmdUI::Enable member function with a FALSE parameter, the corresponding button will be set to the disabled (grayed-
out) state and no longer respond to mouse clicks.
Next to a menu command, the CCmdUI::SetCheck member function displays a check mark. For the toolbar, the SetCheck function
implements check box buttons. If the update command user interface message handler calls SetCheck with a parameter value of 1,
the button will be toggled to the down (checked) state; if the parameter is 0, the button will be toggled up (unchecked).
The update command user interface message handlers for a shortcut menu are called only when the menu is painted. The toolbar is
displayed all the time, so when are its update command user interface message handlers called? They're called during the
application's idle processing so the buttons can be updated continuously. If the same handler covers a menu command and a
toolbar button, it is called both during idle processing and when the shortcut menu is displayed.
ToolTips
TBSTATE_CHECKED Checked (down) state.
TBSTATE_ENABLED Available for use. Button is grayed-out and unavailable if this state is not set.
TBSTATE_HIDDEN Not visible.
TBSTATE_INDETERMINATE Grayed-out.
TBSTATE_PRESSED Currently selected (pressed) using the mouse.
TBSTATE_WRAP Line break follows the button.
Note If the SetCheck parameter value is 2, the button will be set to the indeterminate state. This state looks like the disabled
state, but the button is still active and its color is a bit brighter.




Page 20 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
You've seen ToolTips in various Windows applications, including Visual Studio. When the user positions the mouse on a toolbar
button for a certain interval of time, text is displayed in a little ToolTip box next to the button. In Chapter 12, you learned that menu
commands can have associated prompt strings, which are string resource elements with matching IDs. To create a ToolTip, you
simply add the tip text to the end of the menu prompt, preceded by a newline (\n) character. The resource editor lets you edit the
prompt string while you're editing the toolbar images. Just select a toolbar image and edit the Prompt property in the Properties
window.
Locating the Main Frame Window
The toolbar and status bar objects you'll be working with are attached to the application's main frame window, not to the view
window. How does your view find its main frame window? In an SDI application, you can use the CWnd::GetParentFrame function.
Unfortunately, this function won't work in an MDI application because the view's parent frame is the MDI child frame, not the MDI
frame window.
If you want your view class to work in both SDI and MDI applications, you must find the main frame window through the application
object. The AfxGetApp global function returns a pointer to the application object. You can use that pointer to get the CWinApp data
member m_pMainWnd. In an MDI application, the MFC Application Wizard generates code that sets m_pMainWnd, but in an SDI
application, the framework sets m_pMainWnd during the view creation process. Once m_pMainWnd is set, you can use it in a view
class to get the frame's toolbar with statements such as this:
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
CToolBar* pToolBar = &pFrame->m_wndToolBar;
You can use similar logic to locate menu commands, status bar objects, and dialog objects.
The Ex13a Example: Using Toolbars
In this example, we'll add three special-purpose buttons that control drawing in the view window. We'll also construct a Draw menu
with three commands, as follows:
The menu and toolbar choices force the user to alternate between drawing circles and squares. After the user draws a circle, the
Circle command and toolbar button are disabled; after the user draws a square, the Square command and toolbar button are
disabled.
On the application's Draw menu, the Pattern command gets a check mark when pattern fill is active. On the toolbar, the
corresponding button is a check box button that is down when pattern fill is active and up when it is not active.
Figure 13-2 shows the application in action. The user has just drawn a square with pattern fill. Notice the states of the three drawing
buttons.

Figure 13-2: The Ex13a program in action.
The Ex13a example introduces the resource editor for toolbars. You'll need to do very little C++ coding. Simply follow these steps:




Note You'll need to cast m_pMainWnd from CFrameWnd* to CMainFrame* because m_wndToolBar is a member of that derived
class. You'll also have to make m_wndToolBar public or make your class a friend of CMainFrame.
Note In an SDI application, the value of m_pMainWnd is not set when the view's OnCreate message handler is called. If you
need to access the main frame window in your OnCreate function, you must use the GetParentFrame function.




Command Function
Circle Draws a circle in the view window
Square Draws a square in the view window
Pattern Toggles a diagonal line fill pattern for new squares and circles
Page 21 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
1. Run the MFC Application Wizard to generate a project named Ex13a. Choose New Project from Visual Studio's File
menu. In the New Project dialog box, select the MFC Application template, type the name Ex13a, and click OK. In the MFC
Application Wizard, accept all the defaults but two: On the Application Type page, select Single Document, and on the
Advanced Features page, deselect Printing And Print Preview.
2. Use the resource editor to edit the application's main menu. In Resource View, double-click on IDR_MAINFRAME under
Menu. Edit the IDR_MAINFRAME menu resource to create a new Draw menu that looks like the following. To reposition a
menu, you can just drag the menu.

In the Properties window, verify that the following properties are set for your new Draw menu commands:
3. Use the resource editor to update the application's toolbar. Edit the IDR_MAINFRAME toolbar resource to create a
group of three new buttons that looks like this:

The toolbar editor is fairly intuitive. You add new buttons by editing the blank button at the far right of the toolbar. Use the
Ellipsis, Rectangle, and Line tools on the Image Editor toolbar to draw on a button. You can move buttons around by dragging
them with the mouse. To add a separator between buttons, drag the button where the separator should appear slightly to the
right or left and the buttons will be nudged over. The Delete key erases a button's pixels. If you want to eliminate a button
entirely, just drag it off the toolbar.
In the Properties window, set the ID property for the new buttons to ID_DRAW_CIRCLE, ID_DRAW_SQUARE, and
ID_DRAW_PATTERN.
4. Add the CEx13aView class message handlers. Select the CEx13aView class in Class View, click the Events button in the
Properties window, and add message handlers for the following command and update command user interface messages:
Caption ID Prompt
Circle ID_DRAW_CIRCLE Draw a circle\nCircle
Square ID_DRAW_SQUARE Draw a square\nSquare
Pattern ID_DRAW_PATTERN Change the pattern\nPattern
Object ID Message Member Function
ID_DRAW_CIRCLE COMMAND OnDrawCircle
ID_DRAW_CIRCLE UPDATE_COMMAND_UI OnUpdateDrawCircle
ID_DRAW_PATTERN COMMAND OnDrawPattern
ID_DRAW_PATTERN UPDATE_COMMAND_UI OnUpdateDrawPattern
ID_DRAW_SQUARE COMMAND OnDrawSquare
ID_DRAW_SQUARE UPDATE_COMMAND_UI OnUpdateDrawSquare
Page 22 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
5. Add three data members to the CEx13aView class. Add the following code to Ex13aView.h:
protected:
CRect m_rect;
BOOL m_bCircle;
BOOL m_bPattern;
6. Edit the Ex13aView.cpp file. The CEx13aView constructor simply initializes the class data members. Add the following
boldface code:
CEx13aView::CEx13aView() : m_rect(0, 0, 100, 100)
{
m_bCircle = TRUE;
m_bPattern = FALSE;
}
The OnDraw function draws an ellipse or a rectangle, depending on the value of the m_bCircle flag. The brush is plain white
or a diagonal pattern, depending on the value of m_bPattern.
void CEx13aView::OnDraw(CDC* pDC)
{
Cex13aDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CBrush brush(HS_BDIAGONAL, 0L); // brush with diagonal pattern

if (m_bPattern) {
pDC->SelectObject(&brush);
}
else {
pDC->SelectStockObject(WHITE_BRUSH);
}
if (m_bCircle) {
pDC->Ellipse(m_rect);
}
else {
pDC->Rectangle(m_rect);
}
pDC->SelectStockObject(WHITE_BRUSH); // Deselects brush
// if selected
}
The OnDrawCircle function handles the ID_DRAW_CIRCLE command message, and the OnDrawSquare function handles
the ID_DRAW_SQUARE command message. These two functions move the drawing rectangle down and to the right, and
then they invalidate the rectangle, causing the OnDraw function to redraw it. The effect of this invalidation strategy is a
diagonal cascading of alternating squares and circles. Also, the display is not buffered, so when the window is hidden or
minimized, previously drawn items are not redisplayed.
void CEx13aView::OnDrawCircle()
{
m_bCircle = TRUE;
m_rect += CPoint(25, 25);
InvalidateRect(m_rect);
}

void CEx13aView::OnDrawSquare()
{
m_bCircle = FALSE;
m_rect += CPoint(25, 25);
InvalidateRect(m_rect);
}
The following two update command user interface functions alternately enable and disable the Circle and Square buttons and
corresponding menu commands. Only one item can be enabled at a time.
void CEx13aView::OnUpdateDrawCircle(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_bCircle);
}

void CEx13aView::OnUpdateDrawSquare(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bCircle);
}
The OnDrawPattern function toggles the state of the m_bPattern flag.
Page 23 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
void CEx13aView::OnDrawPattern()
{
m_bPattern ^= 1;
}
The OnUpdateDrawPattern function updates the Pattern button and menu command according to the state of the m_bPattern
flag. The toolbar button appears to move in and out, and the command check mark appears and disappears.
void CEx13aView::OnUpdateDrawPattern(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_bPattern);
}
7. Build and test the Ex13a application. Notice the behavior of the toolbar buttons. Try the corresponding menu commands,
and notice that they too are enabled, disabled, and checked as the application's state changes. Observe the ToolTip and the
prompt in the status bar when you stop the mouse pointer on one of the new toolbar buttons.
Status Bars
The status bar window neither accepts user input nor generates command messages. Its job is simply to display text in panes under
program control. The status bar supports two types of text panesmessage line panes and status indicator panes. To use the
status bar for application-specific data, you must first disable the standard status bar that displays the menu prompt and keyboard
status.
The Status Bar Definition
The static indicators array that the MFC Application Wizard generates in the MainFrm.cpp file defines the panes for the
application's status bar. The constant ID_SEPARATOR identifies a message line pane; the other constants are string resource IDs
that identify indicator panes. Figure 13-3 shows the indicators array and its relationship to the standard framework status bar.

Figure 13-3: The status bar and the indicators array.
The CStatusBar::SetIndicators member function, called in the application's derived frame class, configures the status bar according
to the contents of the indicators array.
The Message Line
A message line pane displays a string that the program supplies dynamically. To set the value of the message line, you must first
get access to the status bar object and then you must call the CStatusBar::SetPaneText member function with a zero-based index
parameter. Pane 0 is the leftmost pane, 1 is the next pane to the right, and so forth.
The following code fragment is part of a view class member function. Note that you must navigate up to the application object and
then back down to the main frame window.
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
CStatusBar* pStatus = &pFrame->m_wndStatusBar;
pStatus->SetPaneText(0, "message line for first pane");
Normally, the length of a message line pane is exactly one-fourth the width of the display. If, however, the message line is the first
(index 0) pane, it is a stretchy pane without a beveled border. Its minimum length is one-fourth the display width, and it expands if
room is available in the status bar.
The Status Indicator
A status indicator pane is linked to a single resource-supplied string that is displayed or hidden by logic in an associated update
command user interface message handler function. An indicator is identified by a string resource ID, and that same ID is used to
route update command user interface messages. The Caps Lock indicator is handled in the frame class by a message map entry
and a handler function equivalent to those shown below. The Enable function turns on the indicator if the Caps Lock mode is set.
ON_UPDATE_COMMAND_UI(ID_INDICATOR_CAPS, OnUpdateKeyCapsLock)

void CMainFrame::OnUpdateKeyCapsLock(CCmdUI* pCmdUI)
{




Page 24 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
pCmdUI->Enable(::GetKeyState(VK_CAPITAL) & 1);
}
The status bar update command user interface functions are called during idle processing so that the status bar is updated
whenever your application receives messages.
The length of a status indicator pane is the exact length of the corresponding resource string.
Taking Control of the Status Bar
In the standard application framework implementation, the status bar has the child window ID AFX_IDW_STATUS_BAR. The
application framework looks for this ID when it wants to display a menu prompt. The update command user interface handlers for
the keyboard state indicators, embedded in the frame window base class, are linked to the following string IDs:
ID_INDICATOR_CAPS, ID_INDICATOR_NUM, and ID_INDICATOR_SCRL. To take control of the status bar, you must use a
different child window ID and different indicator ID constants.
The status bar window ID is assigned in the CStatusBar::Create function called by the derived frame class OnCreate member
function. That function is contained in the MainFrm.cpp file that the MFC Application Wizard generates. The window ID is the third
Create parameter, and it defaults to AFX_IDW_STATUS_BAR.
To assign your own ID, you must replace this call
m_wndStatusBar.Create(this);
with this call
m_wndStatusBar.Create(this, WS_CHILD | WS_VISIBLE | CBRS_BOTTOM,
ID_MY_STATUS_BAR);
You must also, of course, define the ID_MY_STATUS_BAR constant in the resource.h file (using Visual C++'s resource symbol
editor).
We left out one thing. The standard application framework's View menu allows the user to turn the status bar on and off. That logic is
pegged to the AFX_IDW_STATUS_BAR window ID, so you have to change the menu logic, too. In your derived frame class, you
must write message map entries and handlers for the ID_VIEW_STATUS_BAR command and update command user interface
messages. ID_VIEW_STATUS_BAR is the ID of the Status Bar menu command. The derived class handlers override the standard
handlers in the CFrameWnd base class. See the upcoming Ex13b example for code details.
The Ex13b Example: Using Status Bars
The Ex13b example replaces the standard application framework status bar with a new status bar that has the following text panes:
The resulting status bar is shown in Figure 13-4. Notice that the leftmost pane stretches past its normal screen length as the
displayed frame window expands.

Figure 13-4: The status bar of the Ex13b example.
Follow these steps to produce the Ex13b example:
1. Run the MFC Application Wizard to generate a project named Ex13b. Choose New Project from Visual Studio's File
Note The only reason to change the status bar's child window ID is to prevent the framework from writing menu prompts in pane
0. If you like the menu prompts, you can disregard the following instructions.




Pane Index String ID Type Description
0 ID_SEPARATOR (0) Message line x cursor coordinate
1 ID_SEPARATOR (0) Message line y cursor coordinate
2 ID_INDICATOR_LEFT Status indicator Left mouse button status
3 ID_INDICATOR_RIGHT Status indicator Right mouse button status
Page 25 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
menu. In the New Project dialog box, select the MFC Application template, type the name Ex13b, and click OK. In the MFC
Application Wizard, accept all the defaults but two: On the Application Type page, select Single Document, and on the
Advanced Features page, deselect Printing And Print Preview.
2. Use the string editor to edit the application's string table resource. The application has a single string table resource
with artificial "segment" divisions left over from the 16-bit era. In Resource View, double-click on the String Table icon in the
String Table folder to bring up the string editor. Then select the empty entry at the end of the list and add the following two
strings:
When you're finished, the string table should appear as follows:

3. Edit the application's symbols. Choose Resource Symbols from the Edit menu. Click the New button and add the new
status bar identifier, ID_MY_STATUS_BAR, and accept the default value as shown here:

4. Add View menu command handlers in the class CmainFrame. Select the CMainFrame class in Class View, click the
Events button in the Properties window, and add the following command message handlers:
5. Add the following function prototypes to MainFrm.h. You must add these CMainFrame message handler prototypes
manually because Visual Studio doesn't recognize the associated command message IDs.
afx_msg void OnUpdateLeft(CCmdUI* pCmdUI);
afx_msg void OnUpdateRight(CCmdUI* pCmdUI);
While MainFrm.h is open, make m_wndStatusBar public rather than protected.
6. Edit the MainFrm.cpp file. Replace the original indicators array with the following boldface code:
static UINT indicators[] =
String ID String Caption
ID_INDICATOR_LEFT LEFT
ID_INDICATOR_RIGHT RIGHT
Object ID Message Member Function
ID_VIEW_STATUS_BAR COMMAND OnViewStatusBar
ID_VIEW_STATUS_BAR UPDATE_COMMAND_UI OnUpdateViewStatusBar
Page 26 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
{
ID_SEPARATOR, // first message line pane
ID_SEPARATOR, // second message line pane
ID_INDICATOR_LEFT,
ID_INDICATOR_RIGHT,
};
Next, edit the OnCreate member function. Replace the following statement
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
with the statement shown here:
if (!m_wndStatusBar.Create(this,
WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, ID_MY_STATUS_BAR) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
The modified call to Create uses our own status bar ID, ID_MY_STATUS_BAR, instead of AFX_IDW_STATUS_BAR (the
application framework's status bar object).
Now add the following message map entries for the class CMainFrame. Visual Studio can't add these for you because it
doesn't recognize the string table IDs as object IDs.
ON_UPDATE_COMMAND_UI(ID_INDICATOR_LEFT, OnUpdateLeft)
ON_UPDATE_COMMAND_UI(ID_INDICATOR_RIGHT, OnUpdateRight)
Add the following CMainFrame member functions that update the two status indicators:
void CMainFrame::OnUpdateLeft(CCmdUI* pCmdUI)
{
pCmdUI->Enable(::GetKeyState(VK_LBUTTON) < 0);
}

void CMainFrame::OnUpdateRight(CCmdUI* pCmdUI)
{
pCmdUI->Enable(::GetKeyState(VK_RBUTTON) < 0);
}
Note that the left and right mouse buttons have virtual key codes like keys on the keyboard have. You don't have to depend
on mouse-click messages to determine the button status.
Finally, edit the following View menu functions in MainFrm.cpp:
void CMainFrame::OnViewStatusBar()
{
m_wndStatusBar.ShowWindow((m_wndStatusBar.GetStyle() &
WS_VISIBLE) == 0);
RecalcLayout();
}
void CMainFrame::OnUpdateViewStatusBar(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck((m_wndStatusBar.GetStyle() & WS_VISIBLE) != 0);
}
These functions ensure that the View menu's Status Bar command is properly linked to the new status bar.
7. Edit the OnDraw function in Ex13bView.cpp. The OnDraw function displays a message in the view window. Add the
following boldface code:
void CEx13bView::OnDraw(CDC* pDC)
{
CEx13bDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

pDC->TextOut(0, 0,
"Watch the status bar while you move and click the mouse.");
}
Page 27 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
8. Add a WM_MOUSEMOVE handler in the CEx13bView class. Select the CEx13bView class in Class View, click the
Messages button in the Properties window, and add the OnMouseMove function. Edit the function as shown below. This
function gets a pointer to the status bar object and then calls the SetPaneText function to update the first and second
message line panes.
void CEx13bView::OnMouseMove(UINT nFlags, CPoint point)
{
CString str;
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
CStatusBar* pStatus = &pFrame->m_wndStatusBar;
if (pStatus) {
str.Format("x = %d", point.x);
pStatus->SetPaneText(0, str);
str.Format("y = %d", point.y);
pStatus->SetPaneText(1, str);
}
}
Finally, add the statement
#include "MainFrm.h"
near the top of the file Ex13bView.cpp.
9. Build and test the Ex13b application. Move the mouse and observe that the first two status bar panes accurately reflect
the mouse cursor's position. Try the left and right mouse buttons. Can you toggle the status bar on and off from the View
menu?
Rebars
As you learned in Chapter 8, Visual C++ contains features originally found in Internet Explorer: the common controls. One of these
is a new kind of toolbar called a rebar. You're probably familiar with the rebar if you've used Internet Explorer. The rebar differs from
the default MFC toolbar in that it provides grippers and allows the user to "slide" its horizontal and vertical positions. In contrast, you
change the MFC toolbar's position using drag-and-drop docking. Rebars also allow the developer to provide many more internal
control types such as drop-down menusthan are available in CToolBar.
Anatomy of a Rebar
Figure 13-5 shows the various terms used on a rebar. Each internal toolbar in a rebar is called a band. The raised edge where the
user slides the band is called a gripper. Each band can also have a label.

Figure 13-5: Rebar terminology.
MFC provides two classes that facilitate working with rebars:
CReBar A high-level abstraction class that provides members for adding CToolBar and CDialogBar classes to rebars as bands.
CReBar also handles communication (such as message notifications) between the underlying control and the MFC framework.
CReBarCtrl A low-level wrapper class that wraps the ReBar control. This class provides numerous members for creating and
manipulating rebars but does not provide the niceties that are found in CReBar.
Most MFC applications use CReBar and call the member function GetReBarCtrl, which returns a CReBarCtrl pointer to gain access
to the lower-level control if needed.
The Ex13c Example: Using Rebars
Note If you want the first (index 0) status bar pane to have a beveled border like the other panes and you want the status
bar to grow and resize to fit the contents, include the following two lines in the CMainFrame::OnCreate function,
following the call to the status bar Create function.
m_wndStatusBar.SetPaneInfo(0, 0, 0, 50);
m_wndStatusBar.SetPaneInfo(1, 0, SBPS_STRETCH, 50);
These statements change the width of the first two panes (from their default of one-fourth the display size) and
make the second pane (index 1) the stretchy one.








Page 28 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Let's get familiar with the rebar by jumping into an example. This example creates an SDI application that has a rebar with two
bands: a familiar toolbar band and a dialog bar band. Figure 13-6 shows the example in action.

Figure 13-6: Ex13c rebar example.
Here are the steps required to create the Ex13c example:
1. Run the MFC Application Wizard to generate a project named Ex13c. Choose New Project from Visual Studio's File
menu. In the New Project dialog box, select the MFC Application template, type the name Ex13c, and click OK. In the MFC
Application Wizard, accept all the defaults but two: On the Application Type page, select Single Document, and on the User
Interface Features page under Toolbars, select Standard Docking and Browser Style.
2. Compile and run the application. When you run the application, you'll see that the MFC Application Wizard has
automatically created a rebar with two bands. One band contains a conventional toolbar and the other contains the text
TODO: layout dialog bar in the band.

Open the MainFrm.h header file and see the code below, which declares the CReBar data member m_ndReBar.
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
CReBar m_wndReBar;
CDialogBar m_wndDlgBar;
In the MainFrm.cpp file, you can see the code that adds the toolbar and the dialog bar to the CReBar object:
if (!m_wndReBar.Create(this) ||
!m_wndReBar.AddBar(&m_wndToolBar) ||
!m_wndReBar.AddBar(&m_wndDlgBar))
{
TRACE0("Failed to create rebar\n");
return -1; // fail to create
}
3. Lay out the dialog bar. In Resource View, under the Dialog node, you'll find a dialog resource for the dialog bar with the ID
IDR_MAINFRAME. Open IDR_MAINFRAME, and you'll see the dialog bar with the text TODO: layout dialog bar. Let's put
some real controls onto the dialog bar. First, delete the static control with the TODO text in it. Then place a combo box on the
dialog bar and use the Properties window to enter the following default data items in the Data property:
One;Two;Buckle;My;Shoe!;. Now place a button on the dialog bar and change the button's Caption property to Increment.
Place a progress control on the dialog bar and set the Smooth property to True. Finally, place another button on the dialog
bar and change the Caption property to Decrement. When you're done laying out out the dialog bar, it should look similar to
this.
Page 29 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

4. Edit the MainFrm.h file. Visual Studio doesn't understand how to connect the controls on the dialog bar with handlers in
the CMainFrame class. We need to add them by hand. Open up MainFrm.h and add the following prototypes to
CMainFrame.
afx_msg void OnButton1();
afx_msg void OnButton2();
5. Edit the MainFrm.cpp file. Open MainFrm.cpp and add the following message maps for Button1 and Button2:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
ON_BN_CLICKED(IDC_BUTTON2, OnButton2)
END_MESSAGE_MAP()
Add the following OnButton1 and OnButton2 methods to CMainFrame.cpp:
void CMainFrame::OnButton1()
{
CProgressCtrl * pProgress =
(CProgressCtrl*)m_wndDlgBar.GetDlgItem(IDC_PROGRESS1);
pProgress->StepIt();
}

void CMainFrame::OnButton2()
{
CProgressCtrl * pProgress =
(CProgressCtrl*)m_wndDlgBar.GetDlgItem(IDC_PROGRESS1);
int nCurrentPos = pProgress->GetPos();
pProgress->SetPos(nCurrentPos-10);
}
The OnButton1 handler first gets a pointer to the progress control and then calls StepIt to increment the progress control.
OnButton2 decrements the current progress position by 10.
6. Compile and test the Ex13c application. Now you can compile and run Ex13c to see your custom rebar in action. The
Increment button increases the progress bar and the Decrement button decreases it.
Chapter 14: A Reusable Frame Window Base Class
Overview
C++ offers programmers the ability to produce "software building blocks" that can be taken off the shelf and fitted easily into an
application. The Microsoft Foundation Class (MFC) library classes are a good example of this kind of reusable software. This
chapter shows you how to build your own reusable base class by taking advantage of what the MFC library already provides.
In the process of building the reusable class, you'll learn a few more things about Microsoft Windows and the MFC library. In
particular, you'll see how the application framework allows access to the Windows Registry, you'll learn more about the mechanics of
the CFrameWnd class, and you'll get more exposure to static class variables and the CString class.
Why Reusable Base Classes Are Difficult to Write
In a normal application, you write code for software components that solve particular problems. It's usually a simple matter of
meeting the project specification. With reusable base classes, however, you must anticipate future programming needs, both your
own and those of others. You have to write a class that is general and complete yet efficient and easy to use.
This chapter's example shows the difficulty of building reusable software. The class was originally intended to be a frame class that
would "remember" its window size and position. In addition to remembering their window sizes, many existing Windows-based
programs also remember whether they've been minimized to the taskbar or whether they've been maximized to full screen. Then








Page 30 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
there is the oddball case of a window that is both minimized and maximized. In addition, the frame class needs to manage the
toolbar and the status bar, and the class has to work in a dynamic-link library (DLL). In short, it's surprisingly difficult to write a frame
class that would do everything that a programmer might expect.
In a production programming environment, reusable base classes might fall out of the normal software development cycle. A class
written for one project might be extracted and further generalized for another project. There's always the temptation, though, to cut
and paste existing classes without asking, "What can I factor out into a base class?" If you're in the software business for the long
term, it's beneficial to start building your library of truly reusable components.
The CPersistentFrame Class
In this chapter, we'll use a class named CPersistentFrame that's derived from the CFrameWnd class. This CPersistentFrame class
supports a persistent Single Document Interface (SDI) frame window that remembers the following characteristics:
Window size
Window position
Maximized status
Minimized status
Toolbar and status bar enablement and position
When you terminate an application that's built with the CPersistentFrame class, the above information is saved on disk in the
Windows Registry. When the application starts again, it reads the Registry and restores the frame to its state at the previous exit.
You can use the persistent view class in any SDI application, including the examples in this book. All you have to do is substitute
CPersistentFrame for CFrameWnd in your application's derived frame class files.
The CFrameWnd::ActivateFrame Member Function
Why choose CFrameWnd as the base class for a persistent window? Why not have a persistent view class instead? In an MFC SDI
application, the main frame window is always the parent of the view window. This frame window is created first, and then the control
bars and the view are created as child windows. The application framework ensures that the child windows shrink and expand
appropriately as the user changes the size of the frame window. It wouldn't make sense to change the view size after the frame was
created.
The key to controlling the frame's size is the CFrameWnd::ActivateFrame member function. The application framework calls this
virtual function (which is declared in CFrameWnd) during the SDI main frame window creation process (and in response to the File
New and File Open commands). The framework's job is to call the CWnd::ShowWindow function with the parameter nCmdShow.
ShowWindow makes the frame window visible along with its menu, view window, and control bars. The nCmdShow parameter
determines whether the window is maximized or minimized.
If you override ActivateFrame in your derived frame class, you can change the value of nCmdShow before passing it to the
CFrameWnd::ActivateFrame function. You can also call the CWnd::SetWindowPlacement function, which sets the size and position
of the frame window, and you can set the visible status of the control bars. Because all changes are made before the frame window
becomes visible, no annoying flash occurs on the screen.
You must be careful not to reset the frame window's position and size after every File New or File Open command. A first-time flag
data member ensures that your CPersistentFrame::ActivateFrame function operates only when the application starts.
The PreCreateWindow Member Function
PreCreateWindow, which is declared at the CWnd level, is another virtual function that you can override to change the
characteristics of your window before it is displayed. The framework calls this function before it calls ActivateFrame. The MFC
Application Wizard always generates an overridden PreCreateWindow function in your project's view and frame window classes.
This function has a CREATESTRUCT structure as a parameter, and two of the data members in this structure are style and
dwExStyle. You can change these data members before passing the structure on to the base class PreCreateWindow function. The
style flag determines whether the window has a border, scroll bars, a minimize box, and so on. The dwExStyle flag controls other
characteristics, such as always-on-top status. See the Window Styles and Extended Window Styles sections of the MFC Library
Reference for details.
The CREATESTRUCT member lpszClass is also useful for changing the window's background brush, cursor, or icon. It makes no
sense to change the brush or cursor in a frame window because the view window covers the client area. If you want an ugly red
view window with a special cursor, for example, you can override your view's PreCreateWindow function like this:
BOOL CMyView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CView::PreCreateWindow(cs)) {
return FALSE;
}












Page 31 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
cs.lpszClass =
AfxRegisterWndClass(CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW,
AfxGetApp()->LoadCursor(IDC_MYCURSOR),
::CreateSolidBrush(RGB(255, 0, 0)));
if (cs.lpszClass != NULL) {
return TRUE;
}
else {
return FALSE;
}
}
If you override the PreCreateWindow function in your persistent frame class, windows of all derived classes will share the
characteristics you programmed in the base class. Of course, derived classes can have their own overridden PreCreateWindow
functions, but then you'll have to be careful about the interaction between the base class and derived class functions.
The Windows Registry
If you've used Win16-based applications, you've probably seen INI files. You can still use INI files in Win32-based applications, but
Microsoft recommends that you use the Windows Registry instead. The Registry is a set of system files, managed by Windows, in
which Windows and individual applications can store and access permanent information. The Registry is organized as a kind of
hierarchical database in which string and integer data is accessed by a multi-part key.
For example, a text processing application, TEXTPROC, might need to store the most recent font and point size in the Registry.
Suppose that the program name forms the root of the key (a simplification) and that the application maintains two hierarchy levels
below the name. The structure looks something like this:
TEXTPROC
Text formatting
Font = Times New Roman
Points = 10
Unicode
European languages use characters that can be encoded in 8 bitseven characters with diacritics. Most Asian languages
require 16 bits for their characters. Many programs use the double-byte character set (DBCS) standard; some characters use 8
bits and others 16 bits, depending on the value of the first 8 bits. DBCS is being replaced by Unicode, in which all characters are
16-bit "wide" characters. No specific Unicode character ranges are set aside for individual languages: If a character is used in
both the Chinese and the Japanese languages, for example, that character appears only once in the Unicode character set.
When you look at MFC source code and the code that the MFC Application Wizard generates, you'll see the types TCHAR,
LPTSTR, and LPCTSTR and you'll see literal strings such as _T("string"). You're looking at Unicode macros. If you build your
project without defining _UNICODE, the compiler will generate code for ordinary 8-bit ANSI characters (CHAR) and pointers to
8-bit character arrays (LPSTR, LPCSTR). If you do define _UNICODE, the compiler will generate code for 16-bit Unicode
characters (WCHAR), pointers (LPWSTR, LPCWSTR), and literals (L"wide string").
The _UNICODE preprocessor symbol also determines which Windows functions your program will call. Many Win32 functions
have two versions. When your program calls CreateWindowEx, for example, the compiler will generate code to call either
CreateWindowExA (with ANSI parameters) or CreateWindowExW (with Unicode parameters). In Windows NT, Windows 2000,
and Windows XP, which use Unicode internally, CreateWindowExW passes all parameters straight through, but
CreateWindowExA converts ANSI string and character parameters to Unicode. In Windows 95, Windows 98, and Windows Me,
which use ANSI internally, CreateWindowExW is a stub that returns an error and CreateWindowExA passes the parameters
straight through.
If you want to create a Unicode application, you should target it for Windows NT/2000/XP and use the macros throughout. You
can write Unicode applications for Windows 95/98/Me, but you'll do extra work to call the "A" versions of the Win32 functions. As
shown in Chapters 24 through 30, COM calls that support Automation always use wide characters. Although Win32 functions
are available for converting between ANSI and Unicode, if you're using the CString class you can rely on a wide character
constructor and the AllocSysString member function to do the conversions.
For simplicity, this book's example programs use ANSI only. The code generated by the MFC Application Wizard uses Unicode
macros, but the code I wrote uses 8-bit literal strings and the char, char*, and const char* types.
The MFC library provides four CWinApp member functions, which are holdovers from the days of INI files, for accessing the
Registry. The MFC Application Wizard generates a call to CWinApp::SetRegistryKey in your application's InitInstance function, as
shown here:
SetRegistryKey(_T("Local AppWizard-Generated Applications"));




Page 32 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
If you remove this call, your application will not use the Registry but will create and use an INI file in the Windows directory. The
SetRegistryKey function's string parameter establishes the top of the hierarchy, and the following Registry functions define the
bottom two levels, called the heading name and the entry name.
GetProfileInt
WriteProfileInt
GetProfileString
WriteProfileString
These functions treat Registry data as CString objects or unsigned integers. If you need floating-point values as entries, you must
use the string functions and do the conversion yourself. All the functions take a heading name and an entry name as parameters. In
the TEXTPROC example shown earlier, the heading name is Text Formatting and the entry names are Font and Points.
To use the Registry access functions, you need a pointer to the application object. The global function AfxGetApp does the job. In
the previous sample Registry, the Font and Points entries were set with the following code:
AfxGetApp()->WriteProfileString("Text formatting", "Font",
"Times New Roman");
AfxGetApp()->WriteProfileInt("Text formatting", "Points", 10);
You'll see a real Registry example shortly, in Ex14a, and you'll learn to use the Windows Regedit program to examine and edit the
Registry.
Using the CString Class
The MFC CString class is a significant de facto extension of the C++ language. The CString class has many useful operators and
member functions, but perhaps its most important feature is its dynamic memory allocation. You never have to worry about the size
of a CString object. The following statements represent typical uses of CString objects:
CString strFirstName("Elvis");
CString strLastName("Presley");
CString strTruth = strFirstName + " " + strLastName; // concatenation
strTruth += " is alive";
ASSERT(strTruth == "Elvis Presley is alive");
ASSERT(strTruth.Left(5) == strFirstName);
ASSERT(strTruth[2] == 'v'); // subscript operator
In a perfect world, C++ programs would use all CString objects and never use ordinary zero-terminated character arrays.
Unfortunately, many runtime library functions still use character arrays, so programs must always mix and match their string
representations. Fortunately, the CString class provides a const char*() operator that converts a CString object to a character
pointer. Many of the MFC library functions have const char* parameters. Take the global AfxMessageBox function, for example.
Here is one of the function's prototypes:
int AFXAPI AfxMessageBox(LPCTSTR lpszText, UINT nType = MB_OK,
UINT nIDHelp = 0);
Note that LPCTSTR is not a pointer to a CString object but is a Unicode-enabled replacement for const char*.
You can call AfxMessageBox in this way
char szMessageText[] = "Unknown error";
AfxMessageBox(szMessageText);
or this way:
CString strMessageText("Unknown error");
AfxMessageBox(strMessageText);
Now suppose you want to generate a formatted string. CString::Format does the job, as shown here:
int nError = 23;
CString strMessageText;
strMessageText.Format("Error number %d", nError);
AfxMessageBox(strMessageText);
Note The application framework stores a list of most recently used files in the Registry under the heading Recent File List.




Note Suppose you want direct write access to the characters in a CString object. If you write code like this:
CString strTest("test");
strncpy(strTest, "T", 1);
you'll get a compile error because the first parameter of strncpy is declared char*, not const char*. The CString::GetBuffer
function "locks down" the buffer with a specified size and returns a char*. You must call the ReleaseBuffer member
function later to make the string dynamic again. The correct way to capitalize the T is shown in the following example.
Page 33 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The const char* operator takes care of converting a CString object to a constant character pointer, but what about conversion in the
other direction? It so happens that the CString class has a constructor that converts a constant character pointer to a CString object,
and it has a set of overloaded operators for these pointers. That's why statements such as the following work:
strTruth += " is alive";
The special constructor works with functions that take a CString reference parameter, such as CDC::TextOut. In the following
statement, a temporary CString object is created on the calling program's stack and then the object's address is passed to TextOut:
pDC->TextOut(0, 0, "Hello, world!");
It's more efficient to use the other overloaded version of CDC::TextOut if you're willing to count the characters:
pDC->TextOut(0, 0, "Hello, world!", 13);
If you're writing a function that takes a string parameter, you've got some design choices. Here are some programming rules:
If the function doesn't change the contents of the string and you're willing to use C runtime functions such as strncpy, use a
const char* parameter.
If the function doesn't change the contents of the string but you want to use CString member functions inside the function, use a
const CString& parameter.
If the function changes the contents of the string, use a CString& parameter.
The Position of a Maximized Window
As a Windows user, you know that you can maximize a window from the system menu or by clicking a button at the top right corner
of the window. You can return a maximized window to its original size in a similar fashion. It's obvious that a maximized window
remembers its original size and position.
The CWnd function GetWindowRect retrieves the screen coordinates of a window. If a window is maximized, GetWindowRect
returns the coordinates of the screen rather than the window's unmaximized coordinates. If a persistent frame class is to work for
maximized windows, it has to know the window's unmaximized coordinates. CWnd::GetWindowPlacement retrieves the
unmaximized coordinates together with some flags that indicate whether the window is currently minimized or maximized.
The companion SetWindowPlacement function lets you set the maximized and minimized status and the size and position of the
window. To calculate the position of the top left corner of a maximized window, you need to account for the window's border size,
which is obtainable from the Win32 GetSystemMetrics function. Later in the chapter, you'll see the Persist.cpp file, in which the
CPersistentFrame::ActivateFrame code shows an example of how SetWindowPlacement is used.
Control Bar Status and the Registry
The MFC library provides two CFrameWnd member functions, SaveBarState and LoadBarState, for saving and loading control bar
status to and from the Registry, respectively. These functions process the size and position of the status bar and docked toolbars.
They don't process the position of floating toolbars, however.
Static Data Members
The CPersistentFrame class stores its Registry key names in static const char array data members. What would the other storage
choices be? String resource entries won't work because the strings need to be defined with the class itself. (String resources make
sense if CPersistentFrame is made into a DLL, however.) Global variables are generally not recommended because they defeat
encapsulation. Static CString objects don't make sense because the characters must be copied to the heap when the program
starts.
An obvious choice would be regular data members. But static data members are better because, as constants, they're segregated
into the program's read-only data section and can be mapped to multiple instances of the same program. If the CPersistentFrame
class is part of a DLL, all processes that are using the DLL can map the character arrays. Static data members are really global
variables, but they're scoped to their class so there's no chance of name collisions.
The Default Window Rectangle
You're used to defining rectangles using device or logical coordinates. A CRect object constructed with the following statement has
a special meaning:
CRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
CString strTest("test");
strncpy(strTest.GetBuffer(5), "T", 1);
strTest.ReleaseBuffer();
ASSERT(strTest == "Test");
















Page 34 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
When Windows creates a new window with this special rectangle, it positions the window in a cascade pattern with the top left
corner below and to the right of the window most recently created. The right and bottom edges of the window are always within the
display's boundaries.
The CFrameWnd class's static rectDefault data member is constructed using CW_USEDEFAULT in this way, so it contains the
special rectangle. The CPersistentFrame class declares its own rectDefault default window rectangle with a fixed size and position
as a static data member, thus hiding the base class member.
The Ex14a Example: Using a Persistent Frame Window Class
The Ex14a program illustrates the use of a persistent frame window class, CPersistentFrame. The following code shows the
contents of the files Persist.h and Persist.cpp, which are included in the Ex14a project on the companion CD. In this example,
we'll insert the new frame class into an MFC Application Wizard generated SDI application. Ex14a is a "do-nothing" application, but
you can insert the persistent frame class into any of your own SDI "do-something" applications.
Persist.h
// Persist.h

#ifndef _INSIDE_VISUAL_CPP_PERSISTENT_FRAME
#define _INSIDE_VISUAL_CPP_PERSISTENT_FRAME

class CPersistentFrame : public CFrameWnd
{ // remembers where it was on the desktop
DECLARE_DYNAMIC(CPersistentFrame)
private:
static const CRect s_rectDefault;
static const char s_profileHeading[];
static const char s_profileRect[];
static const char s_profileIcon[];
static const char s_profileMax[];
static const char s_profileTool[];
static const char s_profileStatus[];
BOOL m_bFirstTime;
protected: // Create from serialization only
CPersistentFrame();
~CPersistentFrame();

public:
virtual void ActivateFrame(int nCmdShow = -1);
protected:
afx_msg void OnDestroy();
DECLARE_MESSAGE_MAP()
};

#endif // _INSIDE_VISUAL_CPP_PERSISTENT_FRAME
Persist.cpp
// Persist.cpp Persistent frame class for SDI apps

#include "stdafx.h"
#include "persist.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////////////////////////////////
// CPersistentFrame

const CRect CPersistentFrame::s_rectDefault(10, 10,
500, 400); // static
const char CPersistentFrame::s_profileHeading[] = "Window size";
const char CPersistentFrame::s_profileRect[] = "Rect";
const char CPersistentFrame::s_profileIcon[] = "icon";
const char CPersistentFrame::s_profileMax[] = "max";
const char CPersistentFrame::s_profileTool[] = "tool";
const char CPersistentFrame::s_profileStatus[] = "status";
IMPLEMENT_DYNAMIC(CPersistentFrame, CFrameWnd)





Page 35 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
BEGIN_MESSAGE_MAP(CPersistentFrame, CFrameWnd)
ON_WM_DESTROY()
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////
CPersistentFrame::CPersistentFrame(){
m_bFirstTime = TRUE;
}
///////////////////////////////////////////////////////////////
CPersistentFrame::~CPersistentFrame()
{
}
///////////////////////////////////////////////////////////////
void CPersistentFrame::OnDestroy()
{
CString strText;
BOOL bIconic, bMaximized;

WINDOWPLACEMENT wndpl;
wndpl.length = sizeof(WINDOWPLACEMENT);
// gets current window position and
// iconized/maximized status
BOOL bRet = GetWindowPlacement(&wndpl);
if (wndpl.showCmd == SW_SHOWNORMAL) {
bIconic = FALSE;
bMaximized = FALSE;
}
else if (wndpl.showCmd == SW_SHOWMAXIMIZED) {
bIconic = FALSE;
bMaximized = TRUE;
}
else if (wndpl.showCmd == SW_SHOWMINIMIZED) {
bIconic = TRUE;
if (wndpl.flags) {
bMaximized = TRUE;
}
else {
bMaximized = FALSE;
}
}
strText.Format("%04d %04d %04d %04d",
wndpl.rcNormalPosition.left,
wndpl.rcNormalPosition.top,
wndpl.rcNormalPosition.right,
wndpl.rcNormalPosition.bottom);
AfxGetApp()->WriteProfileString(s_profileHeading,
s_profileRect, strText);
AfxGetApp()->WriteProfileInt(s_profileHeading,
s_profileIcon, bIconic);
AfxGetApp()->WriteProfileInt(s_profileHeading,
s_profileMax, bMaximized);
SaveBarState(AfxGetApp()->m_pszProfileName);
CFrameWnd::OnDestroy();
}

///////////////////////////////////////////////////////////////
void CPersistentFrame::ActivateFrame(int nCmdShow)
{
CString strText;
BOOL bIconic, bMaximized;
UINT flags;
WINDOWPLACEMENT wndpl;
CRect rect;

if (m_bFirstTime) {
m_bFirstTime = FALSE;
strText = AfxGetApp()->GetProfileString(s_profileHeading,
s_profileRect);
if (!strText.IsEmpty()) {
rect.left = atoi((const char*) strText);
rect.top = atoi((const char*) strText + 5);
rect.right = atoi((const char*) strText + 10);
Page 36 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
rect.bottom = atoi((const char*) strText + 15);
}
else {
rect = s_rectDefault;
}
bIconic = AfxGetApp()->GetProfileInt(s_profileHeading,
s_profileIcon, 0);
bMaximized = AfxGetApp()->GetProfileInt(s_profileHeading,
s_profileMax, 0);
if (bIconic) {
nCmdShow = SW_SHOWMINNOACTIVE;
if (bMaximized) {
flags = WPF_RESTORETOMAXIMIZED;
}
else {
flags = WPF_SETMINPOSITION;
}
}
else {
if (bMaximized) {
nCmdShow = SW_SHOWMAXIMIZED;
flags = WPF_RESTORETOMAXIMIZED;
}
else {
nCmdShow = SW_NORMAL;
flags = WPF_SETMINPOSITION;
}
}
wndpl.length = sizeof(WINDOWPLACEMENT);
wndpl.showCmd = nCmdShow;
wndpl.flags = flags;
wndpl.ptMinPosition = CPoint(0, 0);
wndpl.ptMaxPosition =
CPoint(-::GetSystemMetrics(SM_CXBORDER),
-::GetSystemMetrics(SM_CYBORDER));
wndpl.rcNormalPosition = rect;
LoadBarState(AfxGetApp()->m_pszProfileName);
// sets window's position and minimized/maximized status
BOOL bRet = SetWindowPlacement(&wndpl);
}
CFrameWnd::ActivateFrame(nCmdShow);
}
Here are the steps for building the Ex14a application:
1. Run the MFC Application Wizard to generate the Ex14a project. Accept all default settings but two: Select Single
Document and deselect Printing And Print Preview.
2. Modify MainFrm.h. You must change the base class of CMainFrame.
To do this, simply change the line
class CMainFrame : public CFrameWnd
to
class CMainFrame : public CPersistentFrame
Also, add this line:
#include "persist.h"
3. Modify MainFrm.cpp. Globally replace all occurrences of CFrameWnd with CPersistentFrame.
4. Modify Ex14a.cpp. Replace the line
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
with this line:
SetRegistryKey("Programming Visual C++ .NET");
5. Add the Persist.cpp file to the project. You can type in the Persist.h and Persist.cpp files from the previous code
listing, or you can copy the files from the companion CD. Having the files in the \vcppnet\Ex14a directory is not sufficient. You
must add the names of the files to the solution. Choose Add Existing Item from Visual C++ .NET's Project menu, and select
Persist.h and Persist.cpp from the list.
Page 37 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
6. Build and test the Ex14a application. Size and move the application's frame window, and then close the application. When
you restart the application, does its window open at the same location at which it was closed? Experiment with maximizing
and minimizing, and then change the status and position of the control bars. Does the persistent frame remember its
settings?
7. Examine the Windows Registry. Run the Windows Regedit.exe program. Navigate to the
HKEY_CURRENT_USER\Software\Programming Visual C++ .NET\Ex14a key. You should see data values similar to those
shown here:

Notice the relationship between the Registry key and the SetRegistryKey function parameter, "Programming Visual
C++ .NET". If you supply an empty string as the SetRegistryKey parameter, the program name (Ex14a, in this case) will be
positioned directly below the Software key.
Persistent Frames in MDI Applications
We won't get into Multiple Document Interface (MDI) applications until Chapter 16, but if you're using this book as a reference, you
might want to apply the persistent frame technique to MDI applications.
The CPersistentFrame class, as presented in this chapter, won't work in an MDI application because the MDI main frame window's
ShowWindow function is called, not by a virtual ActivateFrame function, but directly by the application class's InitInstance member
function. If you need to control the characteristics of an MDI main frame window, add the necessary code to InitInstance.
The ActivateFrame function is called, however, for CMDIChildWnd objects. This means your MDI application could remember the
sizes and positions of its child windows. You could store the information in the Registry, but you would have to accommodate
multiple windows. You would have to modify the CPersistentFrame class for this purpose.
Chapter 15: Separating the Document from Its View
Overview
Now you'll finally get to see the interaction between documents and views. Chapter 12 gave you a preview of this interaction when it
showed the routing of command messages to both view objects and document objects. In this chapter, you'll see how the document
maintains the application's data and how the view presents the data to the user. You'll also learn how the document and view
objects talk to each other while the application executes.
The two examples in this chapter both use the CFormView class as the base class for their views. The first example is as simple as
possible, with the document holding only one simple object of class CStudent, which represents a single student record. The view
shows the student's name and grade and allows editing. With the CStudent class, you'll get some practice writing classes to
represent real-world entities. You'll also get to use the Microsoft Foundation Class (MFC) library diagnostic dump functions.
The second example goes further by introducing pointer collection classesthe CObList and CTypedPtrList classes in particular.
The document holds a collection of student records, and the view allows the sequencing, insertion, and deletion of individual
records.
Document-View Interaction Functions
You already know that the document object holds the data and that the view object displays the data and allows editing. A Single
Document Interface (SDI) application has a document class derived from CDocument, and it has one or more view classes, each
ultimately derived from CView. A complex handshaking process takes place among the document, the view, and the rest of the
application framework.












Page 38 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
To understand this process, you need to know about five important member functions in the document and view classes. Two are
nonvirtual base class functions that you call in your derived classes; three are virtual functions that you often override in your derived
classes. Let's look at these functions one at a time.
The CView::GetDocument Function
A view object has one and only one associated document object. The GetDocument function allows an application to navigate from
a view to its document. Suppose a view object gets a message that the user has entered new data into an edit control. The view
must tell the document object to update its internal data accordingly. The GetDocument function provides the document pointer that
can be used to access document class member functions or public data members.
When the MFC Application Wizard generates a derived CView class, it creates two special type-safe versions of the GetDocument
function (a debug version and a non-debug version) that return a pointer to an object of your derived document class. The non-
debug version (which appears in the view header file) looks like this:
inline CMyDoc* CMyView::GetDocument() const
{ return reinterpret_cast<CMyDoc*>(m_pDocument); }
The debug version (which appears in the view source code file and is compiled when debugging is defined) looks like this:
CMyDoc* CMyView::GetDocument() const // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyDoc)));
return (CMyDoc*)m_pDocument;
}
When the compiler sees a call to GetDocument in your view class code, it uses CMyView::GetDocument, which returns
CMyDocument *, instead of using CView::GetDocument, which returns CDocument *. Because CMyDocument * is returned, you do
not have to cast the returned pointer to your derived document class. Without a helper function like this, the compiler would call the
base class's GetDocument function and thus return a pointer to a CDocument object.
Notice that a statement such as the following always calls the base class's GetDocument functionwhether or not you have the
previous helper function in your programbecause the CView::GetDocument function is not a virtual function:
pView->GetDocument(); // pView is declared CView*
The CDocument::UpdateAllViews Function
If the document data changes for any reason, all views must be notified so they can update their representations of that data. If
UpdateAllViews is called from a member function of a derived document class, its first parameter, pSender, is NULL. If
UpdateAllViews is called from a member function of a derived view class, set the pSender parameter to the current view, like this:
GetDocument()->UpdateAllViews(this);
The non-null parameter prevents the application framework from notifying the current view. The assumption here is that the current
view has already updated itself.
The function has optional hint parameters that you can use to give view-specific and application-dependent information about which
parts of the view to update. This is an advanced use of the function.
How exactly is a view notified when UpdateAllViews gets called? Take a look at the next function, OnUpdate.
The CView::OnUpdate Function
This virtual function is called by the application framework in response to your application's call to the CDocument::UpdateAllViews
function. You can, of course, call it directly within your derived CView class. Typically, your derived view class's OnUpdate function
accesses the document, gets the document's data, and then updates the view's data members or controls to reflect the changes.
Alternatively, OnUpdate can invalidate a portion of the view, causing the view's OnDraw function to use document data to draw in
the window. The OnUpdate function might look something like this:
void CMyView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
CMyDocument* pMyDoc = GetDocument();
CString lastName = pMyDoc->GetLastName();
m_pNameStatic->SetWindowText(lastName); // m_pNameStatic is
// a CMyView data member
}
The hint information is passed through directly from the call to UpdateAllViews. The default OnUpdate implementation invalidates
the entire window rectangle. In your overridden version, you can choose to define a smaller invalid rectangle as specified by the hint
information.
Note The CDocument::GetNextView function navigates from the document to the view, but because a document can have more
than one view, you have to call this member function once for each view, inside a loop. You'll seldom call GetNextView
because the application framework provides a better method of iterating through a document's views.
Page 39 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
If the CDocument function UpdateAllViews is called with the pSender parameter pointing to a specific view object, OnUpdate is
called for all the document's views except the specified view.
The CView::OnInitialUpdate Function
This virtual CView function is called when the application starts, when the user chooses New from the File menu, or when the user
chooses Open from the File menu. The CView base class version of OnInitialUpdate does nothing but call OnUpdate. If you override
OnInitialUpdate in your derived view class, be sure that the view class calls the base class's OnInitialUpdate function or the derived
class's OnUpdate function.
You can use your derived class's OnInitialUpdate function to initialize your view object. When the application starts, the application
framework calls OnInitialUpdate immediately after OnCreate (if you've mapped OnCreate in your view class). OnCreate is called
once, but OnInitialUpdate can be called many times.
The CDocument::OnNewDocument Function
The framework calls this virtual function after a document object is first constructed or when the user chooses New from the File
menu in an SDI application. This is a good place to set the initial values of your document's data members. The MFC Application
Wizard generates an overridden OnNewDocument function in your derived document class. Be sure to retain the call to the base
class function.
The Simplest Document-View Application
Suppose you don't need multiple views of your document, but you plan to take advantage of the application framework's file support.
In this case, you can forget about the UpdateAllViews and OnUpdate functions. Simply follow these steps when you develop the
application:
1. In your derived document class header file (generated by the MFC Application Wizard), declare your document's data
members. These data members are the primary data storage for your application. You can make these data members public,
or you can declare the derived view class a friend of the document class.
2. In your derived view class, override the OnInitialUpdate virtual member function. The application framework calls this function
after the document data has been initialized or read from disk. ( Chapter 16 discusses disk file I/O.) OnInitialUpdate should
update the view to reflect the current document data.
3. In your derived view class, let your window message handlers, command message handlers, and your OnDraw function read
and update the document data members directly, using GetDocument to access the document object.
The sequence of events for this simplified document-view environment is as follows:
The CFormView Class
The CFormView class is a useful view class that has many of the characteristics of a modeless dialog box. Like a class derived from
CDialog, a derived CFormView class is associated with a dialog resource that defines the frame characteristics and enumerates the
controls. The CFormView class supports the same dialog data exchange and validation (DDX and DDV) functions that you saw in
the CDialog examples in Chapter 7.
A CFormView object receives notification messages directly from its controls, and it receives command messages from the
application framework. This application framework command-processing ability clearly separates CFormView from CDialog, and it
makes controlling the view from the frame's main menu or toolbar easy.




Application starts CMyDocument object is constructed

CMyView object is constructed

View window is created

CMyView::OnCreate is called (if it is mapped)

CMyDocument::OnNewDocument is called

CMyView::OnInitialUpdate is called

View object is initialized

View window is invalidated

CMyView::OnDraw is called
User edits data CMyView functions update CMyDocument data members
User exits application CMyView object is destroyed

CMyDocument object is destroyed




Page 40 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The CFormView class is derived from CView (actually, from CScrollView) and not from CDialog. You can't, therefore, assume that
CDialog member functions are supported. CFormView does not have virtual OnInitDialog, OnOK, and OnCancel functions.
CFormView member functions do not call UpdateData and the DDX functions. You have to call UpdateData yourself at the
appropriate times, usually in response to control notification messages or command messages.
Even though the CFormView class is not derived from the CDialog class, it is built around the Microsoft Windows dialog box. For this
reason, you can use many of the CDialog class member functions such as GotoDlgCtrl and NextDlgCtrl. All you have to do is cast
your CFormView pointer to a CDialog pointer. The following statement, extracted from a member function of a class derived from
CFormView, sets the focus to a specified control. GetDlgItem is a CWnd function and is thus inherited by the derived CFormView
class.
((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));
The MFC Application Wizard gives you the option of using CFormView as the base class for your view. When you select
CFormView, the MFC Application Wizard generates an empty dialog box with the correct style properties set. The next step is to use
the Class View's Properties window to add control notification message handlers, command message handlers, and update
command user interface handlers. (The example steps show you what to do.) You can also define data members and validation
criteria.
The CObject Class
If you study the MFC library hierarchy, you'll notice that the CObject class is at the top. Most other classes are derived from the
CObject root class. When a class is derived from CObject, it inherits a number of important characteristics. The many benefits of
CObject derivation will become clear as you read the chapters that follow.
In this chapter, you'll see how CObject derivation allows objects to participate in the diagnostic dumping scheme and allows objects
to be elements in the collection classes.
Diagnostic Dumping
The MFC library gives you some useful tools for diagnostic dumping. You enable these tools when you select the Debug
configuration. When you select the Release configuration, diagnostic dumping is disabled and the diagnostic code is not linked to
your program. All diagnostic output goes to the Debug view in the debug Output window.
The TRACE Macro
We've been using the TRACE macro throughout the preceding examples in this book. TRACE statements are active whenever the
constant _DEBUG is defined (when you select the Debug configuration and when the afxTraceEnabled variable is set to TRUE).
TRACE statements work like C language printf statements, but they're completely disabled in the release version of the program.
Here's a typical TRACE statement:
int nCount = 9;
CString strDesc("total");
TRACE("Count = %d, Description = %s\n", nCount, strDesc);
Even though the TRACE macro is deprecated (the documentation suggests using ATLTRACE), it is still available and works just
fine.
The afxDump Object
An alternative to the TRACE statement is more compatible with the C++ language. The MFC afxDump object accepts program
variables with a syntax similar to that of cout, the C++ output stream object. You don't need complex formatting strings; instead,
overloaded operators control the output format. The afxDump output goes to the same destination as the TRACE output, but the
afxDump object is defined only in the Debug version of the MFC library.
Here is a typical stream-oriented diagnostic statement that produces the same output as the TRACE statement above:
int nCount = 9;
CString strDesc("total");
#ifdef _DEBUG
afxDump << "Count = " << nCount
<< ", Description = " << strDesc << "\n";
Warning If the MFC Application Wizard generates a Form View dialog box, the properties are set correctly, but if you use the
dialog editor to make a dialog box for a form view, you must specify the following items in the Dialog Properties
window:
Style = Child
Border = None
Visible = unchecked








Tip To clear diagnostic output from the debug Output window, position the cursor in the Output window and click the right
mouse button. Then choose Clear All from the shortcut menu.
Page 41 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
#endif // _DEBUG
Although both afxDump and cout use the same insertion operator (<<), they don't share any code. The cout object is part of the
Microsoft Visual C++ iostream library, and afxDump is part of the MFC library. Don't assume that any of the cout formatting
capability is available through afxDump.
Classes that aren't derived from CObject, such as CString, CTime, and CRect, contain their own overloaded insertion operators for
CDumpContext objects. The CDumpContext class, of which afxDump is an instance, includes the overloaded insertion operators for
the native C++ data types (int, double, char*, and so on). The CDumpContext class also contains insertion operators for CObject
references and pointers, and that's where things get interesting.
The Dump Context and the CObject Class
If the CDumpContext insertion operator accepts CObject pointers and references, it must also accept pointers and references to
derived classes. Consider a trivial class, CAction, which is derived from CObject, as shown here:
class CAction : public CObject
{
public:
int m_nTime;
};
What happens when the following statement executes?
#ifdef _DEBUG
afxDump << action; // action is an object of class CAction
#endif // _DEBUG
The virtual CObject::Dump function gets called. If you haven't overridden Dump for CAction, you don't get much except for the
address of the object. If you've overridden Dump, however, you can get the internal state of your object. Here's a CAction::Dump
function:
#ifdef _DEBUG
void CAction::Dump(CDumpContext& dc) const
{
CObject::Dump(dc); // Always call base class function
dc << "time = " << m_nTime << "\n";
}
#endif // _DEBUG
The base class (CObject) Dump function prints a line such as this:
a CObject at $4115D4
If you have called the DECLARE_DYNAMIC macro in your CAction class definition and the IMPLEMENT_DYNAMIC macro in your
CAction declaration, you'll see the name of the class in your dump, as shown here
a CAction at $4115D4
even if your dump statement looks like this:
#ifdef _DEBUG
afxDump << (CObject&) action;
#endif // _DEBUG
The two macros work together to include the MFC library runtime class code in your derived CObject class. With this code in place,
your program can determine an object's class name at run time (for the dump, for example) and it can obtain class hierarchy
information.
Automatic Dump of Undeleted Objects
When the Debug configuration is selected, the application framework dumps all objects that are undeleted when your program exits.
This dump is a useful diagnostic aid, but if you want it to be really useful, you must be sure to delete all your objects, even the ones
that would normally disappear after the exit. This object cleanup is good programming discipline.
The code that adds debug information to allocated memory blocks is now in the Debug version of the C runtime (CRT) library rather
than in the MFC library. If you choose to dynamically link MFC, the MSVCRTD DLL will be loaded along with the necessary MFC
DLLs. When you add the following line at the top of a CPP file, the CRT library will list the filename and line number at which the
allocations were made:
#define new DEBUG_NEW
The MFC Application Wizard puts this line at the top of all the CPP files it generates.
Note The (DECLARE_SERIAL, IMPLEMENT_SERIAL) and (DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE) macro
pairs provide the same runtime class features as those provided by the (DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC)
macro pair.
Page 42 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Window Subclassing for Enhanced Data-Entry Control
What if you want an edit control (in a dialog box or a form view) that accepts only numeric characters? That's easy. You just set
the Number style in the control's property sheet. If, however, you want to exclude numeric characters or change the case of
alphabetic characters, you must do some programming.
The MFC library provides a convenient way to change the behavior of any standard control, including the edit control. Two other
ways are available: You can derive your own classes from CEdit, CListBox, and so forth (with their own message handler
functions) and then create control objects at run time. Or you can register a special window class, as a Win32 programmer
would, and integrate it into the project's resource file with a text editor. Neither of these methods, however, allows you to use the
dialog editor to position controls in the dialog resource.
The easy way to modify a control's behavior is to use the MFC library's window subclassing feature. You use the dialog editor to
position a normal control in a dialog resource, and then you write a new C++ class that contains message handlers for the
events that you want to handle yourself. Here are the steps for subclassing an edit control:
1. With the dialog editor, position an edit control in your dialog resource. Assume that it has the child window ID IDC_EDIT1.
2. Write a new classfor example, CNonNumericEditthat is derived from CEdit. Map the WM_CHAR message and write
a handler like this:
void CNonNumericEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (!isdigit(nChar)) {
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
}
3. In your derived dialog or form view class header, declare a data member of class CNonNumericEdit in this way:
private:
CNonNumericEdit m_nonNumericEdit;
4. If you're working with a dialog class, add the following line to your OnInitDialog override function:
m_nonNumericEdit.SubclassDlgItem(IDC_EDIT1, this);
5. If you're working with a form view class, add the following code to your OnInitialUpdate override function:
if (m_nonNumericEdit.m_hWnd == NULL) {
m_nonNumericEdit.SubclassDlgItem(IDC_EDIT1, this);
}
The CWnd::SubclassDlgItem member function ensures that all messages are routed through the application framework's
message dispatch system before being sent to the control's built-in window procedure. This technique is called dynamic
subclassing and is explained in more detail in Technical Note #14 in the MFC Library Reference.
The code in the preceding steps only accepts or rejects a character. If you want to change the value of a character, your handler
must call CWnd::DefWindowProc, which bypasses some MFC logic that stores parameter values in thread object data
members. Here's a sample handler that converts lowercase characters to uppercase:
void CUpperEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (islower(nChar)) {
nChar = toupper(nChar);
}
DefWindowProc(WM_CHAR, (WPARAM) nChar,
(LPARAM) (nRepCnt | (nFlags << 16)));
}
You can also use window subclassing to handle reflected messages, which were mentioned in Chapter 7. If an MFC window
class doesn't map a message from one of its child controls, the framework will reflect the message back to the control. Technical
Note #62 in the MFC Library Reference explains the details.
If you need an edit control with a yellow background, for example, you can derive a class CYellowEdit from CEdit and use Class
View's Properties window to map the =WM_CTLCOLOR message in CYellowEdit. (The Properties window lists the message
name with an equal sign in front to indicate that it is reflected.) The handler code, shown below, is substantially the same as the
nonreflected WM_CTLCOLOR handler shown on page 157. ((Member variable m_hYellowBrush is defined in the control class's
constructor.)
HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
pDC->SetBkColor(RGB(255, 255, 0)); // yellow
return m_hYellowBrush;
}




Page 43 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The Ex15a Example: A Simple Document-View Interaction
The first of this chapter's two examples shows a very simple document-view interaction. The CEx15aDoc document class, which is
derived from CDocument, allows for a single embedded CStudent object. The CStudent class represents a student record
composed of a CString name and an integer grade. The CEx15aView view class is derived from CFormView. It is a visual
representation of a student record that has edit controls for the name and grade. The default Enter pushbutton updates the
document with data from the edit controls. Figure 15-1 shows the Ex15a program window.

Figure 15-1: The Ex15a program in action.
The code for the CStudent class is shown below. Most of the class's features serve Ex15a, but a few items carry forward to Ex15b
and the programs discussed in Chapter 16. For now, take note of the two data members, the default constructor, the operators, and
the Dump function declaration. The DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC macros ensure that the class name is
available for the diagnostic dump.
Student.h
// student.h

#ifndef _INSIDE_VISUAL_CPP_STUDENT
#define _INSIDE_VISUAL_CPP_STUDENT
class CStudent : public CObject
{
DECLARE_DYNAMIC(CStudent)
public:
CString m_strName;
int m_nGrade;

CStudent()
{
m_nGrade = 0;
}

CStudent(const char* szName, int nGrade) : m_strName(szName)
{
m_nGrade = nGrade;
}

CStudent(const CStudent& s) : m_strName(s.m_strName)
{
// copy constructor
m_nGrade = s.m_nGrade;
}

const CStudent& operator =(const CStudent& s)
{
m_strName = s.m_strName;
m_nGrade = s.m_nGrade;
return *this;
}

BOOL operator ==(const CStudent& s) const
{
if ((m_strName == s.m_strName) && (m_nGrade == s.m_nGrade)) {
return TRUE;
}
else {
return FALSE;
Page 44 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
}
}

BOOL operator !=(const CStudent& s) const
{
// Let's make use of the operator we just defined!
return !(*this == s);
}
#ifdef _DEBUG
void Dump(CDumpContext& dc) const;
#endif // _DEBUG
};

#endif // _INSIDE_VISUAL_CPP_STUDENT
Student.cpp
#include "stdafx.h"
#include "student.h"

IMPLEMENT_DYNAMIC(CStudent, CObject)

#ifdef _DEBUG
void CStudent::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
dc << "m_strName = " << m_strName << "\nm_nGrade = " << m_nGrade;
}
#endif // _DEBUG
Follow these steps to build the Ex15a example:
1. Run the MFC Application Wizard to generate the Ex15a project. Make it an SDI application. On the Generated Classes
page, change the view's base class to CFormView, as shown here.

2. Use the menu editor to replace the Edit menu commands. Delete the current Edit menu commands and replace them
with a Clear All command. Use the default constant ID_EDIT_CLEARALL, which is assigned by the application framework.
3. Use the dialog editor to modify the IDD_EX15A_FORM dialog box.Open the MFC Application Wizardgenerated
dialog box IDD_EX15A_FORM, and then add controls as shown here.
Page 45 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

Be sure that you set the following properties in the dialog editor's Properties window: Style = Child, Border = None, Visible =
False. Use the following IDs for the controls.
4. Use Class View's Properties window to add message handlers for CEx15aView. Select the CEx15aView class, and then
add handlers for the following messages. Accept the default function names.
5. Use the Add Member Variable Wizard to add variables for CEx15aView. In Class View, right-click on CEx15aView and
choose Add Variable. Add the following variables:
For m_nGrade, enter a minimum value of 0 and a maximum value of 100. Notice that the Add Member Variable Wizard
generates the code necessary to validate data entered by the user.
6. Add a prototype for the helper function UpdateControlsFromDoc. In Class View, right-click on CEx15aView and choose
Add Function. Fill out the dialog box to add the following function:
private:
void UpdateControlsFromDoc(void);
7. Edit the file Ex15aView.cpp. The MFC Application Wizard generated the skeleton OnInitialUpdate function, and the Add
Member Function Wizard available from Class View generated the skeleton UpdateControlsFromDoc function.
UpdateControlsFromDoc is a private helper member function that transfers data from the document to the CEx15aView data
members and then to the dialog edit controls. Edit the code as shown here:
void CEx15aView::OnInitialUpdate()
{ // called on startup
CFormView::OnInitialUpdate();
UpdateControlsFromDoc();
}
void CEx15aView::UpdateControlsFromDoc(void)
{ // called from OnInitialUpdate and OnEditClearall
CEx15aDoc* pDoc = GetDocument();
m_nGrade = pDoc->m_student.m_nGrade;
m_strName = pDoc->m_student.m_strName;
UpdateData(FALSE); // calls DDX
Control ID
Name edit control IDC_NAME
Grade edit control IDC_GRADE
Enter button IDC_ENTER
Object ID Message Member Function
IDC_ENTER BN_CLICKED OnBnClickedEnter
ID_EDIT_CLEARALL COMMAND OnEditClearall
ID_EDIT_CLEARALL UPDATE_COMMAND_UI OnUpdateEditClearall
Control ID Member Variable Category Variable Type
IDC_GRADE m_nGrade Value int
IDC_NAME m_strName Value CString
Page 46 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
}
The OnBnClickedEnter function replaces the OnOK function you'd expect to see in a dialog class. The function transfers data
from the edit controls to the view's data members and then to the document. Add the boldface code shown here:
void CEx15aView::OnBnClickedEnter()
{
CEx15aDoc* pDoc = GetDocument();
UpdateData(TRUE);
pDoc->m_student.m_nGrade = m_nGrade;
pDoc->m_student.m_strName = m_strName;
}
In a complex multi-view application, the Edit Clear All command would be routed directly to the document. In this simple
example, it's routed to the view. The update command user interface handler disables the menu command if the document's
student object is already blank. Add the following boldface code:
void CEx15aView::OnEditClearall()
{
GetDocument()->m_student = CStudent(); // "blank" student object
UpdateControlsFromDoc();
}
void CEx15aView::OnUpdateEditClearall(CCmdUI* pCmdUI)
{
pCmdUI->Enable(GetDocument()->m_student != CStudent()); // blank?
}
8. Edit the Ex15a project to add the files for CStudent. Be sure that Student.h and Student.cpp are in your project
directory. Choose Add Existing Item from the Project menu and select the Student.h header and the Student.cpp source
code files. Visual C++ .NET will add the files' names to the project's project file so that they will be compiled when you build
the project.
9. Add a CStudent data member to the CEx15aDoc class. Edit the code in Ex15aDoc.h and remember to include
Student.h in the C Ex15aDoc.h file.
public:
CStudent m_student;
The CStudent constructor is called when the document object is constructed, and the CStudent destructor is called when the
document object is destroyed.
10. Edit the Ex15aDoc.cpp file. Use the CEx15aDoc constructor to initialize the student object, as shown here:
CEx15aDoc::CEx15aDoc() : m_student("default value", 0)
{
TRACE("Document object constructed\n");
}
We can't tell whether the Ex15a program works properly unless we dump the document when the program exits. We'll use
the destructor to call the document's Dump function, which calls the CStudent::Dump function shown here:
CEx15aDoc::~CEx15aDoc()
{
#ifdef _DEBUG
Dump(afxDump);
#endif // _DEBUG
}

void CEx15aDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
dc << "\n" << m_student << "\n";
}
11. Build and test the Ex15a application. Type a name and a grade, and then click Enter. Now exit the application. Does the
Debug window show messages similar to those shown here?
a CEx15aDoc at $411580
m_strTitle = Untitled
m_strPathName =
m_bModified = 0
m_pDocTemplate = $4113A0

a CStudent at $4115D4
m_strName = Sullivan, Walter
m_nGrade = 78
Page 47 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
A More Advanced Document-View Interaction
If you're laying the groundwork for a multi-view application, the document-view interaction must be more complex than the simple
interaction shown in example Ex15a. The fundamental problem is this: The user edits in view #1, so view #2 (and any other views)
must be updated to reflect the changes. Now you need the UpdateAllViews and OnUpdate functions because the document will act
as the clearinghouse for all view updates. The development steps are shown here:
1. In your derived document class header file (generated by the MFC Application Wizard), declare your document's data
members. If you want to, you can make these data members private and you can define member functions to access them or
declare the view class as a friend of the document class.
2. In your derived view class, use Class View's Properties window to override the OnUpdate virtual member function. The
application framework calls this function whenever the document data has changed for any reason. OnUpdate should update
the view with the current document data.
3. Evaluate all your command messages. Determine whether each one is document-specific or view-specific. (A good example
of a document-specific command is the Clear All command on the Edit menu.) Now map the commands to the appropriate
classes.
4. In your derived view class, allow the appropriate command message handlers to update the document data. Be sure that
these message handlers call the CDocument::UpdateAllViews function before they exit. Use the type-safe version of the
CView::GetDocument member function to access the view's document.
5. In your derived document class, allow the appropriate command message handlers to update the document data. Be sure
that these message handlers call the CDocument::UpdateAllViews function before they exit.
The sequence of events for the complex document-view interaction is shown here:
The CDocument::DeleteContents Function
At some point, you'll need a function to delete the contents of your document. You could write your own private member function, but
it happens that the application framework declares a virtual DeleteContents function for the CDocument class. The application
framework calls your overridden DeleteContents function when the document is closed and, as you'll see in the next chapter, at
other times as well.
The CObList Collection Class
Once you get to know the collection classes, you'll wonder how you ever got along without them. The CObList class is a useful
representative of the collection class family. If you're familiar with this class, it's easy to learn the other list classes, the array classes,
and the map classes.
You might think that collections are something new, but the C programming language has always supported one kind of collection:
Note To see these messages, you must compile the application with the DEBUG symbol defined or with the Debug
configuration selected.




Application starts CMyDocument object is constructed
CMyView object is constructed
Other view objects are constructed
View windows are created
CMyView::OnCreate is called (if it is mapped)
CDocument::OnNewDocument is called
CView::OnInitialUpdate is called
CMyView::OnUpdate is called
The view is initialized
User executes view command CMyView functions update CMyDocument data members
CDocument::UpdateAllViews is called
OnUpdate functions are called for other views
User executes document command CMyDocument functions update data members
CDocument::UpdateAllViews is called
CMyView::OnUpdate is called
Other views' OnUpdate functions are
called
User exits application View objects are destroyed
CMyDocument object is destroyed








Page 48 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
the array. C arrays must be fixed in size, and they do not support insertion of elements. Many C programmers have written function
libraries for other collections, including linked lists, dynamic arrays, and indexed dictionaries. For implementing collections, the C++
class is an obvious and good alternative to a C function library. A list object, for example, neatly encapsulates the list's internal data
structures.
The CObList class supports ordered lists of pointers to objects of classes derived from CObject. Another MFC collection class,
CPtrList, stores void pointers instead of CObject pointers. Why not use CPtrList instead? The CObList class offers advantages for
diagnostic dumping, which you'll see in this chapter, and for serialization, which you'll see in the next chapter. One important feature
of CObList is that it can contain mixed pointers. In other words, a CObList collection can hold pointers to both CStudent objects and
CTeacher objects, assuming that both CStudent and CTeacher were derived from CObject.
Using the CObList Class for a First-In, First-Out List
One of the easiest ways to use a CObList object is to add new elements to the tail, or bottom, of the list and to remove elements
from the head, or top, of the list. The first element added to the list will always be the first element removed from the head of the list.
Suppose you're working with element objects of class CAction, which is your own custom class derived from CObject. A command-
line program that puts five elements into a list and then retrieves them in the same sequence is shown here:
#include <afx.h>
#include <afxcoll.h>

class CAction : public CObject
{
private:
int m_nTime;
public:
CAction(int nTime) { m_nTime = nTime; } // Constructor stores
// integer time value
void PrintTime() { TRACE("time = %d\n", m_nTime); }
};

int main()
{
CAction* pAction;
CObList actionList; // action list constructed on stack
int i;

// inserts action objects in sequence {0, 1, 2, 3, 4}
for (i = 0; i < 5; i++) {
pAction = new CAction(i);
actionList.AddTail(pAction); // no cast necessary for pAction
}

// retrieves and removes action objects in sequence {0, 1, 2, 3, 4}
while (!actionList.IsEmpty()) {
pAction = // cast required for
(CAction*) actionList.RemoveHead(); // return value
pAction->PrintTime();
delete pAction;
}

return 0;
}
Here's what's going on in the program. First, a CObList object, actionList, is constructed. Then the CObList::AddTail member
function inserts pointers to newly constructed CAction objects. No casting is necessary for pAction because AddTail takes a CObject
pointer parameter and pAction is a pointer to a derived class.
Next, the CAction object pointers are removed from the list of the objects deleted. A cast is necessary for the returned value of
RemoveHead because RemoveHead returns a CObject pointer that is higher in the class hierarchy than CAction.
When you remove an object pointer from a collection, the object is not automatically deleted. The delete statement is necessary for
deleting the CAction objects.
CObList Iteration: The POSITION Variable
Suppose you want to iterate through the elements in a list. The CObList class provides a GetNext member function that returns a
pointer to the "next" list element, but using it is a little tricky. GetNext takes a parameter of type POSITION, which is a 32-bit
variable. The POSITION variable is an internal representation of the retrieved element's position in the list. Because the POSITION
parameter is declared as a reference (&), the function can change its value.
GetNext does the following:
1. It returns a pointer to the "current" object in the list, which is identified by the incoming value of the POSITION parameter.
Page 49 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
2. It increments the value of the POSITION parameter to the next list element.
Here's what a GetNext loop looks like, assuming you're using the list generated in the previous example:
CAction* pAction;
POSITION pos = actionList.GetHeadPosition();
while (pos != NULL) {
pAction = (CAction*) actionList.GetNext(pos);
pAction->PrintTime();
}
Now suppose you have an interactive Windows-based application that uses toolbar buttons to sequence forward and backward
through the list one element at a time. You can't use GetNext to retrieve the entry because GetNext always increments the
POSITION variable and you don't know in advance whether the user will want the next element or the previous element. Here's a
sample view class command message handler function that gets the next list entry. In the CMyView class, m_actionList is an
embedded CObList object and the m_position data member is a POSITION variable that holds the current list position.
CMyView::OnCommandNext()
{
POSITION pos;
CAction* pAction;

if ((pos = m_position) != NULL) {
m_actionList.GetNext(pos);
if (pos != NULL) { // pos is NULL at end of list
pAction = (CAction*) m_actionList.GetAt(pos);
pAction->PrintTime();
m_position = pos;
}
else {
AfxMessageBox("End of list reached");
}
}
}
GetNext is now called first to increment the list position, and the CObList::GetAt member function is called to retrieve the entry. The
m_position variable is updated only when we're sure we're not at the tail of the list.
The CTypedPtrList Template Collection Class
The CObList class works fine if you want a collection to contain mixed pointers. If, on the other hand, you want a type-safe collection
that contains only one type of object pointer, you should look at the MFC library template pointer collection classes. CTypedPtrList is
a good example. Templates were introduced in Visual C++ version 2.0. CTypedPtrList is a template class that you can use to create
a list of any pointers to objects of any specified class. To make a long story short, you use the template to create a custom derived
list class, using either CPtrList or CObList as a base class.
To declare an object for CAction pointers, you write the following line of code:
CTypedPtrList<CObList, CAction*> m_actionList;
The first parameter is the base class for the collection, and the second parameter is the type for parameters and return values. Only
CPtrList and CObList are permitted for the base class because those are the only two MFC library pointer list classes. If you're
storing objects of classes derived from CObject, you should use CObList as your base class; otherwise, use CPtrList.
By using the template as shown above, the compiler ensures that all list member functions return a CAction pointer. Thus, you can
write the following code:
pAction = m_actionList.GetAt(pos); // no cast required
If you want to clean up the notation a little, use a typedef statement to generate what looks like a class, as shown here:
typedef CTypedPtrList<CObList, CAction*> CActionList;
Now you can declare m_actionList as follows:
CActionList m_actionList;
The Dump Context and Collection Classes
The Dump function for CObList and the other collection classes has a useful property. If you call Dump for a collection object, you
can get a display of each object in the collection. If the element objects use the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC
macros, the dump will show the class name for each object.
The default behavior of the collection Dump functions is to display only class names and addresses of element objects. If you want
the collection Dump functions to call the Dump function for each element object, you must, somewhere at the start of your program,
make the following call:
Page 50 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
#ifdef _DEBUG
afxDump.SetDepth(1);
#endif
Now the following statement
#ifdef _DEBUG
afxDump << actionList;
#endif
will produce output such as this:
a CObList at $411832
with 4 elements
a CAction at $412CD6
time = 0
a CAction at $412632
time = 1
a CAction at $41268E
time = 2
a CAction at $4126EA
time = 3
If the collection contains mixed pointers, the virtual Dump function will be called for the object's class and the appropriate class
name will be printed.
The Ex15b Example: A Multi-View SDI Application
This second SDI example improves on Ex15a in the following ways:
Instead of a single embedded CStudent object, the document contains a list of CStudent objects. (Now you see the reason for
using the CStudent class instead of making m_strName and m_nGrade data members of the document.)
Toolbar buttons allow the user to sequence through the list.
The application is structured to allow the addition of extra views. The Edit Clear All command is now routed to the document
object, so the document's UpdateAllViews function and the view's OnUpdate function are brought into play.
The student-specific view code is isolated so that the CEx15bView class can later be transformed into a base class that
contains only general-purpose code. Derived classes can override selected functions to accommodate lists of application-
specific objects.
The Ex15b window, shown in Figure 15-2, looks a little different from the Ex15a window shown earlier in Figure 15-1. The toolbar
buttons are enabled only when appropriate. The Next (down arrow) button, for example, is disabled when we're positioned at the
bottom of the list.

Figure 15-2: The Ex15b program in action.
The toolbar buttons function as follows.




Button Function

Retrieves the first student record

Retrieves the last student record

Retrieves the previous student record
Page 51 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The Clear button in the view window clears the contents of the Name and Grade edit controls. The Clear All command on the Edit
menu deletes all the student records in the list and clears the view's edit controls.
This example deviates from the step-by-step format in the previous examples. There's now more code, so we'll simply show
selected code and the resource requirements. Boldface code indicates additional code or other changes that you enter in the output
from the MFC Application Wizard and the code wizards available from Class View's Properties window. The frequent use of TRACE
statements lets you follow the program's execution in the debugging window.
Resource Requirements
The file Ex15b.rc defines the application's resources as follows.
Toolbar
The toolbar (visible in Figure 15-2) was created by erasing the Edit Cut, Copy, and Paste tiles (fourth, fifth, and sixth from the left)
and replacing them with six new patterns. The Flip Vertical command (on the Image menu) was used to duplicate some of the tiles.
The Ex15b.rc file defines the linkage between the command IDs and the toolbar buttons.
Student Menu
It isn't absolutely necessary to have menu commands that correspond to the new toolbar buttons. (Class View's Properties window
allows you to map toolbar button commands just as easily as menu commands.) However, most applications for Windows have
corresponding menu commands, so users generally expect them.
Edit Menu
On the Edit menu, the clipboard commands are replaced by the Clear All command. See step 2 of the Ex15a example for an
illustration of the Edit menu.
The IDD_EX15B_FORM Dialog Template
The IDD_EX15B_FORM dialog template is similar to the Ex15a dialog box shown in Figure 15-1 except that the Enter pushbutton
has been replaced by the Clear pushbutton.
The following IDs identify the controls:
The controls' styles are the same as for the Ex15a program.
Code Requirements
Here's a list of the files and classes in the Ex15b example.

Retrieves the next student record

Inserts a new student record

Deletes the current student record
Control ID
Name edit control IDC_NAME
Grade edit control IDC_GRADE
Clear pushbutton IDC_CLEAR
Header File Source Code File Classes Description
Ex15b.h Ex15b.cpp
CEx15bAppCAboutDlg Application class (from the MFC Application
Wizard) About dialog box
MainFrm.h MainFrm.cpp
CMainFrame SDI main frame
Ex15bDoc.h Ex15bDoc.cpp
Ex15bDoc Student document
Ex15b.h Ex15b.cpp
Ex15bView Student form view (derived from CFormView)
Student.h Student.cpp
Cstudent Student record (similar to Ex15a)
StdAfx.h StdAfx.cpp

Includes the standard precompiled headers
Page 52 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
CEx15bApp
The files Ex15b.cpp and Ex15b.h are the standard MFC Application Wizard output.
CMainFrame
The code for the CMainFrame class in MainFrm.cpp is the standard MFC Application Wizard output.
CStudent
This is the code from Ex15a, except for the following line added at the end of Student.h:
typedef CTypedPtrList<CObList, CStudent*> CStudentList;
CEx15bDoc
The MFC Application Wizard originally generated the CEx15bDoc class. The code used in the Ex15b example is shown here:
Ex15bDoc.h
// Ex15bDoc.h : interface of the CEx15bDoc class
//

#pragma once

#include "student.h"

class CEx15bDoc : public CDocument
{
protected: // create from serialization only
CEx15bDoc();
DECLARE_DYNCREATE(CEx15bDoc)

// Attributes
public:
CStudentList* GetList() {
return &m_studentList;
}

// Operations
public:

// Overrides
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);

// Implementation
public:
virtual ~CEx15bDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()

private:
CStudentList m_studentList;

};
Ex15bDoc.cpp
Note
Use of the MFC template collection classes requires the following statement in StdAfx.h:
#include <afxtempl.h>
Page 53 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// Ex15bDoc.cpp : implementation of the CEx15bDoc class
//

#include "stdafx.h"
#include "Ex15b.h"

#include "Ex15bDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CEx15bDoc

IMPLEMENT_DYNCREATE(CEx15bDoc, CDocument)

BEGIN_MESSAGE_MAP(CEx15bDoc, CDocument)
ON_COMMAND(ID_EDIT_CLEARALL, OnEditClearall)
ON_UPDATE_COMMAND_UI(ID_EDIT_CLEARALL, OnUpdateEditClearall)
END_MESSAGE_MAP()


// CEx15bDoc construction/destruction

CEx15bDoc::CEx15bDoc()
{
TRACE("Entering CEx15bDoc constructor\n");
#ifdef _DEBUG
afxDump.SetDepth(1); // Ensure dump of list elements
#endif // _DEBUG
}

CEx15bDoc::~CEx15bDoc()
{
}

BOOL CEx15bDoc::OnNewDocument()
{
TRACE("Entering CEx15bDoc::OnNewDocument\n");
if (!CDocument::OnNewDocument())
return FALSE;

// TODO: add reinitialization code here
// (SDI documents will reuse this document)

return TRUE;
}

// CEx15bDoc serialization

void CEx15bDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}


// CEx15bDoc diagnostics

#ifdef _DEBUG
void CEx15bDoc::AssertValid() const
{
CDocument::AssertValid();
}

Page 54 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
void CEx15bDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
dc << "\n" << m_studentList << "\n";
}
#endif //_DEBUG


// CEx15bDoc commands

void CEx15bDoc::DeleteContents()
{
#ifdef _DEBUG
Dump(afxDump);
#endif
while (m_studentList.GetHeadPosition()) {
delete m_studentList.RemoveHead();
}
}

void CEx15bDoc::OnEditClearall()
{
DeleteContents();
UpdateAllViews(NULL);
}

void CEx15bDoc::OnUpdateEditClearall(CCmdUI *pCmdUI)
{
pCmdUI->Enable(!m_studentList.IsEmpty());
}
Message Handlers for CEx15bDoc
The Edit Clear All command is handled in the document class. The following message handlers were added through Class View's
Properties window.
Data Members
The document class provides for an embedded CStudentList object, the m_stu-dentList data member, which holds pointers to
CStudent objects. The list object is constructed when the CEx15bDoc object is constructed, and it is destroyed at program exit.
CStudentList is a typedef for a CTypedPtrList for CStudent pointers.
Constructor
The document constructor sets the depth of the dump context so that a dump of the list causes dumps of the individual list elements.
GetList
The inline GetList function helps isolate the view from the document. The document class must be specific to the type of object in
the listin this case, objects of the class CStudent. A generic list view base class, however, can use a member function to get a
pointer to the list without knowing the name of the list object.
DeleteContents
The DeleteContents function is a virtual override function that is called by other document functions and by the application
framework. Its job is to remove all student object pointers from the document's list and to delete those student objects. An important
point to remember here is that SDI document objects are reused after they're closed. DeleteContents also dumps the student list.
Dump
The MFC Application Wizard generates the Dump function skeleton between the lines #ifdef _DEBUG and #endif. Because the
afxDump depth was set to 1 in the document constructor, all the CStudent objects contained in the list are dumped.
CEx15bView
The code for the CEx15bView class is shown in the following code listing.
Object ID Message Member Function
ID_EDIT_CLEARALL COMMAND OnEditClearall
ID_EDIT_CLEARALL ON_UPDATE_COMMAND_UI OnUpdateEditClearall
Page 55 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Ex15bView.h
// Ex15bView.h : interface of the CEx15bView class
//

#pragma once

class CEx15bView : public CFormView
{
protected:
POSITION m_position; // current position in document list
CStudentList* m_pList; // copied from document

protected: // create from serialization only
CEx15bView();
DECLARE_DYNCREATE(CEx15bView)

public:
enum{ IDD = IDD_EX15B_FORM };

// Attributes
public:
CEx15bDoc* GetDocument() const;

// Operations
public:

// Overrides
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual void OnInitialUpdate(); // called first time after construct

// Implementation
public:
virtual ~CEx15bView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:
virtual void ClearEntry();
virtual void InsertEntry(POSITION position);
virtual void GetEntry(POSITION position);

// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnStudentHome();
afx_msg void OnStudentDelete();
afx_msg void OnStudentEnd();
afx_msg void OnStudentInsert();
afx_msg void OnStudentNext();
afx_msg void OnStudentPrevious();
afx_msg void OnUpdateStudentHome(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentDelete(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentEnd(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentNext(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentPrevious(CCmdUI *pCmdUI);
int m_nGrade;
CString m_strName;
protected:
virtual void OnUpdate(Cview* /*pSender/,
LPARAM /*lHint*/, CObject* /*pHint*/)
public:
afx_msg void OnBnClickedClear();
};

#ifndef _DEBUG // debug version in Ex15bView.cpp
Page 56 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
inline CEx15bDoc* CEx15bView::GetDocument() const
{ return reinterpret_cast<CEx15bDoc*>(m_pDocument); }
#endif
Ex15bView.cpp
// Ex15bView.cpp : implementation of the CEx15bView class
//

#include "stdafx.h"
#include "Ex15b.h"

#include "Ex15bDoc.h"
#include "Ex15bView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CEx15bView

IMPLEMENT_DYNCREATE(CEx15bView, CFormView)

BEGIN_MESSAGE_MAP(CEx15bView, CFormView)
ON_COMMAND(ID_STUDENT_HOME, OnStudentHome)
ON_COMMAND(ID_STUDENT_DELETE, OnStudentDelete)
ON_COMMAND(ID_STUDENT_END, OnStudentEnd)
ON_COMMAND(ID_STUDENT_INSERT, OnStudentInsert)
ON_COMMAND(ID_STUDENT_NEXT, OnStudentNext)
ON_COMMAND(ID_STUDENT_PREVIOUS, OnStudentPrevious)
ON_UPDATE_COMMAND_UI(ID_STUDENT_HOME, OnUpdateStudentHome)
ON_UPDATE_COMMAND_UI(ID_STUDENT_DELETE, OnUpdateStudentDelete)
ON_UPDATE_COMMAND_UI(ID_STUDENT_END, OnUpdateStudentEnd)
ON_UPDATE_COMMAND_UI(ID_STUDENT_NEXT, OnUpdateStudentNext)
ON_UPDATE_COMMAND_UI(ID_STUDENT_PREVIOUS, OnUpdateStudentPrevious)
ON_BN_CLICKED(IDC_CLEAR, OnBnClickedClear)
END_MESSAGE_MAP()

// CEx15bView construction/destruction

CEx15bView::CEx15bView()
: CFormView(CEx15bView::IDD)
, m_nGrade(0)
, m_strName(_T(""))
, m_position(NULL)
{
TRACE("Entering CEx15bView constructor\n");
}

CEx15bView::~CEx15bView()
{
}

void CEx15bView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
DDX_Text(pDX, IDC_GRADE, m_nGrade);
DDX_Text(pDX, IDC_NAME, m_strName);
}

BOOL CEx15bView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);
}

void CEx15bView::OnInitialUpdate()
{
TRACE("Entering CEx15bView::OnInitialUpdate\n");
Page 57 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
m_pList = GetDocument()->GetList();
CFormView::OnInitialUpdate();
}


// CEx15bView diagnostics

#ifdef _DEBUG
void CEx15bView::AssertValid() const
{
CFormView::AssertValid();
}

void CEx15bView::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}

CEx15bDoc* CEx15bView::GetDocument() const // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx15bDoc)));
return (CEx15bDoc*)m_pDocument;
}
#endif //_DEBUG


// CEx15bView message handlers

void CEx15bView::OnStudentHome()
{
TRACE("Entering CEx15bView::OnStudentHome\n");
// need to deal with list empty condition
if (!m_pList->IsEmpty()) {
m_position = m_pList->GetHeadPosition();
GetEntry(m_position);
}
}

void CEx15bView::OnUpdateStudentHome(CCmdUI *pCmdUI)
{
// called during idle processing and when Student menu drops down
POSITION pos;

// enables button if list not empty and not at home already
pos = m_pList->GetHeadPosition();
pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}

void CEx15bView::OnStudentDelete()
{
// deletes current entry and positions to next one or head
POSITION pos;
TRACE("Entering CEx15bView::OnStudentDelete\n");
if ((pos = m_position) != NULL) {
m_pList->GetNext(pos);
if (pos == NULL) {
pos = m_pList->GetHeadPosition();
TRACE("GetHeadPos = %ld\n", pos);
if (pos == m_position) {
pos = NULL;
}
}
GetEntry(pos);
CStudent* ps = m_pList->GetAt(m_position);
m_pList->RemoveAt(m_position);
delete ps;
m_position = pos;
GetDocument()->SetModifiedFlag();
GetDocument()->UpdateAllViews(this);
}
}

Page 58 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
void CEx15bView::OnUpdateStudentDelete(CCmdUI *pCmdUI)
{
// called during idle processing and when Student menu drops down
pCmdUI->Enable(m_position != NULL);
}

void CEx15bView::OnStudentEnd()
{
TRACE("Entering CEx15bView::OnStudentEnd\n");
if (!m_pList->IsEmpty()) {
m_position = m_pList->GetTailPosition();
GetEntry(m_position);
}
}

void CEx15bView::OnUpdateStudentEnd(CCmdUI *pCmdUI)
{
// called during idle processing and when Student menu drops down
POSITION pos;

// enables button if list not empty and not at end already
pos = m_pList->GetTailPosition();
pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}

void CEx15bView::OnStudentInsert()
{
TRACE("Entering CEx15bView::OnStudentInsert\n");
InsertEntry(m_position);
GetDocument()->SetModifiedFlag();
GetDocument()->UpdateAllViews(this);
}

void CEx15bView::OnStudentNext()
{
POSITION pos;
TRACE("Entering CEx15bView::OnStudentNext\n");
if ((pos = m_position) != NULL) {
m_pList->GetNext(pos);
if (pos) {
GetEntry(pos);
m_position = pos;
}
}
}

void CEx15bView::OnUpdateStudentNext(CCmdUI *pCmdUI)
{
OnUpdateStudentEnd(pCmdUI);
}

void CEx15bView::OnStudentPrevious()
{
POSITION pos;
TRACE("Entering CEx15bView::OnStudentPrevious\n");
if ((pos = m_position) != NULL) {
m_pList->GetPrev(pos);
if (pos) {
GetEntry(pos);
m_position = pos;
}
}
}

void CEx15bView::OnUpdateStudentPrevious(CCmdUI *pCmdUI)
{
OnUpdateStudentHome(pCmdUI);
}

void CEx15bView::OnUpdate(CView* /*pSender*/,
LPARAM /*lHint*/, CObject* /*pHint*/)
{
Page 59 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// called by OnInitialUpdate and by UpdateAllViews
TRACE("Entering CEx15bView::OnUpdate\n");
m_position = m_pList->GetHeadPosition();
GetEntry(m_position); // initial data for view
}

void CEx15bView::ClearEntry()
{
m_strName = "";
m_nGrade = 0;
UpdateData(FALSE);
((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));
}

void CEx15bView::GetEntry(POSITION position)
{
if (position) {
CStudent* pStudent = m_pList->GetAt(position);
m_strName = pStudent->m_strName;
m_nGrade = pStudent->m_nGrade;
}
else {

ClearEntry();
}
UpdateData(FALSE);
}

void CEx15bView::InsertEntry(POSITION position)
{
if (UpdateData(TRUE)) {
// UpdateData returns FALSE if it detects a user error
CStudent* pStudent = new CStudent;
pStudent->m_strName = m_strName;
pStudent->m_nGrade = m_nGrade;
m_position = m_pList->InsertAfter(m_position, pStudent);
}
}


void CEx15bView::OnBnClickedClear()
{
TRACE("Entering CEx15bView::OnBnClickedClear\n");
ClearEntry();
}
Message Handlers for CEx15bView
Class View's Properties window was used to map the CEx15bView Clear pushbutton notification message as follows:
Because CEx15bView is derived from CFormView, Class View supports the definition of dialog data members. The variables shown
here were added using the Add Member Variable Wizard:
You can use Class View's Properties window to map toolbar button commands to their handlers. Here are the commands and the
handler functions to which they were mapped:
Object ID Message Member Function
IDC_CLEAR BN_CLICKED OnBnClickedClear
Control ID Member Variable Category Variable Type
IDC_GRADE m_nGrade Value int
IDC_NAME m_strName Value CString
Object ID Message Member Function
ID_STUDENT_HOME COMMAND OnStudentHome
ID_STUDENT_END COMMAND OnStudentEnd
Page 60 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Each command handler has built-in error checking.
The following update command user interface message handlers are called during idle processing to update the state of the toolbar
buttons and, when the Student menu is painted, to update the menu commands.
For example, the Following button, which retrieves the first student record, is disabled when the list is empty and when the
m_position variable is already set to the head of the list.

The Previous button is disabled under the same circumstances, so it uses the same update command user interface handler. The
End and the Next buttons share a handler for similar reasons. Because a delay sometimes occurs in calling the update command
user interface functions, the command message handlers must look for error conditions.
Data Members
The m_position data member is a kind of cursor for the document's collection. It contains the position of the CStudent object that is
currently displayed. The m_pList variable provides a quick way to get at the student list in the document.
OnInitialUpdate
The virtual OnInitialUpdate function is called when you start the application. It sets the view's m_pList data member for subsequent
access to the document's list object.
OnUpdate
The virtual OnUpdate function is called both by the OnInitialUpdate function and by the CDocument::UpdateAllViews function. It
resets the list position to the head of the list, and it displays the head entry. In this example, the UpdateAllViews function is called
only in response to the Edit Clear All command. In a multi-view application, you might need a different strategy for setting the
CEx15bView m_position variable in response to document updates from another view.
Protected Virtual Functions
The following three functions are protected virtual functions that deal specifically with CStudent objects: GetEntry, InsertEntry, and
ClearEntry. You can transfer these functions to a derived class if you want to isolate the general-purpose list-handling features in a
base class.
Testing the Ex15b Application
Fill in the student name and grade fields, and then click this button to insert the entry into the list:

Repeat this action several times, using the Clear pushbutton to erase the data from the previous entry. When you exit the
application, the debug output should look similar to this:
a CEx15bDoc at $4116D0
m_strTitle = Untitled
m_strPathName =
m_bModified = 1
m_pDocTemplate = $4113F1

a CObList at $411624
with 4 elements
ID_STUDENT_PREVIOUS COMMAND OnStudentPrevious
ID_STUDENT_NEXT COMMAND OnStudentNext
ID_STUDENT_INSERT COMMAND OnStudentInsert
ID_STUDENT_DELETE COMMAND OnStudentDelete
Object ID Message Member Function
ID_STUDENT_HOME UPDATE_COMMAND_UI OnUpdateStudentHome
ID_STUDENT_END UPDATE_COMMAND_UI OnUpdateStudentEnd
ID_STUDENT_PREVIOUS UPDATE_COMMAND_UI OnUpdateStudentPrevious
ID_STUDENT_NEXT UPDATE_COMMAND_UI OnUpdateStudentNext
ID_STUDENT_DELETE UPDATE_COMMAND_UI OnUpdateCommandDelete
Page 61 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
a CStudent at $412770
m_strName = Fisher, Lon
m_nGrade = 67
a CStudent at $412E80
m_strName = Meyers, Lisa
m_nGrade = 80
a CStudent at $412880
m_strName = Seghers, John
m_nGrade = 92
a CStudent at $4128F0
m_strName = Anderson, Bob
m_nGrade = 87
Two Exercises for the Reader
You might have noticed the absence of a Modify button on the toolbar. Without such a button, you can't modify an existing student
record. Can you add the necessary toolbar button and message handlers? The most difficult task might be designing a graphic for
the button's tile.
Recall that the CEx15bView class is just about ready to be a general-purpose base class. Try separating the CStudent-specific
virtual functions into a derived class. After that, make another derived class that uses a new element class other than CStudent.
Chapter 16: Reading and Writing Documents
Overview
As you've probably noticed, every MFC Application Wizardgenerated program has a File menu with the familiar New, Open, Save,
and Save As commands. In this chapter, you'll learn how to make your application respond to these read and write document
commands.
We'll look at both Single Document Interface (SDI) and Multiple Document Interface (MDI) applications. As you learn about reading
and writing documents, you'll get a heavy but necessary dose of application framework theory; you'll learn a lot about the various
helper classes that have been concealed up to this point. Knowing these details will help you to get the most out of the application
framework.
This chapter includes three examples: an SDI application, an MDI application based on the Ex15b example from the previous
chapter, and a Multiple Top-Level Interface (MTI) application. All these examples use the student list document with a CFormView-
derived view class. The student list can be written to and read from disk through a process called serialization.
What Is Serialization?
In the world of object-oriented programming, objects can be persistent, which means they can be saved on disk when a program
exits and then restored when the program is restarted. This process of saving and restoring objects is called serialization. In the
Microsoft Foundation Class (MFC) library, designated classes have a member function named Serialize. When the application
framework calls Serialize for a particular objectfor example, an object of class CStudentthe data for the student is either saved
on disk or read from disk.
In the MFC library, serialization is not a substitute for a database management system. All the objects associated with a document
are sequentially read from or written to a single disk file. It's not possible to access individual objects at random disk file addresses.
If you need database capability in your application, consider using the database support within MFC and the Active Template Library
(ATL).
Disk Files and Archives
How do you know whether Serialize should read or write data? How is Serialize connected to a disk file? With the MFC library,
objects of class CFile represent disk files. A CFile object encapsulates the binary file handle that you get through the Win32 function
CreateFile. This is not the buffered FILE pointer that you'd get with a call to the C runtime fopen function; rather, it's a handle to a
binary file. The application framework uses this file handle for Win32 ReadFile, WriteFile, and SetFilePointer calls.
If your application does no direct disk I/O but instead relies on the serialization process, you can avoid direct use of CFile objects.
Between the Serialize function and the CFile object is an archive object (of class CArchive), as shown in Figure 16-1.












Page 62 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

Figure 16-1: The serialization process.
The CArchive object buffers data for the CFile object, and it maintains an internal flag that indicates whether the archive is storing
(writing to disk) or loading (reading from disk). Only one active archive is associated with a file at any one time. The application
framework takes care of constructing the CFile and CArchive objects, opening the disk file for the CFile object, and associating the
archive object with the file. All you have to do (in your Serialize function) is load data from or store data in the archive object. The
application framework calls the document's Serialize function during the File Open and File Save processes.
Making a Class Serializable
A serializable class must be derived directly or indirectly from CObject. Also (with some exceptions), the class declaration must
contain the DECLARE_SERIAL macro call, and the class implementation file must contain the IMPLEMENT_SERIAL macro call.
(See the MFC Library Reference for a description of these macros.) This chapter's CStudent class example is modified from the
class in Chapter 15 to include these macros.
Writing a Serialize Function
In Chapter 15, you saw a CStudent class, derived from CObject, with these data members:
public:
CString m_strName;
int m_nGrade;
Now your job is to write a Serialize member function for CStudent. Because Serialize is a virtual member function of class CObject,
you must be sure that the return value and parameter types match the CObject declaration. The Serialize function for the CStudent
class is shown here:
void CStudent::Serialize(CArchive& ar)
{
TRACE("Entering CStudent::Serialize\n");
if (ar.IsStoring()) {
ar << m_strName << m_nGrade;
}
else {
ar >> m_strName >> m_nGrade;
}
}
Most serialization functions call the Serialize functions of their base classes. If CStudent were derived from CPerson, for example,
this would be the first line of the Serialize function:
CPerson::Serialize(ar);
The Serialize function for CObject (and for CDocument, which doesn't override it) doesn't do anything useful, so there's no need to
call it.
Notice that ar is a CArchive reference parameter that identifies the application's archive object. The CArchive::IsStoring member
function tells you whether the archive is currently being used for storing. The CArchive class has overloaded insertion operators (<<)
and extraction operators (>>) for many of the C++ built-in types, as shown in Table 16-1.
Table 16-1: Types Supported by CArchive's Insertion and Extraction Operators
Type Description
BYTE 8 bits, unsigned
WORD 16 bits, unsigned
LONG 32 bits, signed
DWORD 32 bits, unsigned
Page 63 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The insertion operators are overloaded for values; the extraction operators are overloaded for references. Sometimes you must use
a cast to satisfy the compiler. Suppose you have a data member m_nType that is an enumerated type. Here's the code you would
use:
ar << (int) m_nType;
ar >> (int&) m_nType;
MFC classes that are not derived from CObject, such as CString and CRect, have their own overloaded insertion and extraction
operators for CArchive.
Loading from an Archive: Embedded Objects vs. Pointers
Now suppose your CStudent object has other objects embedded in it, and that these objects are not instances of standard classes
such as CString, CSize, and CRect. Let's add a new data member to the CStudent class:
public:
CTranscript m_transcript;
Assume that CTranscript is a custom class, derived from CObject, with its own Serialize member function. There's no overloaded <<
or >> operator for CObject, so the CStudent::Serialize function now looks like this:
void CStudent::Serialize(CArchive& ar)
{
if (ar.IsStoring()) {
ar << m_strName << m_nGrade;
}
else {
ar >> m_strName >> m_nGrade;
}
m_transcript.Serialize(ar);
}
Before the CStudent::Serialize function can be called to load a student record from the archive, a CStudent object must exist
somewhere. The embedded CTranscript object m_transcript is constructed along with the CStudent object before the call to the
CTranscript::Serialize function. When the virtual CTranscript::Serialize function does get called, it can load the archived transcript
data into the embedded m_transcript object. If you're looking for a rule, here it is: Always make a direct call to Serialize for
embedded objects of classes derived from CObject.
Suppose that, instead of an embedded object, your CStudent object contains a CTranscript pointer data member such as this:
public:
CTranscript* m_pTranscript;
You could use the Serialize function, as shown here, but as you can see, you would have to construct a new CTranscript object
yourself:
void CStudent::Serialize(CArchive& ar)
{
if (ar.IsStoring())
ar << m_strName << m_nGrade;
else {
m_pTranscript = new CTranscript;
ar >> m_strName >> m_nGrade;
}
m_pTranscript->Serialize(ar);
}
Because the CArchive insertion and extraction operators are indeed overloaded for CObject pointers, you can write Serialize in this
way instead:
void CStudent::Serialize(CArchive& ar)
{
if (ar.IsStoring())
ar << m_strName << m_nGrade << m_pTranscript;
else
ar >> m_strName >> m_nGrade >> m_pTranscript;
float 32 bits
double 64 bits, IEEE standard
int 32 bits, signed
short 16 bits, signed
char 8 bits, unsigned
unsigned 32 bits, unsigned
Page 64 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
}
But how is the CTranscript object constructed when the data is loaded from the archive? That's where the DECLARE_SERIAL and
IMPLEMENT_SERIAL macros in the CTranscript class come in.
When the CTranscript object is written to the archive, the macros ensure that the class name is written along with the data. When
the archive is read, the class name is read in and an object of the correct class is dynamically constructed, under the control of code
generated by the macros. Once the CTranscript object has been constructed, the overridden Serialize function for CTranscript can
be called to do the work of reading the student data from the disk file. Finally, the CTranscript pointer is stored in the m_pTranscript
data member. To avoid a memory leak, you must be sure that m_pTranscript does not already contain a pointer to a CTranscript
object. If the CStudent object was just constructed and thus was not previously loaded from the archive, the transcript pointer will be
null.
The insertion and extraction operators do not work with embedded objects of classes derived from CObject, as shown here:
ar >> m_strName >> m_nGrade >> &m_transcript; // Don't try this
Serializing Collections
Because all collection classes are derived from the CObject class and the collection class declarations contain the
DECLARE_SERIAL macro call, you can conveniently serialize collections with a call to the collection class's Serialize member
function. If you call Serialize for a CObList collection of CStudent objects, for example, the Serialize function for each CStudent
object will be called in turn. You should, however, remember the following specifics about loading collections from an archive:
If a collection contains pointers to objects of mixed classes (all derived from CObject), the individual class names will be stored
in the archive so that the objects can be properly constructed with the appropriate class constructor.
If a container object, such as a document, contains an embedded collection, loaded data is appended to the existing collection.
You might need to empty the collection before loading from the archive. This is usually done in the document's virtual
DeleteContents function, which is called by the application framework.
When a collection of CObject pointers is loaded from an archive, the following processing steps take place for each object in the
collection:
The object's class is identified.
Heap storage is allocated for the object.
The object's data is loaded into the newly allocated storage.
A pointer to the new object is stored in the collection.
The Ex16a example shows serialization of an embedded collection of CStudent records.
The Serialize Function and the Application Framework
OK, so you know how to write Serialize functions, and you know that these function calls can be nested. But do you know when the
first Serialize function gets called to start the serialization process? With the application framework, everything is keyed to the
document (the object of a class derived from CDocument). When you choose Save or Open from the File menu, the application
framework creates a CArchive object (and an underlying CFile object) and then calls your document class's Serialize function,
passing a reference to the CArchive object. Your derived document class Serialize function then serializes each of its nontemporary
data members.
The SDI Application
You've seen many SDI applications that have one document class and one view class. We'll stick to a single view class in this
chapter, but we'll explore the interrelationships among the application object, the main frame window, the document, the view, the
document template object, and the associated string and menu resources.
The Windows Application Object
For each of your applications, the MFC Application Wizard has been quietly generating a class derived from CWinApp. It has also
been generating a statement such as this:
CMyApp theApp;
What you're seeing here is the mechanism that starts an MFC application. The class CMyApp is derived from the class CWinApp,
Note If you take a close look at any MFC Application Wizard generated document class, you'll notice that the class includes the
DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros rather than the DECLARE_SERIAL and
IMPLEMENT_SERIAL macros. The SERIAL macros are not needed because document objects are never used in
conjunction with the CArchive extraction operator or included in collections; the application framework calls the document's
Serialize member function directly. You should include the DECLARE_SERIAL and IMPLEMENT_SERIAL macros in all
other serializable classes.




Page 65 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
and theApp is a globally declared instance of the class. This global object is called the Windows application object.
Here's a summary of the startup steps in a Microsoft Windows MFC library application:
1. Windows loads your program into memory.
2. The global object theApp is constructed. (All globally declared objects are constructed immediately when the program is
loaded.)
3. Windows calls the global function WinMain, which is part of the MFC library. (WinMain is equivalent to the non-Windows main
functioneach is a main program entry point.)
4. WinMain searches for the one and only instance of a class derived from CWinApp.
5. WinMain calls the InitInstance member function for theApp, which is overridden in your derived application class.
6. Your overridden InitInstance function starts the process of loading a document and displaying the main frame and view
windows.
7. WinMain calls the Run member function for theApp, which starts the processes of dispatching window messages and
command messages.
You can override another important CWinApp member function. The ExitInstance function is called when the application terminates,
after all its windows are closed.
The Document Template Class
If you look at the InitInstance function that the MFC Application Wizard generates for your derived application class, you'll see that
the following statements are featured:
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CEx16aDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CEx16aView));
AddDocTemplate(pDocTemplate);
Unless you start doing fancy things with splitter windows and multiple views, this is the only time you'll actually see a document
template object. In this case, it's an object of class CSingleDocTemplate, which is derived from CDocTemplate. The
CSingleDocTemplate class applies only to SDI applications because SDI applications are limited to one document object.
AddDocTemplate is a member function of class CWinApp.
The AddDocTemplate call, together with the document template constructor call, establishes the relationships among classesthe
application class, the document class, the view window class, and the main frame window class. The application object exists, of
course, before template construction, but the document, view, and frame objects are not constructed at this time. The application
framework dynamically constructs these objects later, when they're needed.
This dynamic construction is a sophisticated use of the C++ language. The DECLARE_DYNCREATE and
IMPLEMENT_DYNCREATE macros in a class declaration and implementation enable the MFC library to construct objects of the
specified class dynamically. If this dynamic construction capability weren't present, more relationships among your application's
classes would have to be hard-coded. Your derived application class, for example, would need code for constructing document,
view, and frame objects of your specific derived classes. This would compromise the object-oriented nature of your program.
With the template system, all that's required in your application class is use of the RUNTIME_CLASS macro. Notice that the target
class's declaration must be included for this macro to work.
Figure 16-2 illustrates the relationships among the various classes, and Figure 16-3 illustrates the object relationships. An SDI
application can have only one template (and associated class groups), and when the SDI program is running, there can be only one
document object and only one main frame window object.
Note Windows allows multiple instances of programs to run. The InitInstance function is called each time a program instance
starts up. In Win32, each instance runs as an independent process. It's only incidental that the same code is mapped to
the virtual memory address space of each process. If you want to locate other running instances of your program, you
must either call the Win32 FindWindow function or set up a shared data section or memory-mapped file for
communication.
Page 66 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

Figure 16-2: Class relationships.

Figure 16-3: Object relationships.
The Document Template Resource
The first AddDocTemplate parameter is IDR_MAINFRAME, the identifier for a string table resource. Here is the corresponding string
that the MFC Application Wizard generates for Ex16a in the application's RC file:
IDR_MAINFRAME
"Ex16a\n" // application window caption
"\n" // root for default document name
// ("Untitled" used if none provided)
"Ex16a\n" // document type name
"Ex16a Files (*.16a)\n" // document type description and filter
".16a\n" // extension for documents of this type
"Ex16a.Document\n" // Registry file type ID
"Ex16a.Document" // Registry file type description
IDR_MAINFRAME specifies one string that is separated into substrings by newline characters (\n). The substrings show up in
various places when the application executes. The string 16a is the default document file extension specified to the MFC Application
Wizard.
The IDR_MAINFRAME ID, in addition to specifying the application's strings, identifies the application's icon, toolbar resources, and
menu. The MFC Application Wizard generates these resources, and you can maintain them using the resource editors.
So now you've seen how the AddDocTemplate call ties all the application elements together. Be aware, though, that no windows
have been created yet and so nothing appears on the screen.
Multiple Views of an SDI Document
Providing multiple views of an SDI document is a little more complicated. You could provide a menu command that allows the user
Note The MFC library dynamic construction capability was designed before the runtime type information (RTTI) feature was
added to the C++ language. The original MFC implementation goes beyond RTTI, and the MFC library continues to use it
for dynamic object construction.
Note
The resource compiler won't accept the string concatenations as shown in this example. If you examine the Ex16a.rc
file, you'll see the substrings combined in one long string.
Page 67 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
to choose a view, or you could allow multiple views in a splitter window. We'll look at both techniques in Chapter 18.
Creating an Empty Document: The CWinApp::OnFileNew Function
After your application class's InitInstance function calls the AddDocTemplate member function, it calls OnFileNew (indirectly through
CWinApp::ProcessShellCommand), another important CWinApp member function. OnFileNew sorts through the web of
interconnected class names and does the following:
1. Constructs the document object but does not attempt to read data from disk.
2. Constructs the main frame object (of class CMainFrame); it also creates the main frame window but does not show it. The
main frame window includes the IDR_MAINFRAME menu, the toolbar, and the status bar.
3. Constructs the view object; it also creates the view window but doesn't show it.
4. Establishes connections among the document, main frame, and view objects. Do not confuse these object connections with
the class connections established by the call to AddDocTemplate.
5. Calls the virtual CDocument::OnNewDocument member function for the document object, which calls the virtual
DeleteContents function.
6. Calls the virtual CView::OnInitialUpdate member function for the view object.
7. Calls the virtual CFrameWnd::ActivateFrame for the frame object to show the main frame window together with the menus,
view window, and control bars.
In an SDI application, the document, main frame, and view objects are created only once, and they last for the life of the program.
The CWinApp::OnFileNew function is called by InitInstance. It's also called in response to the user choosing the File New command.
In this case, OnFileNew must behave a little differently. It can't construct the document, frame, and view objects because they're
already constructed. Instead, it reuses the existing document object and performs steps 5, 6, and 7 above. Notice that OnFileNew
always calls DeleteContents (indirectly) to empty the document.
The Document Class's OnNewDocument Function
You've seen the view class OnInitialUpdate member function and the document class OnNewDocument member function in Chapter
15. If an SDI application didn't reuse the same document object, you wouldn't need OnNewDocument because you could perform all
document initialization in your document class constructor. Now you must override OnNewDocument to initialize your document
object each time the user chooses File New or File Open. The MFC Application Wizard helps you by providing a skeleton function in
the derived document class it generates.
Connecting File Open to Your Serialization Code: The OnFileOpen Function
When the MFC Application Wizard generates an application, it maps the File Open menu command to the CWinApp::OnFileOpen
member function. When called, this function invokes a sequence of functions to accomplish these steps:
1. Prompts the user to select a file.
2. Calls the virtual function CDocument::OnOpenDocument for the already existing document object. This function opens the
file, calls CDocument::DeleteContents, and constructs a CArchive object set for loading. It then calls the document's Serialize
function, which loads data from the archive.
3. Calls the view's OnInitialUpdate function.
The Most Recently Used (MRU) file list is a handy alternative to the File Open command. The application framework tracks the four
most recently used files and displays their names on the File menu. These filenames are stored in the Windows Registry between
program executions.
The Document Class's DeleteContents Function
When you load an existing SDI document object from a disk file, you must somehow erase the existing contents of the document
object. The best way to do this is to override the CDocument::DeleteContents virtual function in your derived document class. The
Note Some of the functions listed here are not called directly by OnFileNew but are called indirectly through the application
framework.
Note It's a good idea to minimize the work you do in constructor functions. The fewer things you do, the less chance there is for
the constructor to failand constructor failures are messy. Functions such as CDocument::OnNewDocument and
CView::OnInitialUpdate are excellent places to do initial housekeeping. If anything fails at creation time, you can display a
message box, and in the case of OnNewDocument, you can return FALSE. Be advised that both functions can be called
more than once for the same object. If you need certain instructions executed only once, declare a "first time" flag data
member and then test/set it appropriately.
Note You can change the number of recent files tracked by supplying a parameter to the LoadStdProfileSettings function in the
application class InitInstance function.
Page 68 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
overridden function, as you've seen in Chapter 15, does whatever is necessary to clean up your document class's data members. In
response to both the File New and File Open menu commands, the CDocument functions OnNewDocument and OnOpenDocument
both call the DeleteContents function, which means DeleteContents is called immediately after the document object is first
constructed. It's called again when you close a document.
If you want your document classes to work in SDI applications, plan on emptying the document's contents in the DeleteContents
member function rather than in the destructor. Use the destructor only to clean up items that last for the life of the object.
Connecting the File Save and File Save As Commands to Your Serialization Code
When the MFC Application Wizard generates an application, it maps the File Save menu command to the OnFileSave member
function of the CDocument class. OnFileSave calls the CDocument function OnSaveDocument, which in turn calls your document's
Serialize function with an archive object set for storing. The File Save As menu command is handled in a similar manner: It is
mapped to the CDocument function OnFileSaveAs, which calls OnSaveDocument. Here the application framework does all the file
management necessary to save a document on disk.
The Document's "Dirty" Flag
Many document-oriented applications for Windows track the user's modifications of a document. If the user tries to close a document
or exit the program, a message box asks whether the user wants to save the document. The MFC application framework directly
supports this behavior with the CDocument data member m_bModified. This Boolean variable is TRUE if the document has been
modified (has become "dirty"); otherwise, it is FALSE.
The protected m_bModified flag is accessed through the CDocument member functions SetModifiedFlag and IsModified. The
framework sets the document object's flag to FALSE when the document is created or read from disk and when it is saved on disk.
The programmer must use the SetModifiedFlag function to set the flag to TRUE when the document data changes. The virtual
function CDocument::SaveModified, which the framework calls when the user closes the document, displays a message box if the
m_bModified flag is set to TRUE. You can override this function if you need to do something else.
In the Ex16a example, you'll see how a one-line update command user interface function can use IsModified to control the state of
the disk button and the corresponding menu command. When the user modifies the file, the disk button is enabled; when the user
saves the file, the button is grayed out.
The Ex16a Example: SDI with Serialization
The Ex16a example is similar to example Ex15b. The dialog template and the toolbar are the same, and the view class is the same.
Serialization has been added, together with an update command user interface function for File Save. The header and
implementation files for the view and document classes will be reused in example Ex16b.
All the new code (code that is different from Ex15b) is listed, with additions and changes to the wizard-generated code shown in
boldface. The files and classes in the Ex16a example are listed in Table 16-2.
Note The File New and File Open menu commands are mapped to application class member functions, but File Save and File
Save As are mapped to document class member functions. File New is mapped to OnFileNew. The SDI version of
InitInstance also calls OnFileNew (indirectly). No document object exists when the application framework calls InitInstance,
so OnFileNew can't possibly be a member function of CDocument. When a document is saved, however, a document
object certainly exists.
Note In one respect, MFC SDI applications behave a little differently from other Windows-based SDI applications such as
Notepad. Here's a typical sequence of events:
1. The user creates a document and saves it on disk (for example, under the name test.dat).
2. The user modifies the document.
3. The user chooses File Open and then specifies test.dat.
When the user chooses File Open, Notepad asks whether the user wants to save the changes made to the document (in
step 2 above). If the user answers no, the program will reread the document from disk. An MFC application, on the other
hand, will assume that the changes are permanent and will not reread the file.




Table 16-2: Files and Classes in Ex16a
Header File Source Code File Class Description
Ex16a.h Ex16a.cpp
CEx16aApp Application class (from the MFC Application
Wizard)

CAboutDlg About dialog box
MainFrm.h MainFrm.cpp
CMainFrame SDI main frame
Ex16aDoc.h Ex16aDoc.cpp
CEx16aDoc Student document
Page 69 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
CStudent
The Ex16a Student.h file is almost the same as the file in the Ex15b project. The header contains the macro
DECLARE_SERIAL(CStudent)
instead of
DECLARE_DYNAMIC(CStudent)
and the implementation file contains the macro
IMPLEMENT_SERIAL(CStudent, CObject, 0)
instead of
IMPLEMENT_DYNAMIC(CStudent, CObject)
The virtual Serialize function (as shown on page 394) has also been added.
CEx16aApp
The application class files shown in the following example contain only code generated by the MFC Application Wizard. The
application was generated with a default file extension and with the Windows Explorer launch and drag-and-drop capabilities. These
features are described later in this chapter.
To generate additional code, when you first run the MFC Application Wizard you must enter the filename extension in the File
Extension box of the wizard's Document Template Strings Page, as shown here:

This ensures that the document template resource string contains the correct default extension and that the correct Windows
Explorerrelated code is inserted into your application class InitInstance member function. You can change some of the other
resource substrings if you want.
Ex16a.h
// Ex16a.h : main header file for the Ex16a application
#pragma once

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h" // main symbols

// CEx16aApp:
// See Ex16a.cpp for the implementation of this class
class CEx16aApp : public CWinApp
{
public:
CEx16aApp();
Ex16aView.h Ex16aView.cpp
CEx16aView Student form view (borrowed from Ex15b)
Student.h Student.cpp
CStudent Student record
StdAfx.h StdAfx.cpp

Precompiled headers (with afxtempl.h
included)
Page 70 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// Overrides
public:
virtual BOOL InitInstance();

// Implementation
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
};
extern CEx16aApp theApp;
Ex16a.cpp
// Ex16a.cpp : Defines the class behaviors for the application.

#include "stdafx.h"
#include "Ex16a.h"
#include "MainFrm.h"
#include "Ex16aDoc.h"
#include "Ex16aView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CEx16aApp

BEGIN_MESSAGE_MAP(CEx16aApp, CWinApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

// CEx16aApp construction

CEx16aApp::CEx16aApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

// The one and only CEx16aApp object
CEx16aApp theApp;

// CEx16aApp initialization

BOOL CEx16aApp::InitInstance()
{
// InitCommonControls() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
InitCommonControls();
CWinApp::InitInstance();

// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need
// Change the registry key under which our settings are stored
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(4); // Load standard INI file
// options (including MRU)
// Register the application's document templates. Document templates
Page 71 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// serve as the connection between documents, frame windows and views
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CEx16aDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CEx16aView));
AddDocTemplate(pDocTemplate);
// Enable DDE Execute open
EnableShellOpen();
RegisterShellFileTypes(TRUE);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line. Will return FALSE if
// app was launched with /RegServer, /Register, /Unregserver
// or /Unregister.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The one and only window has been initialized, so show and update it
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
// call DragAcceptFiles only if there's a suffix
// In an SDI app, this should occur after ProcessShellCommand
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
return TRUE;
}
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
enum { IDD = IDD_ABOUTBOX };

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

// Implementation
protected:
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()

// App command to run the dialog
void CEx16aApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}
// CEx16aApp message handlers
CMainFrame
The main frame window class code, shown in the following example, is almost unchanged from the code that the MFC Application
Wizard generated. The overridden ActivateFrame function and the WM_DROPFILES handler exist solely for trace purposes.
MainFrm.h
Page 72 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// MainFrm.h : interface of the CMainFrame class
#pragma once
class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:
// Operations
public:
// Overrides
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;

// Generated message map functions
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnDropFiles(HDROP hDropInfo);
virtual void ActivateFrame(int nCmdShow = -1);
};
MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class
#include "stdafx.h"
#include "Ex16a.h"
#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMainFrame
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_WM_DROPFILES()
END_MESSAGE_MAP()

static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL
};

// CMainFrame construction/destruction
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
}
CMainFrame::~CMainFrame()
{
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
Page 73 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return TRUE;
}

// CMainFrame diagnostics
#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}
void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}
#endif //_DEBUG
// CMainFrame message handlers
void CMainFrame::OnDropFiles(HDROP hDropInfo)
{
TRACE("Entering CMainFrame::OnDropFiles\n");
CFrameWnd::OnDropFiles(hDropInfo);
}
void CMainFrame::ActivateFrame(int nCmdShow)
{
TRACE("Entering CMainFrame::ActivateFrame\n");
CFrameWnd::ActivateFrame(nCmdShow);
}
The CEx16aDoc Class
The CEx16aDoc class is the same as the CEx15bDoc class from the previous chapter except for four functions: Serialize,
DeleteContents, OnOpenDocument, and OnUpdateFileSave.
Serialize
One line has been added to the MFC Application Wizardgenerated function to serialize the document's student list, as shown here:
///////////////////////////////////////////////////////////////////////
// CEx16aDoc serialization

void CEx16aDoc::Serialize(CArchive& ar)
{
TRACE("Entering CEx16aDoc::Serialize\n");
Page 74 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
m_studentList.Serialize(ar);
}
DeleteContents
The Dump statement is replaced by a simple TRACE statement. Here is the modified code:
void CEx16aDoc::DeleteContents()
{
TRACE("Entering CEx16aDoc::DeleteContents\n");
while (m_studentList.GetHeadPosition()) {
delete m_studentList.RemoveHead();
}
}
OnOpenDocument
This virtual function is overridden only for the purpose of displaying a TRACE message, as shown here:
BOOL CEx16aDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
TRACE("Entering CEx16aDoc::OnOpenDocument\n");
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;

// TODO: Add your specialized creation code here

return TRUE;
}
OnUpdateFileSave
This message map function grays out the File Save toolbar button when the document is in the unmodified state. The view controls
this state by calling the document's SetModifiedFlag function, as shown here:
void CEx16aDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
{
// Disable disk toolbar button if file is not modified
pCmdUI->Enable(IsModified());
}
The CEx16aView Class
The code for the CEx16aView class borrows code from the CEx15bView class in Chapter 15.
Testing the Ex16a Application
Build the program and start it from the debugger, and then test it by typing some data and saving it on disk with the filename
Test.16a. (You don't need to type the .16a part.)
Exit the program, and then restart it and open the file you saved. Did the data you typed come back? Take a look at the Debug
window and observe the sequence of function calls. You should see the trace messages showing the student document being read
and written as you load and save the document.
Windows Explorer Launch and Drag and Drop
In the past, PC users were accustomed to starting up a program and then selecting a disk file (sometimes called a document) that
contained data the program understood. Many MS-DOSbased programs worked this way. The old Windows Program Manager
improved things by allowing the user to double-click on a program icon instead of typing a program name. Meanwhile, Apple
Macintosh users were double-clicking on a document icon; the Macintosh operating system figured out which program to run.
Windows Explorer still lets users double-click on a program, but it also lets users double-click on a document icon to run the
document's program. But how does Windows Explorer know which program to run? Windows Explorer uses the Windows Registry
to make the connection between document and program. The link starts with the filename extension that you typed into the MFC
Application Wizard, but as you'll see, there's more to it than that. Once the association is made, users can launch your program by




Page 75 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
double-clicking on its document icon or by dragging the icon from Windows Explorer to a running instance of your program. In
addition, users can drag the icon to a printer, and your program will print it.
Program Registration
In Chapter 14, you saw how MFC applications store data in the Windows Registry by calling SetRegistryKey from the InitInstance
function. Independent of this SetRegistryKey call, your program can write file association information in a different part of the
Registry on startup. To activate this feature, you must type in the filename extension when you create the application with the MFC
Application Wizard. After you do that, the MFC Application Wizard adds the extension as a substring in your template string and
adds the following line in your InitInstance function:
RegisterShellFileTypes(TRUE);
Now your program adds two items to the Registry. Under the HKEY_CLASSES_ROOT top-level key, it adds a subkey and a data
string as shown here (for the Ex16a example):
.16a = Ex16a.Document
The data item is the file type ID that the MFC Application Wizard has chosen for you. Ex16a.Document, in turn, is the key for finding
the program itself. The Registry entries for Ex16a.Document, also beneath HKEY_CLASSES_ROOT, are shown here.

The Registry contains the full pathname of the Ex16a program. Now Windows Explorer can use the Registry to navigate from the
extension to the file type ID to the actual program itself. After the extension is registered, Windows Explorer will find the document's
icon and display it next to the filename.
Double-Clicking on a Document
When the user double-clicks on a document icon, Windows Explorer executes the associated SDI program, passing in the selected
filename on the command line. You might notice that the MFC Application Wizard generates a call to EnableShellOpen in the
application class InitInstance function. This supports execution via DDE message, the technique used by the File Manager in
Windows NT 3.51. Windows Explorer can launch your SDI application without this call.
Enabling Drag and Drop
If you want your already-running program to open files dragged from Windows Explorer, you must call the CWnd function
DragAcceptFiles for the application's main frame window. The application object's public data member m_pMainWnd points to the
CFrameWnd (or CMDIFrameWnd) object. When the user drops a file anywhere inside the frame window, the window receives a
WM_DROPFILES message, which triggers a call to FrameWnd::OnDropFiles. The following line in InitInstance, generated by the
MFC Application Wizard, enables drag and drop:
m_pMainWnd->DragAcceptFiles();
Program Startup Parameters
When you choose Run from the Start menu or when you double-click the program directly in Windows Explorer, there is no
command-line parameter. The InitInstance function processes the command line with calls to ParseCommandLine and
ProcessShellCommand. If the command line contains something that looks like a filename, the program immediately loads that file.
Thus, you create a Windows shortcut that can run your program with a specific document file.
Experimenting with Explorer Launch and Drag and Drop
Once you've built Ex16a, you can try running it from Windows Explorer. You must execute the program directly, however, in order to
write the initial entries in the Registry. Be sure that you've saved at least one 16A file to disk, and then exit Ex16a. Start Windows
Explorer and locate the directory in which you saved 16A files. Double-click on one of the 16A files in the panel on the right. Your
program should start with the selected file loaded. Now, with both Ex16a and Windows Explorer open on the desktop, try dragging
another file from Windows Explorer to the Ex16a window. The program should open the new file just as if you had chosen File Open
from the Ex16a menu.
You might also want to look at the Ex16a entries in the Registry. Run the Regedit program (possibly named Regedt32 in Windows
2000 and Windows XP), and expand the HKEY_CLASSES_ROOT key. Look under .16a and Ex16a.Document. Also expand the
HKEY_CURRENT_USER key, and look at Local AppWizard-Generated Applications under Software. You should see a Recent File
List under the subkey Ex16a. The Ex16a program calls SetRegistryKey with the string Local AppWizard-Generated Applications, so
the program name goes beneath the Ex16a subkey.


Page 76 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
MDI Support
In addition to SDI applications, MFC supports MDI applications. In this section, we'll look at MDI applications and see how they read
and write their document files. For a long time, MDI applications were the preferred MFC library program style. It's the MFC
Application Wizard default, and most of the sample programs that come with Visual C++ are MDI applications.
In addition, you'll learn the similarities and differences between SDI and MDI applications, and you'll learn how to convert an SDI
application to an MDI application. Be sure that you thoroughly understand the SDI application described earlier in this chapter before
you attack the MDI application in this section.
Before you look at the MFC library code for MDI applications, you should be familiar with the operation of Windows MDI programs.
Take a close look at Visual C++ .NET now. It's an MDI application whose "multiple documents" are program source code files.
Visual C++ .NET is not the most typical MDI application, however, because it collects its documents into projects. It's better to
examine Microsoft Word or, better yet, a real MFC library MDI applicationthe kind that the MFC Application Wizard generates.
A Typical MDI Application, MFC Style
Ex16b is an MDI version of Ex16a. Figure 16-4 shows the Ex16b program in use.

Figure 16-4: The Ex16b application with two files open.
The user has two separate document files open, each in a separate MDI child window. Only one child window is active at a time.
The application has only one menu and one toolbar, and all commands are routed to the active child window. The main window's
title bar reflects the name of the active child window's document file.
The child window's minimize box allows the user to reduce the child window to an icon in the main window. The application's
Window menu (shown in Figure 16-4) lets the user control the presentation through the following commands.
The menus and toolbars in an MDI application are dynamic. When all the windows in an MDI application are closed, the File menu
changes, most toolbar buttons are disabled, and the window caption does not show a filename. The only choice the user has is to
start a new document or to open an existing document from disk.
As the user creates new files, the empty child window gets the default document name Ex16b1. This name is based on the Doc
Type Name (Ex16b) selected in the Document Template Strings page of the MFC Application Wizard. The first new file is Ex16b1,
the second is Ex16b2, and so forth. The user normally chooses a different name when saving the document.
An MFC library MDI application, like many commercial MDI applications, starts up with a new, empty document. (Visual C++ .NET is
an exception.) If you want your application to start up with a blank frame, you can modify the argument to the
ProcessShellCommand call in the application class file, as shown in example Ex16b.
The MDI Application Object
You're probably wondering how an MDI application works and what code makes it different from an SDI application. Actually, the


Menu Command Action
New Window Opens as an additional child window for the selected document
Cascade Arranges the existing windows in an overlapped pattern
Tile Arranges the existing windows in a nonoverlapped, tiled pattern
Arrange Icons Arranges minimized windows in the frame window
(document names) Selects the corresponding child window and brings it to the top
Page 77 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
startup sequences are pretty much the same. An application object of a class derived from class CWinApp has an overridden
InitInstance member function. This InitInstance function is somewhat different from the SDI InitInstance function, starting with the
call to AddDocTemplate.
The MDI Document Template Class
The MDI template construction call in InitInstance looks like this:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_EX16BTYPE,
RUNTIME_CLASS(CEx16bDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CEx16b));
AddDocTemplate(pDocTemplate);
Unlike Ex16a, an MDI application can use multiple document types and allows the simultaneous existence of more than one
document object. This is the essence of the MDI application.
The single AddDocTemplate call shown in the previous example permits the MDI application to support multiple child windows, each
connected to a document object and a view object. It's also possible to have several child windows (and corresponding view objects)
connected to the same document object. In this chapter, we'll start with only one view class and one document class. You'll see
multiple view classes and multiple document classes in Chapter 18.
The MDI Frame Window and the MDI Child Window
The SDI examples had only one frame window class and only one frame window object. For SDI applications, the MFC Application
Wizard generated a class named CMainFrame, which was derived from the class CFrameWnd. An MDI application has two frame
window classes and many frame objects, as shown in the following table. The MDI frameview window relationship is shown in
Figure 16-5.

Figure 16-5: The MDI frameview window relationship.
In an SDI application, the CMainFrame object frames the application and contains the view object. In an MDI application, the two
roles are separated. Now the CMainFrame object is explicitly constructed in InitInstance, and the CChildFrame object contains the
view. The MFC Application Wizard generates the following code:
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
Note When your application is running, the document template object maintains a list of active document objects that were
created from the template. The CMultiDocTemplate member functions GetFirstDocPosition and GetNextDoc allow you to
iterate through the list. Use CDocument::GetDocTemplate to navigate from a document to its template.
Base Class MFC Application
Wizard
Generated Class
Number of
Objects
Menu and
Control Bars
Contains a
View
Object
Constructed
CMDIFrameWnd CMainFrame 1 only Yes No In application
class's
InitInstance
function
CMDIChildWnd CChildFrame 1 per child
window
No Yes By application
framework when
a new child
window is
opened
Page 78 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
m_pMainWnd = pMainFrame;

pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
The application framework can create the CChildFrame objects dynamically because the CChildFrame runtime class pointer is
passed to the CMultiDocTemplate constructor.
The Main Frame and Document Template Resources
An MDI application (such as Ex16b, described later in this chapter) has two separate string and menu resources, identified by the
IDR_MAINFRAME and IDR_EX16BTYPE constants. The first resource set goes with the empty main frame window; the second set
goes with the occupied main frame window. Here are the two string resources with substrings broken out:
IDR_MAINFRAME
"Ex16b" // application window caption

IDR_EX16BTYPE
"\n" // (not used)
"Ex16b\n" // root for default document name
"Ex16b\n" // document type name
"Ex16b Files (*.16b)\n" // document type description and filter
".16b\n" // extension for documents of this type
"Ex16b.Document\n" // Registry file type ID
"Ex16b.Document" // Registry file type description
The application window caption comes from the IDR_MAINFRAME string. When a document is open, the document filename is
appended. The last two substrings in the IDR_EX16BTYPE string support embedded launch and drag and drop.
Creating an Empty Document
The CWinApp::OnFileNew function enables you to create an empty document. The MDI InitInstance function calls OnFileNew
(through ProcessShellCommand), as did the SDI InitInstance function. This time, however, the main frame window has already been
created. OnFileNew, through a call to the CMultiDocTemplate function OpenDocumentFile, now does the following:
1. Constructs a document object but does not attempt to read data from disk.
2. Constructs a child frame window object (of class CChildFrame). Also creates the child frame window but does not show it. In
the main frame window, the IDR_EX16BTYPE menu replaces the IDR_MAINFRAME menu. IDR_EX16BTYPE also identifies
an icon resource that is used when the child window is minimized within the frame.
3. Constructs a view object. Also creates the view window but does not show it.
4. Establishes connections among the document, the main frame, and view objects. Do not confuse these object connections
with the class associations established by the call to AddDocTemplate.
5. Calls the virtual OnNewDocument member function for the document object.
6. Calls the virtual OnInitialUpdate member function for the view object.
7. Calls the virtual ActivateFrame member function for the child frame object to show the frame window and the view window.
The OnFileNew function is also called in response to the File New menu command. In an MDI application, OnFileNew performs
exactly the same steps as it does when called from InitInstance.
Creating an Additional View for an Existing Document
If you choose the New Window command from the Window menu, the application framework opens a new child window that is
linked to the currently selected document. The associated CMDIFrameWnd function, OnWindowNew, does the following:
1. Constructs a child frame object (of class CChildFrame). Also creates the child frame window but does not show it.
2. Constructs a view object. Also creates the view window but does not show it.
3. Establishes connections between the new view object and the existing document and main frame objects.
Note The MDI InitInstance function sets the CWinApp data member m_pMainWnd to point to the application's main frame
window. This means you can access m_pMainWnd through the global AfxGetApp function anytime you need to get your
application's main frame window.
Note
The resource compiler won't accept the string concatenations as shown here. If you examine the Ex16b.rc file, you'll
see the substrings combined in one long string.
Note Some functions listed above are not called directly by OpenDocumentFile but are called indirectly through the application
framework.
Page 79 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
4. Calls the virtual OnInitialUpdate member function for the view object.
5. Calls the virtual ActivateFrame member function for the child frame object to show the frame window and the view window.
Loading and Storing Documents
In MDI applications, documents are loaded and stored the same way as in SDI applications but with two important differences: A
new document object is constructed each time a document file is loaded from disk, and the document object is destroyed when the
child window is closed. Don't worry about clearing a document's contents before loadingbut override the
CDocument::DeleteContents function anyway to make the class portable to the SDI environment.
Multiple Document Templates
An MDI application can support multiple document templates through multiple calls to the AddDocTemplate function. Each template
can specify a different combination of document, view, and MDI child frame classes. When the user chooses New from the File
menu of an application with multiple templates, the application framework displays a list box that allows the user to select a template
by name as specified in the string resource (document type substring). Multiple AddDocTemplate calls are not supported in SDI
applications because the document, view, and frame objects are constructed once for the life of the application.
If you don't want the template list box, you can edit the File menu to add a New menu command for each document type. Code the
command message handlers as shown here, using the document type substring from each template:
void CMyApp::OnFileNewStudent()
{
OpenNewDocument("Studnt");
}
void CMyApp::OnFileNewTeacher()
{
OpenNewDocument("Teachr");
}
Then add the OpenNewDocument helper function as follows:
BOOL CMyApp::OpenNewDocument(const CString& strTarget)
{
CString strDocName;
CDocTemplate* pSelectedTemplate;
POSITION pos = GetFirstDocTemplatePosition();
while (pos != NULL) {
pSelectedTemplate = (CDocTemplate*) GetNextDocTemplate(pos);
ASSERT(pSelectedTemplate != NULL);
ASSERT(pSelectedTemplate->IsKindOf(
RUNTIME_CLASS(CDocTemplate)));
pSelectedTemplate->GetDocString(strDocName,
CDocTemplate::docName);
if (strDocName == strTarget) { // from template's
// string resource
pSelectedTemplate->OpenDocumentFile(NULL);
return TRUE;
}
}
return FALSE;
}
Explorer Launch and Drag and Drop
When you double-click on a document icon for an MDI application in Windows Explorer, the application launches only if it was not
running already; otherwise, a new child window opens in the running application for the document you selected. The
EnableShellOpen call in the application class InitInstance function is necessary for this to work. Drag and drop works much the
same way in an MDI application as it does in an SDI application. If you drag a file from Windows Explorer to your MDI main frame
window, the program opens a new child frame (with associated document and view) just as if you'd chosen the File Open command.
As with SDI applications, you must use the Document Template Strings page of the MFC Application Wizard to specify the filename
extension.
The Ex16b Example: An MDI Application
Note When your application is running, the application object keeps a list of active document template objects. The CWinApp
member functions GetFirstDocTemplatePosition and GetNextDocTemplate allow you to iterate through the list of
templates. These functions, together with the CDocTemplate member functions GetFirstDocPosition and GetNextDoc,
allow you to access all of the application's document objects.




Page 80 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
This example is the MDI version of the Ex16a example. It uses the same document and view class code and the same resources
(except the program name). The application code and main frame class code are different, however. All the new code is listed here,
including the code that the MFC Application Wizard generates. A list of the files and classes in the Ex16b example are shown in
Table 16-3.
CEx16bApp
In the CEx16bApp source code listing, the OpenDocumentFile member function is overridden only for the purpose of inserting a
TRACE statement. Also, a few lines have been added before the ProcessShellCommand call in InitInstance. They check the
argument to ProcessShellCommand and change it if necessary to prevent the creation of any empty document window on startup.
The following shows the source code:
Ex16b.h
// Ex16b.h : main header file for the Ex16b application
//
#pragma once

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h" // main symbols

// CEx16bApp:
// See Ex16b.cpp for the implementation of this class
//

class CEx16bApp : public CWinApp
{
public:
CEx16bApp();
// Overrides
public:
virtual BOOL InitInstance();

// Implementation
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName);
};
extern CEx16bApp theApp;
Ex16b.cpp
// Ex16b.cpp : Defines the class behaviors for the application.
//
#include "stdafx.h"
#include "Ex16b.h"
#include "MainFrm.h"
Table 16-3: Files and Classes in Ex16b
Header File Source Code File Class Description
Ex16b.h Ex16b.cpp
CEx16bApp Application class (from the MFC
Application Wizard)

CAboutDlg About dialog box
MainFrm.h MainFrm.cpp
CMainFrame MDI main frame
ChildFrm.h ChildFrm.cpp
CChildFrame MDI child frame
C Ex16bDoc.h C Ex16bDoc.cpp
CEx16bDoc Student document (borrowed from
Ex16a)
C Ex16bView.h Ex16bView.cpp
CEx16bView Student form view (borrowed from
Ex16a)
Student.h Student.cpp
CStudent Student record (from Ex16a)
StdAfx.h StdAfx.cpp

Precompiled headers (with afxtempl.h
included)
Page 81 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
#include "ChildFrm.h"
#include "Ex16bDoc.h"
#include "Ex16bView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CEx16bApp

BEGIN_MESSAGE_MAP(CEx16bApp, CWinApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

// CEx16bApp construction

CEx16bApp::CEx16bApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

// The one and only CEx16bApp object
CEx16bApp theApp;

// CEx16bApp initialization
BOOL CEx16bApp::InitInstance()
{
// InitCommonControls() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
InitCommonControls();
CWinApp::InitInstance();

// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need
// Change the registry key under which our settings are stored
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
// Load standard INI file options (including MRU)
LoadStdProfileSettings(4);
// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_Ex16bTYPE,
RUNTIME_CLASS(CEx16bDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CEx16bView));
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// call DragAcceptFiles only if there's a suffix
// In an MDI app, this should occur immediately after setting m_pMainWnd
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
Page 82 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// Enable DDE Execute open
EnableShellOpen();
RegisterShellFileTypes(TRUE);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// no empty document window on startup
if(cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew) {
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
}
// Dispatch commands specified on the command line. Will return FALSE
// if app was launched with /RegServer, /Register, /Unregserver
// or /Unregister.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}

// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
enum { IDD = IDD_ABOUTBOX };

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

// Implementation
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()

// App command to run the dialog
void CEx16bApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}

// CEx16bApp message handlers
CDocument* CEx16bApp::OpenDocumentFile(LPCTSTR lpszFileName)
{
TRACE("CEx16bApp::OpenDocumentFile\n");
return CWinApp::OpenDocumentFile(lpszFileName);
}
CMainFrame
This main frame class, as shown in the following code listings, is almost identical to the SDI version, except that it's derived from
CMDIFrameWnd instead of CFrameWnd.
MainFrm.h
// MainFrm.h : interface of the CMainFrame class
//
#pragma once
Page 83 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
class CMainFrame : public CMDIFrameWnd
{
DECLARE_DYNAMIC(CMainFrame)
public:
CMainFrame();
// Attributes
public:
// Operations
public:
// Overrides
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
// Generated message map functions
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class
//
#include "stdafx.h"
#include "Ex16b.h"
#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CMainFrame
IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()

static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL
};

// CMainFrame construction/destruction
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
}
CMainFrame::~CMainFrame()
{
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY
Page 84 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
| CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs

return TRUE;
}
// CMainFrame diagnostics
#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CMDIFrameWnd::AssertValid();
}
void CMainFrame::Dump(CDumpContext& dc) const
{
CMDIFrameWnd::Dump(dc);
}
#endif //_DEBUG
// CMainFrame message handlers
CChildFrame
This child frame class, shown in the following code listings, lets you conveniently control the child frame window's characteristics by
adding code in the PreCreateWindow function. You can also map messages and override other virtual functions.
ChildFrm.h
// ChildFrm.h : interface of the CChildFrame class
//
#pragma once

class CChildFrame : public CMDIChildWnd
{
DECLARE_DYNCREATE(CChildFrame)
public:
CChildFrame();
// Attributes
public:
// Operations
public:
// Overrides
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// Implementation
public:
virtual ~CChildFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
Page 85 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
#endif

// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
public:
virtual void ActivateFrame(int nCmdShow = -1);
};
ChildFrm.cpp
// ChildFrm.cpp : implementation of the CChildFrame class
//
#include "stdafx.h"
#include "Ex16b.h"
#include "ChildFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CChildFrame
IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd)
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
END_MESSAGE_MAP()

// CChildFrame construction/destruction
CChildFrame::CChildFrame()
{
// TODO: add member initialization code here
}
CChildFrame::~CChildFrame()
{
}
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIChildWnd::PreCreateWindow(cs) )
return FALSE;
return TRUE;
}
// CChildFrame diagnostics
#ifdef _DEBUG
void CChildFrame::AssertValid() const
{
CMDIChildWnd::AssertValid();
}
void CChildFrame::Dump(CDumpContext& dc) const
{
CMDIChildWnd::Dump(dc);
}
#endif //_DEBUG

// CChildFrame message handlers
void CChildFrame::ActivateFrame(int nCmdShow)
{
TRACE("Entering CChildFrame::ActivateFrame\n");
CMDIChildWnd::ActivateFrame(nCmdShow);
}
Testing the Ex16b Application
Do the build, run the program from Visual C++ .NET, and then make several documents. Try saving the documents on disk, closing
them, and reloading them. Also, choose New Window from the Window menu. Notice that you now have two views (and child
frames) attached to the same document. Now exit the program and start Windows Explorer. The files you created should show up
with document icons. Double-click on a document icon and see whether the Ex16b program starts up. Now, with both Windows
Explorer and Ex16b on the desktop, drag a document from Windows Explorer to Ex16b. Was the file opened?
MTI Support



Page 86 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Windows 2000 introduced a third type of application into its repertoire: the Multiple Top-Level Interface (MTI) application. This is the
type of interface favored by Microsoft Office 2000 and Office XP applications. MTI applications are similar to SDI applications, but
whereas SDI applications run as separate windowsone instance of the application for every window openMTI applications have
one instance serving all the open windows. When the user creates a new file, the application opens a new independent top-level
window and a new document along with thembut they're tied to the same running instance of the application.
The Ex16c Example: An MTI Application
This example is an MTI version of the Ex16a we looked at in a previous section. To create this example, in the MFC Application
Wizard select Multiple Top-Level Documents on the Application Type page and deselect Printing And Print Preview on the
Advanced Features page. On the Generated Classes page, change the view's base class to CFormView.
Ex16c uses the same document and view class code and the same resources (except the resource name). The application code
and main frame class code are different, however. You can examine all the new code in the Ex16c application on the companion
CD. A list of files and classes in the Ex16c example are shown in Table 16-4.
Unlike the MDI and SDI applications, the MTI application includes a New Frame command on the File menu. This command tells the
application to open a new top-level window. The following listing illustrates handling the New Frame command:
void CEx16cApp::OnFileNewFrame()
{
ASSERT(m_pDocTemplate != NULL);
CDocument* pDoc = NULL;
CFrameWnd* pFrame = NULL;

// Create a new instance of the document referenced
// by the m_pDocTemplate member.
pDoc = m_pDocTemplate->CreateNewDocument();
if (pDoc != NULL)
{
// If creation worked, use create a new frame for
// that document.
pFrame = m_pDocTemplate->CreateNewFrame(pDoc, NULL);
if (pFrame != NULL)
{
// Set the title, and initialize the document.
// If document initialization fails, clean-up
// the frame window and document.

m_pDocTemplate->SetDefaultTitle(pDoc);
if (!pDoc->OnNewDocument())
{
pFrame->DestroyWindow();
pFrame = NULL;
}
else
{
// Otherwise, update the frame
m_pDocTemplate->InitialUpdateFrame(pFrame, pDoc, TRUE);
}
}
}

// If we failed, clean up the document and show a




Table 16-4: Files and Classes in Ex16c
Header File Source Code File Class Description
Ex16c.h Ex16c.cpp
CEx16cApp Application class (from the MFC Application
Wizard)

CAboutDlg About dialog box
MainFrm.h MainFrm.cpp
CMainFrame MTI main frame
C Ex16cDoc.h C Ex16cDoc.cpp
CEx16cDoc Student document (borrowed from Ex16a)
C Ex16cView.h Ex16cView.cpp
CEx16cView Student form view (borrowed from Ex16a)
Student.h Student.cpp
CStudent Student record (from Ex16a)
StdAfx.h StdAfx.cpp

Precompiled headers (with afxtempl.h included)
Page 87 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
// message to the user.
if (pFrame == NULL || pDoc == NULL)
{
delete pDoc;
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
}
}
MTI applications use the CMultiDocTemplate class to manage the document, the frame, and the view. Notice that OnFileNewFrame
creates a new document and then a new top-level frame window instead of depending on the framework to create the document,
frame, and view classes. Otherwise, MTI applications manage their documents and views in the same way that SDI and MDI
applications do.
Testing the Ex16c Application
To test the Ex16c application, run the application and choose New Frame from the File menu. Notice that a new frame opens up
near the existing frame. The new top-level frame includes a new instance of the document, but the document is associated with the
new frame (rather than with a new MDI child frame, as in Ex16b).
Chapter 17: Printing and Print Preview
Overview
If you're depending on the Win32 API alone, printing will be one of the tougher programming jobs you'll face. The Microsoft
Foundation Class (MFC) library application framework goes a long way toward making printing easier, and it adds a print preview
capability that behaves like the print preview functions in commercial Microsoft Windowsbased programs such as Microsoft Word
and Microsoft Excel.
In this chapter, you'll learn how to use the MFC library print and print preview features. In the process, you'll get a feel for what's
involved in Windows-based printing and how it's different from printing in MS-DOS. First, we'll do some WYSIWYG printing in which
the printer output matches the screen display. This option requires careful use of mapping modes. Then we'll print a paginated data
processingstyle report that doesn't reflect the screen display at all. In that example, we'll use a template array to structure our
document so the program can print any specified range of pages on demand.
Windows-Based Printing
In the old days, programmers had to worry about configuring their applications for dozens of printers. Windows makes life easier
because it provides all of the printer drivers you'll ever need. It also supplies a consistent user interface for printing.
Standard Printer Dialog Boxes
When the user chooses Print from the File menu of a Windows-based application, the standard Print dialog box appears, as shown
in Figure 17-1.

Figure 17-1: The standard Print dialog box.
If the user clicks the Properties button in the Print dialog box, the Document Properties dialog box appears, as shown in Figure 17-2.








Page 88 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

Figure 17-2: The Document Properties dialog box.
During the printing process, the application displays a standard printer status dialog box.
Interactive Print Page Selection
If you've worked in the data processing field, you might be used to batch-mode printing. A program reads a record and then formats
and prints selected information as a line in a report. Let's say, for example, that every time 50 lines have been printed the program
ejects the paper and prints a new page heading. The programmer assumes that the whole report will be printed at one time and
therefore makes no allowance for interactively printing selected pages.
As Figure 17-1 shows, page numbers are important in Windows-based printing. A program must respond to a user's page selection
by calculating which information to print and then printing the selected pages. If you're aware of this page selection requirement, you
can design your application's data structures accordingly.
Remember the student list from Chapter 16? Let's say the list includes 1000 student names and the user wants to print page 5 of a
student report. If each student record requires one print line and a page holds 50 lines, page 5 will include records 201 through 250.
With an MFC list collection class, you're stuck iterating through the first 200 list elements before you can start printing. Maybe the list
isn't the ideal data structure. How about an array collection instead? With the CObArray class (or one of the template array classes),
you can directly access the 201st student record.
Not every application has elements that map to a fixed number of print lines. Suppose the student record contains a multi-line text
biography field. You can't know how many biography lines each record includes, so you have to search through the entire file to
determine the page breaks. If your program can remember those page breaks as it calculates them, its efficiency will increase.
Display Pages vs. Printed Pages
In many cases, you'll want a printed page to correspond to a display page. You cannot guarantee that objects will be printed exactly
as they're displayed on screen, but with TrueType fonts, your printed page will be pretty close. If you're working with full-size paper
and you want the corresponding display to be readable, you'll certainly want a display window that's larger than the screen. Thus, a
scrolling view such as the one that the CScrollView class provides is ideal for your printable views.
At other times, you might not care about display pages. Perhaps your view holds its data in a list box, or maybe you don't need to
display the data at all. In these cases, your program can contain stand-alone print logic that simply extracts data from the document
and sends it to the printer. Of course, the program must properly respond to a user's page-range request. If you query the printer to
determine the paper size and orientation (portrait or landscape), you can adjust the pagination accordingly.
Print Preview
The MFC library print preview feature shows you on screen the exact page and line breaks you'll get when you print your document
on a selected printer. The fonts might look a little funny, especially in the smaller sizes, but that's not a problem. (Look at the Print
Preview window that appears on page 455.)
Print preview is an MFC library feature, not a Windows feature. Don't underestimate how much effort went into programming print
preview. (Just look at the source code.) The print preview program examines each character individually, determining its position
based on the printer's device context. After selecting an approximating font, the program displays the character in the Print Preview
window at the proper location.
Programming for the Printer
The application framework does most of the work for printing and print preview. To use the printer effectively, you must understand
the sequence of function calls and know which functions to override.








Page 89 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The Printer Device Context and the CView::OnDraw Function
When your program prints on the printer, it uses a device context object of class CDC. Don't worry about where the object comes
from; the application framework constructs it and passes it as a parameter to your view's OnDraw function. If your application uses
the printer to duplicate the display, the OnDraw function can do double duty. If you're displaying, the OnPaint function calls OnDraw
and the device context is the display context. If you're printing, OnDraw is called by another CView virtual function, OnPrint, with a
printer device context as a parameter. The OnPrint function is called once to print an entire page.
In print preview mode, the OnDraw parameter is actually a pointer to a CPreviewDC object. Your OnPrint and OnDraw functions
work the same regardless of whether you're printing or previewing.
The CView::OnPrint Function
You've seen that the base class OnPrint function calls OnDraw and that OnDraw can use both a display device context and a printer
device context. The mapping mode should be set before OnPrint is called. You can override OnPrint to print items that you don't
need on the display, such as a title page, headers, and footers. The OnPrint parameters are a pointer to the device context and a
pointer to a print information object (CPrintInfo) that includes page dimensions, the current page number, and the maximum page
number.
In your overridden OnPrint function, you can elect not to call OnDraw at all to support print logic that is totally independent of the
display logic. The application framework calls the OnPrint function once for each page to be printed, with the current page number in
the CPrintInfo structure. You'll find out shortly how the application framework determines the page number.
Preparing the Device Context: The CView::OnPrepareDC Function
If you need a display mapping mode other than MM_TEXT (and you often will), you'll usually set it in the view's OnPrepareDC
function. You must override this function yourself if your view class is derived directly from CView, but it's already overridden if your
view is derived from CScrollView. The OnPrepareDC function is called in OnPaint immediately before the call to OnDraw. If you're
printing, the same OnPrepareDC function is called, this time immediately before the application framework calls OnPrint. Thus, the
mapping mode is set before both the painting of the view and the printing of a page.
The second parameter of the OnPrepareDC function is a pointer to a CPrintInfo structure. This pointer is valid only if OnPrepareDC
is being called before printing. You can test for this condition by calling the CDC member function IsPrinting. The IsPrinting function
is particularly handy if you're using OnPrepareDC to set different mapping modes for the display and the printer.
If you do not know in advance how many pages your print job will require, your overridden OnPrepareDC function can detect the
end of the document and reset the m_bContinuePrinting flag in the CPrintInfo structure. When this flag is FALSE, the OnPrint
function won't be called again and control will pass to the end of the print loop.
The Start and End of a Print Job
When a print job starts, the application framework calls two CView functions, OnPreparePrinting and OnBeginPrinting. (The MFC
Application Wizard generates the OnPreparePrinting, OnBeginPrinting, and OnEndPrinting functions for you if you select the
Printing And Print Preview option.) The first function, OnPreparePrinting, is called before the display of the Print dialog box. If you
know the first and last page numbers, call CPrintInfo::SetMinPage and CPrintInfo::SetMaxPage in OnPreparePrinting. The page
numbers you pass to these functions will appear in the Print dialog box for the user to override.
The second function, OnBeginPrinting, is called after the Print dialog box closes. You override this function to create Graphics
Device Interface (GDI) objects, such as fonts, that you need for the entire print job. A program runs faster if you create a font once
instead of re-creating it for each page.
The CView function OnEndPrinting is called at the end of the print job, after the last page has been printed. You override this
function to get rid of GDI objects created in OnBeginPrinting.
Table 17-1 lists the important overridable CView print loop functions.
Table 17-1: Overridable CView Print Loop Functions
Function Common Override Behavior
OnPreparePrinting Sets first and last page numbers
OnBeginPrinting Creates GDI objects
OnPrepareDC (for each page) Sets mapping mode and optionally detects end of print job
OnPrint (for each page) Does print-specific output and then calls OnDraw
OnEndPrinting Deletes GDI objects




Page 90 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The Ex17a Example: A WYSIWYG Print Program
This example displays and prints a single page of text stored in a document. The printed image should match the displayed image.
The MM_TWIPS mapping mode is used for both printer and display. First, we'll use a fixed drawing rectangle, and then we'll base
the drawing rectangle on the printable area rectangle supplied by the printer driver.
Here are the steps for building the example:
1. Run the MFC Application Wizard to generate the Ex17a project. Accept the default options. On the Generated Classes
page, rename the document class CPoemDoc and the view class CStringView. Derive CStringView from CScrollView. Note
that this is an MDI application.
2. Add a CStringArray data member to the CPoemDoc class. Edit the PoemDoc.h header file as follows:
public:
CStringArray m_stringArray;
The document data is stored in a string array. The MFC library CStringArray class holds an array of CString objects, which
are accessible by a zero-based subscript. You need not set a maximum dimension in the declaration because the array is
dynamic.
3. Add a CRect data member to the CStringView class. Edit the StringView.h header file as shown here:
private:
CRect m_rectPrint;
4. Edit three CPoemDoc member functions in the file PoemDoc.cpp. The MFC Application Wizard generates skeleton
OnNewDocument and Serialize functions, but we'll have to use Class View's Properties window to override the
DeleteContents function. We'll initialize the poem document in the overridden OnNewDocument function. DeleteContents is
called in CDocument::OnNewDocument, so by calling the base class function first we're sure the poem won't be deleted. (The
text, by the way, is an excerpt from the 20th poem in Lawrence Ferlinghetti's book A Coney Island of the Mind.) Type 10 lines
of your choice. You can substitute another poem or maybe your favorite Win32 function description. Add the following
boldface code:
BOOL CPoemDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;

m_stringArray.SetSize(10);
m_stringArray[0] = "The pennycandystore beyond the El";
m_stringArray[1] = "is where I first";
m_stringArray[2] = " fell in love";
m_stringArray[3] = " with unreality";
m_stringArray[4] = "Jellybeans glowed in the semi-gloom";
m_stringArray[5] = "of that september afternoon";
m_stringArray[6] = "A cat upon the counter moved among";
m_stringArray[7] = " the licorice sticks";
m_stringArray[8] = " and tootsie rolls";
m_stringArray[9] = " and Oh Boy Gum";

return TRUE;
}
The application framework calls the document's virtual DeleteContents function when it closes the document; this action
deletes the strings in the array. A CStringArray contains actual objects, and a CObArray contains pointers to objects. This
distinction is important when it's time to delete the array elements. Here, the RemoveAll function actually deletes the string
objects:
void CPoemDoc::DeleteContents()
{
// called before OnNewDocument and when document is closed
m_stringArray.RemoveAll();
}
Serialization isn't important in this example, but the following function shows how easy it is to serialize strings. The application
framework calls the DeleteContents function before loading from the archive, so you don't have to worry about emptying the
array. Add the following boldface code:
void CPoemDoc::Serialize(CArchive& ar)
{
m_stringArray.Serialize(ar);
}
Note The CStringArray class supports dynamic arrays, but here we're using the m_stringArray object as if it were a static
array of 10 elements.
Page 91 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
5. Edit the OnInitialUpdate function in StringView.cpp. You must override the function for all classes derived from
CScrollView. This function's job is to set the logical window size and the mapping mode. Add the following boldface code:
void CStringView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(m_rectPrint.Width(), -m_rectPrint.Height());
CSize sizePage(sizeTotal.cx / 2,
sizeTotal.cy / 2); // page scroll
CSize sizeLine(sizeTotal.cx / 100,
sizeTotal.cy / 100); // line scroll
SetScrollSizes(MM_TWIPS, sizeTotal, sizePage, sizeLine);
}
6. Edit the OnDraw function in StringView.cpp. The OnDraw function of class CStringView draws on both the display and
the printer. In addition to displaying the poem text lines in 10-point Roman font, it draws a border around the printable area
and a crude ruler along the top and left margins. OnDraw assumes the MM_TWIPS mapping mode, in which 1 inch = 1440
units. Add the boldface code shown here:
void CStringView::OnDraw(CDC* pDC)
{
int i, j, nHeight;
CString str;
CFont font;
TEXTMETRIC tm;

CPoemDoc* pDoc = GetDocument();
// Draw a border slightly smaller to avoid truncation
pDC->Rectangle(m_rectPrint + CRect(0, 0, -20, 20));
// Draw horizontal and vertical rulers
j = m_rectPrint.Width() / 1440;
for (i = 0; i <= j; i++) {
str.Format("%02d", i);
pDC->TextOut(i * 1440, 0, str);
}
j = -(m_rectPrint.Height() / 1440);
for (i = 0; i <= j; i++) {
str.Format("%02d", i);
pDC->TextOut(0, -i * 1440, str);
}
// Print the poem 0.5 inch down and over;
// use 10-point roman font
font.CreateFont(-200, 0, 0, 0, 400, FALSE,
FALSE, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN,
"Times New Roman");
CFont* pOldFont = (CFont*) pDC->SelectObject(&font);
pDC->GetTextMetrics(&tm);
nHeight = tm.tmHeight + tm.tmExternalLeading;
TRACE("font height = %d, internal leading = %d\n",
nHeight, tm.tmInternalLeading);
j = pDoc->m_stringArray.GetSize();
for (i = 0; i < j; i++) {
pDC->TextOut(720, -i * nHeight - 720,
pDoc->m_stringArray[i]);
}
pDC->SelectObject(pOldFont);
TRACE("LOGPIXELSX = %d, LOGPIXELSY = %d\n",
pDC->GetDeviceCaps(LOGPIXELSX),
pDC->GetDeviceCaps(LOGPIXELSY));
TRACE("HORZSIZE = %d, VERTSIZE = %d\n",
pDC->GetDeviceCaps(HORZSIZE),
pDC->GetDeviceCaps(VERTSIZE));
}
7. Edit the OnPreparePrinting function in StringView.cpp. This function sets the maximum number of pages in the print
job. This example has only one page. You must call the base class DoPreparePrinting function in your overridden
OnPreparePrinting function. Add the following boldface code:
BOOL CStringView::OnPreparePrinting(CPrintInfo* pInfo)
{
pInfo->SetMaxPage(1);
Page 92 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
return DoPreparePrinting(pInfo);
}
8. Edit the constructor in StringView.cpp. The initial value of the print rectangle should be 8 by 15 inches, expressed in
twips (1 inch = 1440 twips). Add the following boldface code:
CStringView::CStringView() : m_rectPrint(0, 0, 11520, -21600)
{
}
9. Build and test the application. If you run the Ex17a application under Windows NT, Window 2000, or Windows XP with the
lowest screen resolution, your MDI child window will look like the one shown here. (The text will be larger with higher
resolutions.)

The window text is too small, isn't it? Go ahead and choose Print Preview from the File menu, and then click twice with the
magnifying glass to enlarge the image. The Print Preview output is shown here:

Remember logical twips from Chapter 6? We'll now use logical twips to enlarge type on the display while keeping the printed
text the same size. This requires some extra work because the CScrollView class wasn't designed for nonstandard mapping
modes. We'll change the view's base class from CScrollView to CLogScrollView, which is a class that borrows from the MFC
code in ViewScrl.cpp. The files LogScrollView.h and LogScrollView.cpp are in the \vcppnet\Ex17a directory on the
companion CD.
10. Insert the CLogScrollView class into the project. Copy the files LogScrollView.h and LogScrollView.cpp from the
companion CD if you haven't done so already. Choose Add Existing Item from the Project menu. Select the two new files and
click OK to insert them into the project.
11. Edit the StringView.h header file. Add the following line at the top of the file:
#include "LogScrollView.h"
Then change the line
class CStringView : public CScrollView
to
class CStringView : public CLogScrollView
Page 93 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
12. Edit the StringView.cpp file. Globally replace all occurrences of CScrollView with CLogScrollView. Then edit the
OnInitialUpdate function. Here's the edited code, which is much shorter:
void CStringView::OnInitialUpdate()
{
CLogScrollView::OnInitialUpdate();
CSize sizeTotal(m_rectPrint.Width(), -m_rectPrint.Height());
SetLogScrollSizes(sizeTotal);
}
13. Build and test the application again. Now the screen should look like this:

Reading the Printer Rectangle
The Ex17a program prints in a fixed-size rectangle that's appropriate for a laser printer set to portrait mode with 8.5-by-11-inch
(letter-size) paper. But what if you load European-size paper or switch to landscape mode? The program should be able to adjust
accordingly.
It's relatively easy to read the printer rectangle. Remember the CPrintInfo pointer that's passed to OnPrint? That structure has a data
member m_rectDraw that contains the rectangle in logical coordinates. Your overridden OnPrint function simply stuffs the rectangle
in a view data member, and OnDraw uses it. There's only one problem: You can't get the rectangle until you start printing, so the
constructor still needs to set a default value for OnDraw to use before printing begins.
If you want the Ex17a program to read the printer rectangle and adjust the size of the scroll view, use Class View's Properties
window to override OnPrint and then code the function as follows:
void CStringView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
m_rectPrint = pInfo->m_rectDraw;
SetLogScrollSizes(CSize(m_rectPrint.Width(),
-m_rectPrint.Height()));
CLogScrollView::OnPrint(pDC, pInfo);
}
Template Collection Classes Revisited: The CArray Class
In the Ex15b example in Chapter 15, you saw the MFC library CTypedPtrList template collection class, which was used to store a
list of pointers to CStudent objects. Another collection class, CArray, is appropriate for our next example, Ex17b. This class is
different from CTypedPtrList in two ways. First, it's an array, with elements accessible by index, just like CStringArray in Ex17a.
Second, the array holds actual objects, not pointers to objects. In Ex17b, the elements are CRect objects. The elements' class does
not have to be derived from CObject, and indeed, CRect is not.
As in Ex17b, a typedef makes the template collection easier to use. We'll use the following statement to define an array class that
holds CRect objects and whose functions take CRect reference parameters. (It's cheaper to pass a 32-bit pointer than to copy a
128bit object.)
typedef CArray<CRect, CRect&> CRectArray;
To use the template array, you declare an instance of CRectArray and then you call CArray member functions such as SetSize. You
can also use the CArray subscript operator to get and set elements.
The template classes CArray, CList, and CMap are easy to use if the element class is sufficiently simple. The CRect class fits that
description because it contains no pointer data members. Each template class uses a global function, SerializeElements, to serialize
all the elements in the collection. The default SerializeElements function does a bitwise copy of each element to and from the
archive.




Page 94 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
If your element class contains pointers or is otherwise complex, you must write your own SerializeElements function. For example, if
you write this function for the rectangle array (not required), your code will look like this:
void AFXAPI SerializeElements(CArchive& ar, CRect* pNewRects,
int nCount)
{
for (int i = 0; i < nCount; i++, pNewRects++) {
if (ar.IsStoring()) {
ar << *pNewRects;
}
else {
ar >> *pNewRects;
}
}
}
When the compiler sees this function, it uses the function to replace the SerializeElements function inside the template. This only
works, however, if the compiler sees the SerializeElements prototype before it sees the template class declaration.
The Ex17b Example: A Multi-Page Print Program
In this example, the document contains an array of 50 CRect objects that define circles. The circles are randomly positioned in a 6-
by-6-inch area and have random diameters of as much as 0.5 inch. The circles, when drawn on the display, look like two-
dimensional simulations of soap bubbles. Instead of drawing the circles on the printer, the application prints the corresponding
CRect coordinates in numeric form, 12 to a page, with headers and footers. Here are the steps:
1. Run the MFC Application Wizard to generate a project named Ex17b. Select Single Document, and accept the defaults
for all the other settings.
2. Edit the StdAfx.h header file. You'll need to bring in the declarations for the MFC template collection classes. Add the
following statement:
#include <afxtempl.h>
3. Edit the Ex17bDoc.h header file. In the Ex17a example, the document data consists of strings stored in a CStringArray
collection. Because we're using a template collection for ellipse rectangles, we'll need a typedef statement outside the class
declaration, as shown here:
typedef CArray<CRect, CRect&> CRectArray;
Next, add the following public data members to the Ex17bDoc.h header file:
public:
enum { nLinesPerPage = 12 };
enum { nMaxEllipses = 50 };
CRectArray m_ellipseArray;
The two enumerations are object-oriented replacements for #defines.
4. Edit the Ex17bDoc.cpp implementation file. The overridden OnNewDocument function initializes the ellipse array with
some random values, and the Serialize function reads and writes the whole array. The MFC Application Wizard generated the
skeletons for both functions. You don't need a DeleteContents function because the CArray subscript operator writes a new
CRect object on top of any existing one. Add the following boldface code:
BOOL CEx17bDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;

int n1, n2, n3;
// Make 50 random circles
srand((unsigned) time(NULL));
m_ellipseArray.SetSize(nMaxEllipses);

for (int i = 0; i < nMaxEllipses; i++) {
n1 = rand() * 600 / RAND_MAX;
n2 = rand() * 600 / RAND_MAX;
n3 = rand() * 50 / RAND_MAX;
m_ellipseArray[i] = CRect(n1, -n2, n1 + n3, -(n2 + n3));
}

Note The template classes depend on two other global functions, ConstructElements and DestructElements. Starting with
Microsoft Visual C++ version 4.0, these functions call the element class constructor and destructor for each object.
Therefore, there's no real need to replace them.




Page 95 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
return TRUE;
}

void CEx17bDoc::Serialize(CArchive& ar)
{
m_ellipseArray.Serialize(ar);
}
5. Edit the Ex17bView.h header file. Use the Add Member Variable Wizard and the Add Member Function Wizard, both
available from Class View, to add the member variable and two function prototypes listed below. The Add Member Function
Wizard will also generate skeletons for the functions in Ex17bView.cpp.
public:
int m_nPage;
private:
void PrintPageHeader(CDC *pDC);
void PrintPageFooter(CDC *pDC);
The m_nPage data member holds the document's current page number for printing. The private functions are for the header
and footer subroutines.
6. Edit the OnDraw function in Ex17bView.cpp. The overridden OnDraw function simply draws the bubbles in the view
window. Add the boldface code shown here:
void CEx17bView::OnDraw(CDC* pDC)
{
int i, j;

CEx17bDoc* pDoc = GetDocument();
j = pDoc->m_ellipseArray.GetUpperBound();
for (i = 0; i < j; i++) {
pDC->Ellipse(pDoc->m_ellipseArray[i]);
}
}
7. Insert the OnPrepareDC function in Ex17bView.cpp. The view class is not a scrolling view, so the mapping mode must
be set in this function. Use Class View's Properties window to override the OnPrepareDC function, and then add the following
boldface code:
void CEx17bView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
pDC->SetMapMode(MM_LOENGLISH);
}
8. Insert the OnPrint function in Ex17bView.cpp. The CView default OnPrint function calls OnDraw. In this example, we
want the printed output to be entirely different from the displayed output, so the OnPrint function must take care of the print
output without calling OnDraw. OnPrint first sets the mapping mode to MM_TWIPS, and then it creates a fixed-pitch font.
After printing the numeric contents of 12 m_ellipseArray elements, OnPrint deselects the font. You could have created the
font once in OnBeginPrinting, but you wouldn't have noticed the increased efficiency. Use Class View's Properties window to
override the OnPrint function, and then add the following boldface code:
void CEx17bView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
int i, nStart, nEnd, nHeight;
CString str;
CPoint point(720, -1440);
CFont font;
TEXTMETRIC tm;

pDC->SetMapMode(MM_TWIPS);
CEx17bDoc* pDoc = GetDocument();
m_nPage = pInfo->m_nCurPage; // for PrintPageFooter's benefit
nStart = (m_nPage - 1) * CEx17bDoc::nLinesPerPage;
nEnd = nStart + CEx17bDoc::nLinesPerPage;
// 14-point fixed-pitch font
font.CreateFont(-280, 0, 0, 0, 400, FALSE, FALSE,
0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_MODERN, "Courier New");
// Courier New is a TrueType font
CFont* pOldFont = (CFont*) (pDC->SelectObject(&font));
PrintPageHeader(pDC);
pDC->GetTextMetrics(&tm);
nHeight = tm.tmHeight + tm.tmExternalLeading;
for (i = nStart; i < nEnd; i++) {
Page 96 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
if (i > pDoc->m_ellipseArray.GetUpperBound()) {
break;
}
str.Format("%6d %6d %6d %6d %6d", i + 1,
pDoc->m_ellipseArray[i].left,
pDoc->m_ellipseArray[i].top,
pDoc->m_ellipseArray[i].right,
pDoc->m_ellipseArray[i].bottom);
point.y -= nHeight;
pDC->TextOut(point.x, point.y, str);
}
PrintPageFooter(pDC);
pDC->SelectObject(pOldFont);
}
9. Edit the OnPreparePrinting function in Ex17bView.cpp. The OnPreparePrinting function (whose skeleton is generated
by the MFC Application Wizard) computes the number of pages in the document and then communicates that value to the
application framework through the SetMaxPage function. Add the following boldface code:
BOOL CEx17bView::OnPreparePrinting(CPrintInfo* pInfo)
{
CEx17bDoc* pDoc = GetDocument();
pInfo->SetMaxPage(pDoc->m_ellipseArray.GetUpperBound() /
CEx17bDoc::nLinesPerPage + 1);
return DoPreparePrinting(pInfo);
}
10. Insert the page header and footer functions in Ex17bView.cpp. These private functions, called from OnPrint, print the
page headers and the page footers. The page footer includes the page number, stored by OnPrint in the view class data
member m_nPage. The CDC::GetTextExtent function provides the width of the page number so that it can be right-justified.
Add the boldface code shown here:
void CEx17bView::PrintPageHeader(CDC* pDC)
{
CString str;

CPoint point(0, 0);
pDC->TextOut(point.x, point.y, "Bubble Report");
point += CSize(720, -720);
str.Format("%6.6s %6.6s %6.6s %6.6s %6.6s",
"Index", "Left", "Top", "Right", "Bottom");
pDC->TextOut(point.x, point.y, str);
}

void CEx17bView::PrintPageFooter(CDC* pDC)
{
CString str;

CPoint point(0, -14400); // Move 10 inches down
CEx17bDoc* pDoc = GetDocument();
str.Format("Document %s", (LPCSTR) pDoc->GetTitle());
pDC->TextOut(point.x, point.y, str);
str.Format("Page %d", m_nPage);
CSize size = pDC->GetTextExtent(str);
point.x += 11520 - size.cx;
pDC->TextOut(point.x, point.y, str); // right-justified
}
11. Build and test the application. For one set of random numbers, the bubble view window looks like this:
Page 97 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

Each time you choose New from the File menu, you should see a different picture. In Print Preview, the first page of the
output should look like this:

In the Print dialog box, you can specify any range of pages to print.
Chapter 18: Splitter Windows and Multiple Views
Overview
Except for the Ex16b example, every program you've seen so far in this book has had only one view attached to a document. If
you've used a Microsoft Windowsbased word processor, you know that it's convenient to have two windows open simultaneously
on different parts of a document. Both windows might show a normal view, or one window might show a print layout view and the
other might show an outline view.
With the application framework, you can use a splitter window or multiple Multiple Document Interface (MDI) child windows to
display multiple views. You'll learn about both presentation options in this chapter and learn how to make multiple view objects of the
same view class (the normal view) in both cases. It's slightly more difficult, however, to use two or more view classes in the same
application (say, the outline view and the print layout view).
This chapter emphasizes the selection and presentation of multiple views. The examples are based on a document with data
initialized in the OnNewDocument function. You can refer back to Chapter 15 for a review of document-view communication.
The Splitter Window
A splitter window appears as a special type of frame window that holds several views in panes. The application can split the window
on creation, or the user can split the window by choosing a menu command or by dragging a splitter box on the window's scroll bar.
After the window has been split, the user can move the splitter bars with the mouse to adjust the relative sizes of the panes. Splitter
windows can be used in both Single Document Interface (SDI) and MDI applications. You can see examples of splitter windows on
pages 461 and 463.








Page 98 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
An object of class CSplitterWnd represents the splitter window. As far as Windows is concerned, a CSplitterWnd object is an actual
window that fully occupies the frame window (CFrameWnd or CMDIChildWnd) client area. The view windows occupy the splitter
window pane areas. The splitter window does not take part in the command dispatch mechanism. The active view window (in a
splitter pane) is connected directly to its frame window.
View Options
When you combine multi-view presentation methods with application models, you get a number of permutations. Here are some of
them:
SDI application with splitter window, single view class This chapter's first example, Ex18a, illustrates this scenario. Each
splitter window pane can be scrolled to a different part of the document. The programmer determines the maximum number of
horizontal and vertical panes; the user makes the split at run time.
SDI application with splitter window, multiple view classes The Ex18b example illustrates this scenario. The programmer
determines the number of panes and the sequence of views; the user can change the pane size at run time.
SDI application with no splitter windows, multiple view classes The Ex18c example illustrates this scenario. The user
switches view classes by choosing a command from a menu.
MDI application with no splitter windows, single view class This is the standard MDI application you saw in Chapter 16. The
New Window menu command lets the user open a new child window for a document that's already open.
MDI application with no splitter windows, multiple view classes A small change to the standard MDI application allows the
use of multiple views. As example Ex18d shows, all you need to do is add a menu command and a handler function for each
additional view class.
MDI application with splitter child windows This scenario is covered thoroughly in the SCRIBBLE example in the MFC
Library Reference.
Dynamic and Static Splitter Windows
A dynamic splitter window allows the user to split the window at any time by choosing a menu command or by dragging a splitter
box on the scroll bar. The panes in a dynamic splitter window generally use the same view class. The top left pane is initialized to a
particular view when the splitter window is created. In a dynamic splitter window, scroll bars are shared among the views. In a
window with a single horizontal split, for example, the bottom scroll bar controls both views. A dynamic splitter application starts with
a single view object. When the user splits the frame, other view objects are constructed. When the user unsplits the frame, view
objects are destroyed.
The panes of a static splitter window are defined when the window is first created, and they cannot be changed. The user can move
the bars but cannot unsplit or resplit the window. Static splitter windows can accommodate multiple view classes, with the
configuration set at creation time. In a static splitter window, each pane has separate scroll bars. In a static splitter window
application, all view objects are constructed when the frame is constructed, and they are all destroyed when the frame is destroyed.
The Ex18a Example: A Single View Class SDI Dynamic Splitter
In this example, the user can dynamically split the view into four panes with four separate view objects, all managed by a single view
class. We'll use the document and the view code from the Ex17a example. The MFC Application Wizard lets you add a dynamic
splitter window to a new application. You create an SDI project and select Split Window on the User Interface Features page, as
shown here:

When you select the Split Window check box, the MFC Application Wizard adds code to your CMainFrame class. Of course, you
can add the same code to the CMainFrame class of an existing application to add splitter capability.












Page 99 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Resources for Splitting
When the MFC Application Wizard generates an application with a splitter frame, it includes a Split menu command on the project's
View menu. The ID_WINDOW_SPLIT command ID is mapped in the CView class within the MFC library.
CMainFrame
The application's main frame window class needs a splitter window data member and a prototype for an overridden OnCreateClient
function. Here are the additions that the MFC Application Wizard makes to the MainFrm.h file:
protected:
CSplitterWnd m_wndSplitter;
public:
virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs,
CCreateContext* pContext);
The application framework calls the CFrameWnd::OnCreateClient virtual member function when the frame object is created. The
base class version creates a single view window as specified by the document template. The MFC Application Wizardgenerated
OnCreateClient override shown here (in MainFrm.cpp) creates a splitter window instead, and the splitter window creates the first
view:
BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,
CCreateContext* pContext)
{
return m_wndSplitter.Create( this,
2, 2, // TODO: adjust the number of rows, columns
CSize(10, 10), // TODO: adjust the minimum pane size
pContext);
}
The CsplitterWnd::Create member function creates a dynamic splitter window, and the CSplitterWnd object knows the view class
because its name is embedded in the CCreateContext structure that's passed as a parameter to Create.
The second and third Create parameters (2, 2) specify that the window can be split into a maximum of two rows and two columns. If
you change the parameters to (2, 1), you'll allow only a single horizontal split. The parameters (1, 2) allow only a single vertical split.
The CSize parameter specifies the minimum pane size.
Testing the Ex18a Application
When the application starts, you can split the window by choosing Split from the View menu or by dragging the splitter boxes at the
left and top of the scroll bars. Figure 18-1 shows a typical single view window with a four-way split. Multiple views share the scroll
bars.

Figure 18-1: A single view window with a four-way split.
The Ex18b Example: A Double View Class SDI Static Splitter
In Ex18b, we'll extend Ex18a by defining a second view class and allowing a static splitter window to show the two views. (The H
and CPP files are cloned from the original view class.) This time the splitter window works a little differently. Instead of starting off as
a single pane, the splitter is initialized with two panes. The user can move the bar between the panes by dragging it with the mouse
or by choosing the Window Split menu command.




Page 100 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The easiest way to generate a static splitter application is to let the MFC Application Wizard generate a dynamic splitter application
and then edit the generated CMainFrame::OnCreateClient function.
CHexView
The CHexView class was written to allow programmers to appreciate poetry. It is essentially the same code used for CStringView
except for the OnDraw member function:
void CHexView::OnDraw(CDC* pDC)
{
// hex dump of document strings
int i, j, k, l, n, nHeight;
CString outputLine, str;
CFont font;
TEXTMETRIC tm;

CPoemDoc* pDoc = GetDocument();
font.CreateFont(-160, 80, 0, 0, 400, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial");
CFont* pOldFont = pDC->SelectObject(&font);
pDC->GetTextMetrics(&tm);
nHeight = tm.tmHeight + tm.tmExternalLeading;

j = pDoc->m_stringArray.GetSize();
for (i = 0; i < j; i++) {
outputLine.Format("%02x ", i);
l = pDoc->m_stringArray[i].GetLength();
for (k = 0; k < l; k++) {
n = pDoc->m_stringArray[i][k] & 0x00ff;
str.Format("%02x ", n);
outputLine += str;
}
pDC->TextOut(720, -i * nHeight - 720, outputLine);
}
pDC->SelectObject(pOldFont);
}
This function displays a hexadecimal dump of all strings in the document's m_stringArray collection. Notice the use of the subscript
operator to access individual characters in a CString object.
CMainFrame
As in Ex18a, the Ex18b application's main frame window class needs a splitter window data member and a prototype for an
overridden OnCreateClient function. You can let the MFC Application Wizard generate the code by specifying Split Window, as in
Ex18a. You don't have to modify the MainFrm.h file.
The implementation file, MainFrm.cpp, needs both view class headers (and the prerequisite document header), as shown here:
#include "PoemDoc.h"
#include "StringView.h"
#include "HexView.h"
The MFC Application Wizard generates dynamic splitter code in the OnCreateClient function, so you'll have to do some editing if you
want a static splitter. Instead of calling CSplitterWnd::Create, you call the CSplitterWnd::CreateStatic function, which is tailored for
multiple view classes. The following calls to CSplitterWnd::CreateView attach the two view classes. As the second and third
CreateStatic parameters (2, 1) dictate, this splitter window contains only two panes. The horizontal split is initially 100 device units
from the top of the window. The top pane is the string view; the bottom pane is the hex dump view. The user can change the splitter
bar position, but not the view configuration.
BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,
CCreateContext* pContext)
{
VERIFY(m_wndSplitter.CreateStatic(this, 2, 1));
VERIFY(m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CStringView),
CSize(100, 100), pContext));
VERIFY(m_wndSplitter.CreateView(1, 0, RUNTIME_CLASS(CHexView),
CSize(100, 100), pContext));
return TRUE;
}
Testing the Ex18b Application
Page 101 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
When you start the Ex18b application, the window should look like the one shown here. Notice the separate horizontal scroll bars for
the two views.

The Ex18c Example: Switching View Classes Without a Splitter
Sometimes you'll just want to switch view classes under program control and not be bothered with a splitter window. The Ex18c
example is an SDI application that switches between CStringView and CHexView in response to commands on the View menu.
Starting with what the MFC Application Wizard generates, all you need to do is add two new menu commands and then add some
code to the CMainFrame class. You also need to change the CStringView and CHexView constructors from protected to public.
Resource Requirements
The following two commands have been added to the View menu in the IDR_MAINFRAME menu resource.
The Class View's Properties window was used to add the command-handling functions and corresponding update command user
interface handlers to the CMainFrame class.
CMainFrame
The CMainFrame class gets a new private helper function, SwitchToView, which is called from the two menu command handlers.
The enum parameter tells the function which view to switch to. Here are the two added items in the MainFrm.h header file:
private:
enum eView { STRING = 1, HEX = 2 };
void SwitchToView(eView nView);
The SwitchToView function (in MainFrm.cpp) makes some low-level MFC calls to locate the requested view and activate it. Don't
worry about how it worksjust adapt it to your own applications when you want the view-switching feature. Add the following code:
void CMainFrame::SwitchToView(eView nView)
{
CView* pOldActiveView = GetActiveView();
CView* pNewActiveView = (CView*) GetDlgItem(nView);
if (pNewActiveView == NULL) {
switch (nView) {
case STRING:
pNewActiveView = (CView*) new CStringView;
break;
case HEX:
pNewActiveView = (CView*) new CHexView;
break;
}
CCreateContext context;
context.m_pCurrentDoc = pOldActiveView->GetDocument();
pNewActiveView->Create(NULL, NULL, WS_BORDER,
CFrameWnd::rectDefault, this, nView, &context);




Caption Command ID CMainFrame Function
St&ring View ID_VIEW_STRINGVIEW OnViewStringView
&Hex View ID_VIEW_HEXVIEW OnViewHexView
Page 102 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
pNewActiveView->OnInitialUpdate();
}
SetActiveView(pNewActiveView);
pNewActiveView->ShowWindow(SW_SHOW);
pOldActiveView->ShowWindow(SW_HIDE);
pOldActiveView->SetDlgCtrlID(
pOldActiveView->GetRuntimeClass() ==
RUNTIME_CLASS(CStringView) ? STRING : HEX);
pNewActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
RecalcLayout();
}
Finally, here are the menu command handlers and update command user interface handlers that the code wizard available from
Class View's Properties window initially generated (along with message map entries and prototypes). The update command user
interface handlers test the current view's class.
void CMainFrame::OnViewStringView()
{
SwitchToView(STRING);
}

void CMainFrame::OnUpdateViewStringView(CCmdUI* pCmdUI)
{
pCmdUI->Enable(
!GetActiveView()->IsKindOf(RUNTIME_CLASS(CStringView)));
}

void CMainFrame::OnViewHexView()
{
SwitchToView(HEX);
}

void CMainFrame::OnUpdateViewHexView(CCmdUI* pCmdUI)
{
pCmdUI->Enable(
!GetActiveView()->IsKindOf(RUNTIME_CLASS(CHexView)));
}
Testing the Ex18c Application
The Ex18c application initially displays the CStringView view of the document. You can toggle between the CStringView and
CHexView views by choosing the appropriate command from the View menu. Both views of the document are shown side by side in
Figure 18-2.

Figure 18-2: The CStringView view and the CHexView view of the document.
The Ex18d Example: A Multiple View Class MDI Application
The final example, Ex18d, uses the previous document and view classes to create a multiple view class MDI application without a
splitter window. The logic is different from the logic in the other multiple view class applications. This time the action takes place in
the application class in addition to the main frame class. As you study Ex18d, you'll gain more insight into the use of CDocTemplate
objects.
This example was generated with the Context-Sensitive Help option on the Advanced Features page of the MFC Application Wizard.
If you're starting from scratch, use the wizard to generate an ordinary MDI application with one of the view classes. Then add the




Page 103 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
second view class to the project and modify the application class files and main frame class files, as described in the following
sections.
Resource Requirements
Two items have been added to the Window menu in the IDR_Ex18dTYPE menu resource:
Class View's Properties window was used to add the command-handling function OnWindowNewhexwindow to the CMainFrame
class.
CEx18dApp
In the application class header file, Ex18d.h, the following data member and function prototype have been added:
public:
CMultiDocTemplate* m_pTemplateHex;
The implementation file, Ex18d.cpp, contains the #include statements shown here:
#include "PoemDoc.h"
#include "StringView.h"
#include "HexView.h"
The CEx18dApp InitInstance member function has the code shown below inserted immediately after the AddDocTemplate function
call:
m_pTemplateHex = new CMultiDocTemplate(
IDR_Ex18dTYPE,
RUNTIME_CLASS(CPoemDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CHexView));
The AddDocTemplate call generated by the MFC Application Wizard established the primary document-frame-view combination for
the application that is effective when the program starts. The template object above is a secondary template that can be activated in
response to the New Hex Window menu command.
Now all you need is an ExitInstance member function, which overrides the WinApp::ExitInstance to clean up the secondary
template:
int CEx18dApp::ExitInstance()
{
delete m_pTemplateHex;
return CWinApp::ExitInstance(); // saves profile settings
}
CMainFrame
The main frame class implementation file, MainFrm.cpp, has the CHexView class header (and the prerequisite document header)
included:
#include "PoemDoc.h"
#include "HexView.h"
The base frame window class, CMDIFrameWnd, has an OnWindowNew function that is normally connected to the standard New
Window command on the Window menu. The New String Window command is mapped to this function in Ex18d. The New Hex
Window command is mapped to the command handler function below to create new hex child windows. The function is a clone of
OnWindowNew, adapted for the hex view-specific template defined in InitInstance.
void CMainFrame::OnWindowNewhexwindow()
{
CMDIChildWnd* pActiveChild = MDIGetActive();
CDocument* pDocument;
if (pActiveChild == NULL ||
(pDocument = pActiveChild->GetActiveDocument()) == NULL) {
TRACE("Warning: No active document for WindowNew command\n");
AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
return; // Command failed
}
Caption Command ID CMainFrame Function
New &String Window (replaces New
Window item)
ID_WINDOW_NEWSTRINGWINDOW CMDIFrameWnd::OnWindowNew
New &Hex Window ID_WINDOW_NEWHEXWINDOW OnWindowNewhexwindow
Page 104 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

// Otherwise, we have a new frame!
CDocTemplate* pTemplate =
((CEx18dApp*) AfxGetApp())->m_pTemplateHex;
ASSERT_VALID(pTemplate);
CFrameWnd* pFrame =
pTemplate->CreateNewFrame(pDocument, pActiveChild);
if (pFrame == NULL) {
TRACE("Warning: failed to create new frame\n");
AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
return; // Command failed
}

pTemplate->InitialUpdateFrame(pFrame, pDocument);
}
Testing the Ex18d Application
When you start the Ex18d application, a text view child window appears. Choose New Hex Window from the Window menu. The
application should look like this:

Chapter 19: Context-Sensitive Help
Overview
Help technology comes in two flavors these days: HTML format and the classic WinHelp format. Microsoft Foundation Class (MFC)
library application framework programs work with both WinHelp and HTML Help, but the trend is toward HTML Help. You can see an
example of HTML Help in the Microsoft Visual C++ .NET online documentation.
This chapter shows you how to construct and process a simple standalone help file that has a table of contents and lets the user
jump between topics. You'll also learn how your MFC library program activates the help system using help context IDs derived from
window and command IDs keyed to an MFC Application Wizardgenerated help file. Finally, you'll learn how to use the MFC library
help message routing system to customize the help capability.
WinHelp vs. HTML Help
The choice between WinHelp and HTML Help is largely a personal one. The programmatic interface for accessing and managing
each help system from MFC is the same. WinHelp uses Rich Text Format (RTF), whereas HTML Help uses HTML format. Over the
last few years, several Microsoft Windows help tools such as RoboHELP from Blue Sky Software and ForeHelp from the Forefront
Corporation have made writing standard WinHelp straightforward, but WinHelp implementations will probably eventually give way to
HTML Help help systems.
The process of accessing topics in classic WinHelp is sequentialyou get a list of topics via an index or table of contents, and when
you select a topic WinHelp takes you to another window. Here's an example of the default WinHelp produced by the MFC
Note The function cloning above is a useful MFC programming technique. You must first find a base class function that does
almost what you want, and then copy it from the \Vc7\atlmfc\src\mfc subdirectory into your derived class, changing it as
required. The only danger with cloning is that subsequent versions of the MFC library might implement the original function
differently.








Page 105 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Application Wizard:

Here's the screen you see after selecting the "File menu commands" topic. You can get to the contents or the index or get back to
the previous topic by clicking the appropriate button.

Here's an example of the default HTML Help produced by the MFC Application Wizard. Notice that the left pane of the window
includes an Index tab, a Contents tab, and a Search tab, and that the topic content is shown in the right pane.

The HTML Help system is implemented as an ActiveX control named HHCtrl.ocx. HHCtrl.ocx provides navigation features and
manages secondary windows and pop-up definitions. HHCtrl.ocx is flexible and will display topics from a precompiled help file as
well as from HTML pages displayed in a Web browser.
Let's first look at using WinHelp in an MFC application.
The Windows WinHelp Program
If you've used commercial Windows-based applications, you're familiar with their sophisticated help screens, in which graphics,
hyperlinks, and pop-ups abound. At some software firms, including Microsoft, help authoring has been elevated to a profession in its
own right. This chapter won't turn you into a help expert, but you can get started by learning to prepare a simple no-frills help file.




Page 106 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Rich Text Format
The original Windows SDK documentation showed you how to format help files using the ASCII file format called Rich Text Format
(RTF). We'll be using RTF too, but we'll be working in WYSIWYG mode to avoid the direct use of awkward escape sequences. You'll
write with the same fonts, sizes, and styles that users will see on the help screens. You'll definitely need a word processor that
handles RTF. Microsoft Word is just fine, but many other word processors also accommodate the RTF format.
Writing a Simple Help File
We're going to write a simple help file with a table of contents and three topics. This help file is designed to be run directly from
WinHelp and started from Windows. No C++ programming is involved. Here are the steps:
1. Create a \vcppnet\Ex19a subdirectory.
2. Write the main help text file. Use Word (or another RTF-compatible word processor) to type text as shown here.

Be sure to apply the double-underline and hidden text formatting correctly and to insert the page break at the correct place.
3. Insert footnotes for the Table Of Contents screen. The Table Of Contents screen is the first topic screen in this help
system. Using the specified custom footnote marks, insert the following footnotes at the beginning of the topic title:
When you're finished with this step, the document should look like this:

4. Insert footnotes for the Help Topic 1 screen. The Help Topic 1 screen is the second topic screen in the help system. Using
the specified custom footnote marks, insert these footnotes:
Note To see hidden text, you must turn on your word processor's hidden text viewing mode. In Word, choose Options
from the Tools menu, click on the View tab, and then select All in the Formatting Marks section.
Footnote Mark Text Description
# HID_CONTENTS Help context ID
$ SIMPLE Help Contents Topic title
Footnote Mark Text Description
Page 107 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
5. Clone the Help Topic 1 screen. Copy the entire Help Topic 1 section of the documentincluding the page breakto the
Clipboard, and then paste two copies of the text into the document. The footnotes will be copied along with the text. In the
first copy, change all occurrences of 1 to 2. In the second copy, change all occurrences of 1 to 3. Don't forget to change the
footnotes. With Word, seeing which footnote goes with which topic can be a little difficult, so be careful. When you're finished
with this step, the document text (including footnotes) should look like this:

6. Save the document. Save the document as \vcppnet\Ex19a\ Simple.rtf. Specify Rich Text Format as the file type.
7. Write a help project file. Using Visual C++ .NET or another text editor, create the file \vcppnet\Ex19a\ Simple.hpj, as
follows:
[OPTIONS]
CONTENTS=HID_CONTENTS
TITLE=SIMPLE Application Help
COMPRESS=true
WARNING=2

[FILES]
Simple.rtf
This file specifies the context ID of the Table Of Contents screen and the name of the RTF file that contains the help text. Be
sure to save the file in text (ASCII) format.
8. Build the help file. From Windows, run the Microsoft Help Workshop (HCRTF) utility (located by default in Program
Files\Microsoft Visual Studio .NET\Common7\Tools). Open the file \vcppnet\Ex19a\ Simple.hpj, and then compile the help
file by choosing Compile from the File menu.
The Windows Help Compiler will run with the project file Simple.hpj. The output will be the help file Simple.hlp in the
same directory.
9. Run WinHelp with the new help file. In Windows Explorer, double-click on the file \vcppnet\Ex19a\ Simple.hlp. The Table
Of Contents screen should look like this:
# HID_TOPIC1 Help context ID
$ SIMPLE Help Topic 1 Topic title
K SIMPLE Topics Keyword text
Page 108 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

Now move the cursor to Topic 1. Notice that the cursor changes from an arrow to a pointing hand. When you press the left
mouse button, the Help Topic 1 screen should appear, as shown here:

The HID_TOPIC1 text on the Table Of Contents screen links to the corresponding context ID (the # footnote) on the topic
page. This link is known as a jump.
The link to Topic 2 is coded as a pop-up jump. When you click on Topic 2, here's what you'll see:

10. Click the WinHelp Contents button. Clicking this button should take you to the Table Of Contents screen, as shown at the
beginning of step 9. WinHelp knows the ID of the Table Of Contents window because you specified it in the HPJ file.
11. Click the WinHelp Index button. When you click the Index button, WinHelp opens its Index dialog box, which displays the
help file's list of keywords. In Simple.hlp, all topics (excluding the table of contents) have the same keyword (the K
footnotes): SIMPLE Topics. When you double-click on this keyword, you'll see all associated topic titles (the $ footnotes), as
shown here:
Page 109 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

What we have here is a two-level help search hierarchy. The user can type the first few letters of the keyword and then select a topic
from a list box. The more carefully you select your keywords and topic titles, the more effective your help system will be.
An Improved Table of Contents
You've been looking at an "old-style" help table of contents. The latest Win32 version of WinHelp can give you a modern, tree-view
table of contents. All you need is a text file with a CNT extension. Add a new file, Simple.cnt, in the \vcppnet\Ex19a directory,
containing this text:
:Base Simple.hlp
1 Help topics
2 Topic 1=HID_TOPIC1
2 Topic 2=HID_TOPIC2
2 Topic 3=HID_TOPIC3
Notice the context IDs that match the help file. The next time you run WinHelp with the Simple.hlp file, you'll see a new contents
screen similar to the one shown here:

You can also use HCRTF to edit CNT files. The CNT file is independent of the HPJ file and the RTF files. If you update your RTF
files, you must make corresponding changes in your CNT file.
The Application Framework and WinHelp
You've seen WinHelp running as a standalone program. The application framework and WinHelp cooperate to give you context-
sensitive help. Here are some of the main elements:
1. You select the Context-Sensitive Help option when you run the MFC Application Wizard. Select WinHelp (rather than HTML
Text) as the help system.
2. The MFC Application Wizard generates a Help Topics command on your application's Help menu, and it creates one or more
generic RTF files together with an HPJ file and a batch file that runs the Help Compiler.
3. The MFC Application Wizard inserts a keyboard accelerator for the F1 key, and it maps the F1 key and the Help Topics
command to member functions in the main frame window object.




Page 110 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
4. When your program runs, it calls WinHelp when the user presses F1 or chooses the Help Topics command, passing a
context ID that determines which help topic is displayed.
You now need to understand how WinHelp is called from another application and how your application generates context IDs for
WinHelp.
Calling WinHelp
The CWinApp member function WinHelp activates WinHelp from within your application. If you look up WinHelp in the online
documentation, you'll see a long list of actions that the optional second parameter controls. We'll ignore the second parameter and
pretend that WinHelp has only one unsigned long integer parameter, dwData. This parameter corresponds to a help topic.
Suppose the SIMPLE help file is available and that your program contains the following statement:
AfxGetApp()->WinHelp(HID_TOPIC1);
When the statement is executed in response to the F1 key or some other event, the Help Topic 1 screen appears, as it would if the
user had clicked on Topic 1 in the Help Table Of Contents screen.
"Wait a minute," you might say. "How does WinHelp know which help file to use?" The name of the help file matches the application
name. If the executable program name is Simple.exe, the help file is named Simple.hlp.
And how does WinHelp match the program constant HID_TOPIC1 to the help file's context ID? The help project file must contain a
MAP section that maps context IDs to numbers. If your application's resource.h file defines HID_TOPIC1 as 101, the
Simple.hpj MAP section will look like this:
[MAP]
HID_TOPIC1 101
The program's #define constant name doesn't have to match the help context ID; only the numbers must match. Making the names
correspond is good practice, however.
Using Search Strings
For a text-based application, you might need help based on a keyword rather than a numeric context ID. In this case, you can use
the WinHelp HELP_KEY or HELP_PARTIALKEY option, as follows:
CString string("find this string");
AfxGetApp()->WinHelp((DWORD) (LPCSTR) string, HELP_KEY);
The double cast for string is necessary because the first WinHelp parameter is multi-purpose; its meaning depends on the value of
the second parameter.
Calling WinHelp from the Application's Menu
The MFC Application Wizard generates a Help Topics command on the Help menu, and it maps that command to
CWnd::OnHelpFinder in the main frame window, which calls WinHelp in this way:
AfxGetApp()->WinHelp(0L, HELP_FINDER);
With this call, WinHelp displays the Help Table Of Contents screen, and the user can navigate through the help file using jumps and
searches.
If you want the old-style table of contents, you can call WinHelp in this way instead:
AfxGetApp()->WinHelp(0L, HELP_INDEX);
And if you want a "help on help" item, you can make this call:
AfxGetApp()->WinHelp(0L, HELP_HELPONHELP);
HELP_HELPONHELP is a standard identifier that asks the help system to display help on how to use Windows Help. This works
only if the Winhlp32.hlp file is available.
Help Context Aliases
The ALIAS section of the HPJ file allows you to equate one context ID with another. Suppose your HPJ file contains the following
statements:
[ALIAS]
HID_TOPIC1 = HID_GETTING_STARTED

[MAP]
HID_TOPIC1 101
Note You can force WinHelp to use a different help file by setting the CWinApp data member m_pszHelpFilePath.
Page 111 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Your RTF files can use HID_TOPIC1 and HID_GETTING_STARTED interchangeably. Both will be mapped to the help context 101
as generated by your application.
Determining the Help Context
You now have enough information to add a simple context-sensitive help system to an MFC program. You define F1 (the standard
MFC library Help key) as a keyboard accelerator, and then you write a command handler that maps the program's help context to a
WinHelp parameter. You could invent your own method for mapping the program state to a context ID, but why not take advantage
of the system that's already built into the application framework?
The application framework determines the help context based on the ID of the active program element. These identified program
elements include menu commands, frame windows, dialog boxes, message boxes, and control bars. For example, a menu
command might be identified as ID_EDIT_CLEARALL. The main frame window usually has the IDR_MAINFRAME identifier. You
might expect these identifiers to map directly to help context IDs. IDR_MAINFRAME, for example, will map to a help context ID of
the same name. But what if a frame ID and a command ID have the same numeric value? Obviously, you need a way to prevent
such overlaps.
The application framework solves the overlap problem by defining a new set of help #define constants that are derived from program
element IDs. These help constants are the sum of the element ID and a base value, as shown in the following table.
HID_EDIT_CLEARALL (0x1E121) corresponds to ID_EDIT_CLEARALL (0xE121), and HIDR_MAINFRAME (0x20080) corresponds
to IDR_MAINFRAME (0x80).
F1 Help
Two separate context-sensitive help access methods are built into an MFC application and are available if you've selected the MFC
Application Wizard's Context-Sensitive Help option. The first is standard F1 help. The user presses F1, the program makes its best
guess about the help context, and then it calls WinHelp. In this mode, it is possible to determine the currently selected menu
command or the currently selected window (frame, view, dialog box, or message box).
Shift+F1 Help
With Shift+F1 help, which is more powerful than the F1 mode, the program can identify the following help contexts:
A menu command selected with the mouse cursor
A toolbar button
A frame window
A view window
A specific graphics element within a view window
The status bar
Various nonclient elements such as the system menu control
The user activates Shift+F1 help by pressing Shift+F1 or by clicking the Context Help toolbar button. In either case, the mouse
cursor changes to include a question mark next to it. On the next mouse click, the help topic appears, with the position of the mouse
cursor determining the context.
Message Box Help: The AfxMessageBox Function
The global function AfxMessageBox displays application framework error messages. This function is similar to the
CWnd::MessageBox member function except that it has a help context ID as a parameter. The application framework maps this ID
Program Element Element ID Prefix Help Context ID
Prefix
Base (Hexadecimal)
Menu command or toolbar button ID_, IDM_ HID_, HIDM_ 10000
Frame or dialog box IDR_, IDD_ HIDR_, HIDD 20000
Error message box IDP_ HIDP_ 30000
Nonclient area

H 40000
Control bar IDW_ HIDW_ 50000
Dispatch error messages

60000
Note Shift+F1 help doesn't work with modal dialog boxes or message boxes.
Page 112 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
to a WinHelp context ID and then calls WinHelp when the user presses F1. If you can use the AfxMessageBox help context
parameter, be sure to use prompt IDs that begin with IDP_. In your RTF file, use help context IDs that begin with HIDP_.
There are two versions of AfxMessageBox. In the first version, the prompt string is specified by a character-array pointer parameter.
In the second version, the prompt ID parameter specifies a string resource. If you use the second version, your executable program
will be more efficient. Both AfxMessageBox versions take a style parameter that makes the message box display an exclamation
point, a question mark, or another graphics symbol.
Generic Help
When context-sensitive help is enabled, the MFC Application Wizard assembles a series of default help topics that are associated
with standard MFC library program elements. Here are some of the standard topics:
Menu and toolbar commands (File, Edit, and so forth)
Nonclient window elements (maximize box, title bar, and so forth)
Status bar
Error message boxes
These topics are contained in the files AfxCore.rtf and AfxPrint.rtf, which are copied, along with the associated bitmap files, to
the application's \hlp subdirectory. Your job is to customize the generic help files.
A Help Example with No Programming Required
If you followed the instructions for the Ex18d example in Chapter 18, you selected the MFC Application Wizard's Context-Sensitive
Help option. We'll now use that example to explore the application framework's built-in help capability. You'll see how easy it is to
link help topics to menu command IDs and frame window resource IDs. We'll edit RTF files, not CPP files.
Here are the steps for customizing the help for Ex18d:
1. Verify that the help file was built correctly. If you've built the Ex18d project, the help file was probably created correctly as
part of the build process. Check this by running the application and then pressing the F1 key. You should see the generic
Application Help screen with the title "Modifying the Document," as shown here:

If you do not see this screen, the help file was not built correctly. You can rebuild it by rebuilding the entire solution. Rerun the
Ex18d program, and press F1 again.
2. Test the generic help file. Try the following experiments:
Close the Help dialog box, press Alt+F, and then press F1. This should open the help topic for the File New command.
You can also press F1 while holding down the mouse button on the File New command to see the same help topic.
Close the Help dialog box, click the Context Help toolbar button and then choose Save from the File menu. You should
get the appropriate help topic.
Click the Context Help toolbar button again, and then select the frame window's title bar. You should get an explanation
of a Windows title bar.
Close all child windows and then press F1. You should see a main index page that is also an old-style table of contents.
3. Change the application title. The file AfxCore.rtf, in the \vcppnet\Ex18d\hlp directory, contains the string <<YourApp>>
throughout. Replace it globally with Ex18d.
Note
The MFC Application Wizard generates AfxPrint.rtf only if you specify the Printing And Print Preview option.




Page 113 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
4. Change the Modifying The Document Help screen. The file AfxCore.rtf in the \vcppnet\Ex18d\hlp directory contains text
for the generic Application Help screen. Search on Modifying the Document, and then change the text to something
appropriate for the application. This topic has the help context ID HIDR_DOC1TYPE. The generated Ex18d.hpj file
provides the alias HIDR_Ex18dTYPE.
5. Add a topic for the New String Window and New Hex Window commands on the Window menu. The New String
Window and New Hex Window commands were added to Ex18d, but without appropriate help text. Add a topic to
AfxCore.rtf, as shown here.

Be sure the # footnote that links the topic to the context ID uses HID_WINDOW_NEWSTRINGWINDOW and
HID_WINDOW_NEWHEXWINDOW, as defined in hlp\ Ex18d.hm. The program's command ID for the New String Window
command is ID_WINDOW_NEWSTRINGWINDOW. The command ID for a new hex window is
ID_WINDOW_NEWHEXWINDOW.
6. Rebuild and test the application. Rebuild the entire application to synchronize the help files. Try the two new help links.
Help Command Processing
You've seen the components of a help file, and you've seen the effects of F1 and Shift+F1. You know how the application element
IDs are linked to help context IDs. What you haven't seen is the application framework's internal processing of the help requests.
Why should you be concerned? Suppose you want to provide help on a specific view window instead of a frame window. What if you
need help topics linked to specific graphics items in a view window? You can address these and other needs by mapping the
appropriate help messages in the view class.
Help command processing depends on whether the help request was an F1 request or a Shift+F1 request. Let's look at the
processing of each help request separately.
F1 Processing
The F1 key is normally handled by a keyboard accelerator entry that the MFC Application Wizard inserts in the RC file. The
accelerator associates the F1 key with an ID_HELP command that is sent to the OnHelp member function in the CFrameWnd class.
The CFrameWnd::OnHelp function sends an MFC-defined WM_COMMANDHELP message to the innermost window, which is
usually the view. If your view class does not map this message or if the handler returns FALSE, the framework will route the
message to the next outer window, which is either the MDI child frame or the main frame. If you have not mapped
WM_COMMANDHELP in your derived frame window classes, the message will be processed in the MFC CFrameWnd class, which
displays help for the symbol that the MFC Application Wizard generates for your application or document type.
If you map the WM_COMMANDHELP message in a derived class, your handler must call CWinApp::WinHelp with the proper
context ID as a parameter.
For any application, the MFC Application Wizard adds the symbol IDR_MAINFRAME to your project and the HM file defines the help
context ID HIDR_MAINFRAME, which is aliased to main_index in the HPJ file. The standard AfxCore.rtf file associates the main
index with this context ID.
For an MDI application named SAMPLE, for example, the MFC Application Wizard will also add the symbol IDR_SAMPLETYPE to
your project and the HM file will define the help context ID HIDR_SAMPLETYPE, which is aliased to HIDR_DOC1TYPE in the HPJ
file. The standard AfxCore.rtf file will associate the topic "Modifying the Document" with this context ID.




Note In an active modal dialog box or a menu command in progress, the F1 key is processed by a Windows hook that causes
the same OnHelp function to be called. The F1 accelerator key would otherwise be disabled.
Page 114 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Shift+F1 Processing
When the user presses Shift+F1 or clicks the Context Help toolbar button, a command message is sent to the CFrameWnd function
OnContextHelp. When the user presses the mouse button again after positioning the mouse cursor, an MFC-defined
WM_HELPHITTEST message is sent to the innermost window where the mouse click is detected. From that point on, the routing of
this message is identical to that for the WM_COMMANDHELP message, described previously.
The lParam parameter of OnHelpHitTest contains the mouse coordinates in device units, relative to the upper left corner of the
window's client area. The y value is in the high-order half; the x value is in the low-order half. You can use these coordinates to set
the help context ID specifically for an item in the view. Your OnHelpHitTest handler should return the correct context ID; the
framework will call WinHelp.
Example Ex19b: Help Command Processing
Ex19b is based on example Ex18d from Chapter 18. It's a two-view MDI application with view-specific help added. Each of the two
view classes has an OnCommandHelp message handler to process F1 help requests and an OnHelpHitTest message handler to
process Shift+F1 help requests.
Header Requirements
The compiler recognizes help-specific identifiers only if the following #include statement is present:
#include <afxpriv.h>
In Ex19b, the statement is in the StdAfx.h file.
CStringView
The modified string view in StringView.h needs message map function prototypes for both F1 help and Shift+F1 help, as shown
here:
afx_msg LRESULT OnCommandHelp(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnHelpHitTest(WPARAM wParam, LPARAM lParam);
Here are the message map entries in StringView.cpp:
ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)
The OnCommandHelp message handler member function in StringView.cpp processes F1 help requests. It responds to the
message sent from the MDI main frame and displays the help topic for the string view window, as shown here:
LRESULT CStringView::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
if (lParam == 0) { // context not already determined
lParam = HID_BASE_RESOURCE + IDR_STRINGVIEW;
}
AfxGetApp()->WinHelp(lParam);
return TRUE;
}
Finally, the OnHelpHitTest member function handles Shift+F1 help, as shown here:
LRESULT CStringView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
return HID_BASE_RESOURCE + IDR_STRINGVIEW;
}
In a more complex application, you might want OnHelpHitTest to set the help context ID based on the mouse cursor position.
CHexView
The CHexView class processes help requests the same way as the CStringView class does. Following is the necessary header
code in HexView.h:
afx_msg LRESULT OnCommandHelp(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnHelpHitTest(WPARAM wParam, LPARAM lParam);
Here are the message map entries in HexView.cpp:
ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)




Page 115 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
And here is the implementation code in HexView.cpp:
LRESULT CHexView::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
if (lParam == 0) { // context not already determined
lParam = HID_BASE_RESOURCE + IDR_HEXVIEW;
}
AfxGetApp()->WinHelp(lParam);
return TRUE;
}

LRESULT CHexView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
return HID_BASE_RESOURCE + IDR_HEXVIEW;
}
Resource Requirements
Two new symbols were added to the project's Resource.h file. Their values and corresponding help context IDs are shown here:
Help File Requirements
Two topics were added to the AfxCore.rtf file with the help context IDs HIDR_STRINGVIEW and HIDR_HEXVIEW, as shown
here:

The generated Ex19b.hm file, which is in the project's \hlp subdirectory, should look like this:
// Commands (ID_* and IDM_*)
HID_WINDOW_NEWHEXWINDOW 0x10082
HID_WINDOW_NEWSTRINGWINDOW 0x10083

// Prompts (IDP_*)
HIDP_OLE_INIT_FAILED 0x30064

// Resources (IDR_*)
HIDR_MANIFEST 0x20001
HIDR_MAINFRAME 0x20080
HIDR_Ex19bTYPE 0x20081
HIDR_STRINGVIEW 0x20065
HIDR_HEXVIEW 0x20066

// Dialogs (IDD_*)
HIDD_ABOUTBOX 0x20064

// Frame Controls (IDW_*)
Testing the Ex19b Application
To test the application, open a string child window and a hexadecimal child window. Test the action of F1 help and Shift+F1 help
within those windows.
Symbol Value Help Context ID Value
IDR_STRINGVIEW 101 HIDR_STRINGVIEW 0x20065
IDR_HEXVIEW 102 HIDR_HEXVIEW 0x20066


Page 116 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
MFC and HTML Help
When you add help to your application, the second option is to use HTML Help. MFC applications access HTML Help in much the
same way that they access WinHelp file. The application feeds context-sensitive help IDs into the help system, and the help system
displays the appropriate help screen. However, HTML Help files are constructed differently than WinHelp files. HTML Help files are
compiled from a number of HTML pages rather than from a single RTF file.
The following table shows the files generated by the MFC Application Wizard when you select HTML Help Format as the help
system:
Let's take a look at how to add HTML Help to an MFC application.
Example Ex19c: HTML Help
Ex19c is also based on example Ex18d. It is an MDI application that includes two views to a single document and uses HTML Help.
The example was created using the MFC Application Wizard with HTML Help Format selected on the Advanced Features page.
If you look in the file hid_window_newhexwindow.htm, you'll see some updated help text reflecting the New String Window menu
command. This is the text that appears whenever you select context-sensitive help for the New String Window command. Also
notice a new file (one not generated by the MFC Application Wizard) named hid_window_newhex.htm, which is the help text that
appears for the New Hex Window command.
If you're feeling gutsy, you can modify the help files with Notepad. The file includes tags that are used by the HTML help system, so
tread lightly. However, a better option is to open the .HTM file in Visual Studio .NET. Visual Studio .NET understands HTML files
and lets you edit them easily.
You might wonder how to create new help topics in the first place? The easiest way to create a new help topic is to take an existing
HTM file, rename it appropriately, and then add new content to the file. Then you associate the new HTM file with the command ID
as described below.
Visual Studio .NET adds help context IDs when you add new menu commands to the program and recompile it. Near the top of the
HTMLDefines.h file, you'll see the following lines, which define help contexts for the New String Window menu command and the
New Hex Window menu command:
#define HID_WINDOW_NEWSTRINGWINDOW 0x10082
#define HID_WINDOW_NEWHEXWINDOW 0x10083
In addition to the standard menu command help created by the MFC Application Wizard, Ex19c has a new help topic for the New
Hex Window command. The help context ID was generously included by Visual Studio .NET when the command was added. Now it
needs to be tied to the hid_window_newhexwindow.htm file. A line in the Ex19c.hhp file associates the help context ID with the
help file:
hid_window_newhexwindow = hid_window_newhexwindow.htm
Finally, the Ex19c.hhp file includes a reference to the new HTML file under the files tag:
[FILES]
afx_hidd_color.htm
afx_hidd_fileopen.htm
afx_hidd_filesave.htm

hid_window_newstringwindow.htm
hid_window_newhexwindow.htm
hid_window_split.htm

Once you relate the HTML files to the help command IDs, there's nothing else you need to do to get the help topics working. The
rest of the built-in MFC help functionality will take care of the details for you. To see for yourself, run the Ex19c example, select
various menu commands and push F1. You'll see the correct help screens appear after pressing F1.


File Description
HTMLDefines.h
Includes the context IDs for the entire project.
HTML help documents HTML files that define the help textgenerally one per help topic.
projectname.hhc HTML Help Compiler file that contains instructions to the HTML Help compiler about how
to compile the help contents.
projectname.hhp HTML Help Compiler file that defines directives for compiling a help project.
Main_index.htm
Top-level HTM file. This is also where you add your own help topics.








Page 117 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Chapter 20: Dynamic-Link Libraries
Overview
Dynamic-link libraries (DLLs) lie at the heart of the Microsoft Windows component modeleven with the Microsoft .NET common
language runtime right around the corner. Windows is itself composed of DLLs, which are binary modules. Binary modularity is
different from source code modularity, which is what C++ employs. Instead of programming giant EXEs that you must rebuild and
test each time you make a change, you can build smaller DLL modules and test them individually. You can, for example, put a C++
class in a DLL, which might be as small as 12 KB after compiling and linking. Client programs can load and link your DLL very
quickly when they run.
DLLs have become quite easy to write. Win32 has greatly simplified the programming model, and more and better support is
available from the Microsoft Foundation Class (MFC) DLL Wizard and the MFC library. This chapter shows you how to write DLLs in
C++ and how to write client programs that use DLLs. We'll explore how Win32 maps DLLs into your processes, and you'll learn the
differences between MFC library regular DLLs and MFC library extension DLLs. You'll see examples of simple DLLs of both types
as well as a more complex DLL example that implements a custom control.
DLL Fundamentals
Before we look at the application framework's support for DLLs, you must understand how Win32 integrates DLLs into your process.
You might want to review Chapter 10 to refresh your knowledge of processes and virtual memory. Remember that a process is a
running instance of a program and that the program starts out as an EXE file on disk.
Basically, a DLL is a file on disk (usually with a DLL extension) consisting of global data, compiled functions, and resources that
becomes part of your process. A DLL is compiled to load at a preferred base address, and if there's no conflict with other DLLs, the
file is mapped to the same virtual address in your process. The DLL has various exported functions, and the client program (the
program that loaded the DLL in the first place) imports those functions. Windows matches up the imports and exports when it loads
the DLL.
In Win32, each process gets its own copy of the DLL's read/write global variables. If you want to share memory among processes,
you must use a memory-mapped file or declare a shared data section, as described in Jeffrey Richter's Programming Applications
for Microsoft Windows (Microsoft Press, 1999). Whenever your DLL requests heap memory, that memory is allocated from the client
process's heap.
How Imports Are Matched to Exports
A DLL contains a table of exported functions. These functions are identified to the outside world by their symbolic names and
(optionally) by integers called ordinal numbers. The function table also contains the addresses of the functions within the DLL. When
the client program first loads the DLL, it doesn't know the addresses of the functions it needs to call, but it does know the symbols or
ordinals. The dynamic linking process then builds a table that connects the client's calls to the function addresses in the DLL. If you
edit and rebuild the DLL, you don't need to rebuild your client program unless you've changed function names or parameter
sequences.
In the DLL code, you must explicitly declare your exported functions as follows. (The alternative is to list your exported functions in a
module-definition [DEF] file, but that's usually more troublesome.)
__declspec(dllexport) int MyFunction(int n);
On the client side, you must declare the corresponding imports like this:
__declspec(dllimport) int MyFunction(int n);
If you're using C++, the compiler generates a decorated name for MyFunction that other languages can't use. These decorated
names are the long names that the compiler invents based on class name, function name, and parameter types. They are listed in
the project's MAP file. If you want to use the plain name MyFunction, you have to write the declarations in this way:
extern "C" __declspec(dllexport) int MyFunction(int n);
extern "C" __declspec(dllimport) int MyFunction(int n);
Just having import declarations isn't enough to make a client link to a DLL. The client's project must specify the import library (LIB) to
the linker, and the client program must actually contain a call to at least one of the DLL's imported functions. That call statement
must be in an executable path in the program.




Note Win32 DLLs allow exported global variables as well as functions.
Note In a simple world, you'd have one EXE file that imports functions from one or more DLLs. In the real world, many DLLs call
functions inside other DLLs. Thus, a particular DLL can have both exports and imports. This is not a problem because the
dynamic linkage process can handle cross-dependencies.
Note By default, the compiler uses the __cdecl argument-passing convention, which means that the calling program pops the
parameters off the stack. Some client languages might require the __stdcall convention, which replaces the Pascal calling
convention and results in the called function popping the stack. As a result, you might have to use the __stdcall modifier in
your DLL export declaration.
Page 118 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Implicit Linkage vs. Explicit Linkage
The preceding section primarily described implicit linking, which is what C++ programmers will probably use for their DLLs. When
you build a DLL, the linker produces a companion import LIB file, which contains every DLL's exported symbols and (optionally)
ordinals, but no code. The LIB file is a surrogate for the DLL that's added to the client program's project. When you build (statically
link) the client, the imported symbols are matched to the exported symbols in the LIB file, and those symbols (or ordinals) are bound
into the EXE file. The LIB file also contains the DLL filename (but not its full pathname), which gets stored in the EXE file. When the
client is loaded, Windows finds and loads the DLL and then dynamically links it by symbol or by ordinal.
Explicit linking is more appropriate for interpreted languages such as Microsoft JScript, but you can use it from C++ if you need to.
With explicit linking, you don't use an import file; instead, you call the Win32 LoadLibrary function, specifying the DLL's pathname as
a parameter. LoadLibrary returns an HINSTANCE parameter that you can use in a call to GetProcAddress, which converts a symbol
(or an ordinal) to an address inside the DLL.
Suppose you have a DLL that exports a function such as this:
extern "C" __declspec(dllexport) double SquareRoot(double d);
Here's an example of a client's explicit linkage to the function:
typedef double (SQRTPROC)(double);
HINSTANCE hInstance;
SQRTPROC* pFunction;
VERIFY(hInstance = ::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));
VERIFY(pFunction = (SQRTPROC*)::GetProcAddress(hInstance, "SquareRoot"));
double d = (*pFunction)(81.0); // Call the DLL function
With implicit linkage, all DLLs are loaded when the client is loaded, but with explicit linkage, you can determine when DLLs are
loaded and unloaded. Explicit linkage allows you to determine at run time which DLLs to load. You can, for example, have one DLL
with string resources in English and another with string resources in Spanish. Your application will load the appropriate DLL after the
user chooses a language.
Symbolic Linkage vs. Ordinal Linkage
In Win16, the more efficient ordinal linkage was the preferred linkage option. In Win32, the efficiency of symbolic linkage has been
improved. Microsoft now recommends symbolic over ordinal linkage. The DLL version of the MFC library, however, uses ordinal
linkage.
A typical MFC program might link to hundreds of functions in the MFC DLL. Ordinal linkage permits that program's EXE file to be
smaller because it does not have to contain the long symbolic names of its imports. If you build your own DLL with ordinal linkage,
you must specify the ordinals in the project's DEF file, which doesn't have too many other uses in the Win32 environment. If your
exports are C++ functions, you must use decorated names in the DEF file (or declare your functions with extern "C").
Here's a short extract from one of the MFC library DEF files:
??0CRecentFileList@@QAE@IPBD0HH@Z @ 479 NONAME
??0CRecordset@@QAE@PAVCDatabase@@@Z @ 480 NONAME
??0CRecordView@@IAE@I@Z @ 481 NONAME
??0CRecordView@@IAE@PBD@Z @ 482 NONAME
??0CRectTracker@@QAE@PBUtagRECT@@I@Z @ 483 NONAME
??0CReObject@@QAE@PAVCRichEditCntrItem@@@Z @ 484 NONAME
??0CReObject@@QAE@XZ @ 485 NONAME
??0CResetPropExchange@@QAE@XZ @ 486 NONAME
??0CRichEditCntrItem@@QAE@PAU_reobject@@PAVCRichEditDoc@@@Z @ 487 NONAME
??0CRichEditDoc@@IAE@XZ @ 488 NONAME
??0CRichEditView@@QAE@XZ @ 489 NONAME
??0CScrollView@@IAE@XZ @ 490 NONAME
??0CSemaphore@@QAE@JJPBDPAU_SECURITY_ATTRIBUTES@@@Z @ 491 NONAME
??0CSharedFile@@QAE@II@Z @ 492 NONAME
The numbers after the @ symbols are the ordinals. (Kind of makes you want to use symbolic linkage instead, doesn't it?)
The DLL Entry Point: DllMain
By default, the linker assigns the main entry point _DllMainCRTStartup to your DLL. When Windows loads the DLL, it calls this
function, which first calls the constructors for global objects and then calls the global function DllMain, which you're supposed to
write. DllMain is called not only when the DLL is attached to the process but also when it is detached (and at other times as well).
Here's a skeleton DllMain function:
HINSTANCE g_hInstance;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
Page 119 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("Ex20a.DLL Initializing!\n");
// Do initialization here
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("Ex20a.DLL Terminating!\n");
// Do cleanup here
}
return 1; // ok
}
If you don't write a DllMain function for your DLL, a do-nothing version will be brought in from the runtime library.
The DllMain function is also called when individual threads are started and terminated, as indicated by the dwReason parameter.
Richter's book tells you all you need to know about this complex subject.
Instance Handles: Loading Resources
Each DLL in a process is identified by a unique 32-bit HINSTANCE value. In addition, the process itself has an HINSTANCE value.
All these instance handles are valid only within a particular process, and they represent the starting virtual address of the DLL or
EXE. In Win32, the HINSTANCE and HMODULE values are the same and the types can be used interchangeably. The process
(EXE) instance handle is almost always 0x400000, and the handle for a DLL loaded at the default base address is 0x10000000. If
your program uses several DLLs, each will have a different HINSTANCE value, either because the DLLs had different base
addresses specified at build time or because the loader copied and relocated the DLL code.
Instance handles are particularly important for loading resources. The Win32 FindResource function takes an HINSTANCE
parameter. EXEs and DLLs can each have their own resources. If you want a resource from the DLL, you specify the DLL's instance
handle. If you want a resource from the EXE file, you specify the EXE's instance handle.
How do you get an instance handle? If you want the EXE's handle, you call the Win32 GetModuleHandle function with a NULL
parameter. If you want the DLL's handle, you call the Win32 GetModuleHandle function with the DLL name as a parameter. Later,
you'll see that the MFC library has its own method of loading resources by searching various modules in sequence.
How the Client Program Finds a DLL
If you link explicitly using LoadLibrary, you can specify the DLL's full pathname. If you don't specify the pathname, or if you link
implicitly, Windows will follows this search sequence to locate your DLL:
1. The directory containing the EXE file
2. The process's current directory
3. The Windows system directory
4. The Windows directory
5. The directories listed in the Path environment variable
Here's a trap you can easily fall into. You build a DLL as one project, copy the DLL file to the system directory, and then run the DLL
from a client program. So far, so good. Next, you rebuild the DLL with some changes, but you forget to copy the DLL file to the
system directory. The next time you run the client program, it loads the old version of the DLL. Be careful!
Debugging a DLL
Visual C++ makes debugging a DLL easy. You just run the debugger from the DLL project. The first time you do this, the debugger
will ask for the pathname of the client EXE file. Every time you "run" the DLL from the debugger after this, the debugger will load the
EXE, but the EXE will use the search sequence to find the DLL. This means that you must either set the Path environment variable
to point to the DLL or copy the DLL to a directory in the search sequence.
MFC DLLs: Extension vs. Regular
We've been looking at Win32 DLLs that have a DllMain function and some exported functions. Now we'll move on to the MFC
application framework, which adds its own support layer on top of the Win32 basics. The MFC Application Wizard lets you build two
kinds of DLLs with MFC library support: extension DLLs and regular DLLs. You must understand the differences between these two
types so you can decide which one is best for your needs.
An extension DLL supports a C++ interface. In other words, the DLL can export whole classes and the client can construct objects of
those classes or derive classes from them. An extension DLL dynamically links to the code in the DLL version of the MFC library.




Note Of course, Visual C++ .NET lets you build a pure Win32 DLL without the MFC library, just as it lets you build a Windows-
based program without the MFC library.
Page 120 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Therefore, an extension DLL requires that your client program be dynamically linked to the MFC library (the MFC Application Wizard
default) and that both the client program and the extension DLL be synchronized to the same version of the MFC DLLs (mfc70.dll,
mfc70d.dll, and so on). Extension DLLs are quite small; you can build a simple extension DLL with a size of 10 KB, which will load
quickly.
If you need a DLL that can be loaded by any Win32 programming environment, you should use a regular DLL. A big restriction here
is that the regular DLL can export only C-style functions. It can't export C++ classes, member functions, or overloaded functions
because every C++ compiler has its own method of decorating names. You can, however, use C++ classes (and MFC library
classes, in particular) inside your regular DLL. Implementing a COM interface for your DLL also solves the issue of integrating with
Visual Basic.
When you build an MFC regular DLL, you can choose to statically link or dynamically link to the MFC library. If you choose static
linking, your DLL will include a copy of all the MFC library code it needs and will thus be self-contained. A typical release-build
statically linked regular DLL is about 144 KB. If you choose dynamic linking, the size will drop to about 17 KB but you'll have to
ensure that the proper MFC DLLs are present on the target machine. That's no problem if the client program is already dynamically
linked to the same version of the MFC library.
When you tell the MFC wizards what kind of DLL or EXE you want, compiler #define constants are set as shown in the following
table.
If you look inside the MFC source code and header files, you'll see a lot of #ifdef statements for these constants. This means that
the library code is compiled quite differently depending on the kind of project you're producing.
MFC Extension DLLs: Exporting Classes
If your extension DLL contains only exported C++ classes, you'll have an easy time building and using it. The steps shown later for
building the Ex20a example show you how to tell the MFC DLL Wizard that you're building an extension DLL skeleton. That skeleton
has only the DllMain function. You simply add your own C++ classes to the project. There's only one special thing you must do: You
must add the macro AFX_EXT_CLASS to the class declaration, as shown here:
class AFX_EXT_CLASS CStudent : public CObject
This modification goes into the H file that's part of the DLL project, and it also goes into the H file that client programs use. In other
words, the H files are exactly the same for both client and DLL. The macro generates different code depending on the situationit
exports the class in the DLL and imports the class in the client.
The MFC Extension DLL Resource Search Sequence
If you build a dynamically linked MFC client application, many of the MFC library's standard resources (error message strings, print
preview dialog templates, and so on) will be stored in the MFC DLLs, but your application will have its own resources, too. When
you call an MFC function such as CString::LoadString or CBitmap::LoadBitmap, the framework will step in and search first the EXE
file's resources and then the MFC DLL's resources.
If your program includes an extension DLL and your EXE needs a resource, the search sequence will be first the EXE file, then the
extension DLL, and then the MFC DLLs. If you have a string resource ID, for example, that is unique among all resources, the MFC
library will find it. If you have duplicate string IDs in your EXE file and your extension DLL file, the MFC library will load the string in
the EXE file.
If the extension DLL loads a resource, the sequence will be first the extension DLL, then the MFC DLLs, and then the EXE.
You can change the search sequence if you need to. Suppose you want your EXE code to search the extension DLL's resources
first. You can use code such as this:
HINSTANCE hInstResourceClient = AfxGetResourceHandle();
// Use DLL's instance handle
AfxSetResourceHandle(::GetModuleHandle("mydllname.dll"));
CString strRes;
strRes.LoadString(IDS_MYSTRING);
// Restore client's instance handle
AfxSetResourceHandle(hInstResourceClient);
You can't use AfxGetInstanceHandle instead of ::GetModuleHandle. In an extension DLL, AfxGetInstanceHandle returns the EXE's
instance handle, not the DLL's handle.
The Ex20a Example: An MFC Extension DLL

Dynamically Linked to Shared MFC
Library
Statically Linked to MFC Library
Regular DLL _AFXDLL, _USRDLL _USRDLL
Extension DLL _AFXEXT, _AFXDLL Unsupported option
Client EXE _AFXDLL No constants defined
Page 121 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
This example makes an extension DLL out of the CPersistentFrame class you saw in Chapter 14. First you'll build the Ex20a.dll
file, and then you'll use it in a test client program, Ex20b.
Here are the steps for building the Ex20a example:
1. Run the MFC DLL Wizard to produce the Ex20a project. Choose New Project from the Visual Studio .NET File menu.
Select Visual C++ Projects, and then select MFC DLL from the list of templates. On the Application Settings page, select the
MFC Extension DLL, as shown here:

2. Examine the Ex20a.cpp file. The MFC DLL Wizard generates the following code, which includes the DllMain function:
// Ex20a.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include <afxdllx.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

static AFX_EXTENSION_MODULE Ex20aDLL = { NULL, NULL };

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("Ex20a.DLL Initializing!\n");

// Extension DLL one-time initialization
if (!AfxInitExtensionModule(Ex20aDLL, hInstance))
return 0;

// Insert this DLL into the resource chain
// NOTE: If this Extension DLL is being implicitly
// linked to by an MFC Regular DLL
// (such as an ActiveX Control) instead of an
// MFC application, then you will want to remove
// this line from DllMain and put it in a separate
// function exported from this Extension DLL.
// The Regular DLL that uses this Extension DLL
// should then explicitly call that function to
// initialize this Extension DLL.
// Otherwise, the CDynLinkLibrary object will not be
// attached to the Regular DLL's resource chain,
// and serious problems will result.

new CDynLinkLibrary(Ex20aDLL);

}
else if (dwReason == DLL_PROCESS_DETACH)
{
Page 122 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
TRACE0("Ex20a.DLL Terminating!\n");

// Terminate the library before destructors are called
AfxTermExtensionModule(Ex20aDLL);
}
return 1; // ok
}
3. Insert the CPersistentFrame class into the project. Choose Add Existing Item from the Project menu and locate the files
Persist.h and Persist.cpp in the Ex14a folder on the companion CD. Add the class to the current project.
4. Edit the Persist.h file. Modify the line
class CPersistentFrame : public CFrameWnd
to read
class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd
5. Build the project and copy the DLL file. Copy the file Ex20a.dll from the \vcppnet\Ex20a\Debug directory to your system
directory.
The Ex20b Example: A DLL Test Client Program
This example starts off as a client for Ex20a.dll. It imports the CPersistentFrame class from the DLL and uses it as a base class
for the SDI frame window. Later, we'll add code to load and test the other sample DLLs in this chapter.
Here are the steps for building the Ex20b example:
1. Run the MFC Application Wizard to produce the Ex20b project. This is an ordinary MFC EXE program. Select Single
Document. Otherwise, accept the default settings. Be absolutely sure that you accept the Use MFC In A Shared DLL option
on the Application Type page.
2. Copy the file persist.h from the \vcppnet\Ex20a directory. Note that you're copying the header file, not the CPP file.
3. Change the CFrameWnd base class to CPersistentFrame, as you did in Ex14a. Replace all occurrences of CFrameWnd
with CPersistentFrame in both MainFrm.h and MainFrm.cpp. Also insert the following line into MainFrm.h:
#include "persist.h"
4. Add the Ex20a import library to the linker's input library list. Choose Add Existing Item from the Visual Studio .NET
Project menu.
5. Locate the Ex20a.lib file in the \vcppnet\Ex20a\Debug directory on the companion CD.
6. Build and test the Ex20b program. If you run the program from the debugger and Windows can't find the Ex20a DLL,
Windows will display a message box when Ex20b starts. If all goes well, you should have a persistent frame application that
works exactly like the one in Ex14a. The only difference is that the CPersistentFrame code will be in an extension DLL.
MFC Regular DLLs: The AFX_EXTENSION_MODULE Structure
When the MFC DLL Wizard generates a regular DLL, the DllMain function will be inside the framework and you'll end up with a
structure of type AFX_EXTENSION_MODULE (and a global instance of the structure). AFX_EXTENSION_MODULE is used during
initialization of MFC extension DLLs to hold the state of extension DLL module.
You usually don't need to do anything with this structure. You normally just write C functions and then export them using the
__declspec(dllexport) modifier (or using entries in the project's DEF file).
Using the AFX_MANAGE_STATE Macro
When mfc70.dll is loaded as part of a process, it stores data in some truly global variables. If you call MFC functions from an MFC
program or extension DLL, mfc70.dll will know how to set these global variables on behalf of the calling process. If you call into
mfc70.dll from a regular MFC DLL, however, the global variables will not be synchronized and the effects will be unpredictable. To
solve this problem, insert the following line at the start of all exported functions in your regular DLL:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
If the MFC code is statically linked, the macro will have no effect.
The MFC Regular DLL Resource Search Sequence
When an EXE links to a regular DLL, resource loading functions inside the EXE will load the EXE's own resources. Resource
loading functions inside the regular DLL will load the DLL's own resources.
If you want your EXE code to load resources from the DLL, you can use AfxSetResourceHandle to temporarily change the resource
Page 123 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
handle. If you're writing an application that needs to be localized, you can put language-specific strings, dialog boxes, menus, and
so forth in an MFC regular DLL. You might, for example, include the modules English.dll, German.dll, and French.dll. Your client
program will explicitly load the correct DLL and load the resources using regular resource-management function calls, which will
have the same IDs in all the DLLs.
The Ex20c Example: An MFC Regular DLL
This example creates a regular DLL that exports a single square root function. First we'll build the Ex20c.dll file, and then we'll
modify the test client program, Ex20b, to test the new DLL.
Here are the steps for building the Ex20c example:
1. Run the MFC DLL Wizard to produce the project Ex20c. Proceed as you did for Ex20a, but accept Regular DLL Using
Shared MFC DLL (instead of selecting MFC Extension DLL) on the Application Settings page.
2. Examine the Ex20c.cpp file. The MFC DLL Wizard generates the following code:
// Ex20c.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include "Ex20c.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//
// Note!
// If this DLL is dynamically linked against the MFC
// DLLs, any functions exported from this DLL which
// call into MFC must have the AFX_MANAGE_STATE macro
// added at the very beginning of the function.
//
// For example:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // normal function body here
// }
//
// It is very important that this macro appear in each
// function, prior to any calls into MFC. This means that
// it must appear as the first statement within the
// function, even before any object variable declarations
// as their constructors may generate calls into the MFC
// DLL.
//
// Please see MFC Technical Notes 33 and 58 for additional
// details.
//

// CEx20cApp
BEGIN_MESSAGE_MAP(CEx20cApp, CWinApp)
END_MESSAGE_MAP()

// CEx20cApp construction
CEx20cApp::CEx20cApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

// The one and only CEx20cApp object
CEx20cApp theApp;


// CEx20cApp initialization
BOOL CEx20cApp::InitInstance()
{
CWinApp::InitInstance();

return TRUE;
Page 124 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
}
3. Add the code for the exported Ex20cSquareRoot function. It's okay to add this code in the Ex20c.cpp file, although
you can use a new file if you want to:
extern "C" __declspec(dllexport) double Ex20cSquareRoot(double d)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
TRACE("Entering Ex20cSquareRoot\n");
if (d >= 0.0) {
return sqrt(d);
}
AfxMessageBox("Can't take square root of a negative number.");
return 0.0;
}
You can see that there's no problem with the DLL displaying a message box or another modal dialog box. You'll need to
include math.h in the file that contains this code.
Be sure to prototype the Ex20cSquareRoot function in the Ex20c.h file so external clients can see it.
4. Build the project and copy the DLL file. Copy the file Ex20c.dll from the \vcppnet\Ex20c\Debug directory to your system
directory.
Updating the Ex20b Example: Adding Code to Test Ex20c.dll
When we built the Ex20b program, it linked dynamically to the Ex20a MFC extension DLL. Now we'll update the project to implicitly
link to the Ex20c MFC regular DLL and to call the DLL's square root function.
Here are the steps for updating the Ex20b example:
1. Add a new dialog resource and class to the Ex20b project. Use the dialog editor to create the IDD_EX20C template, as
shown here:

Use the Add Class Wizard to add a class CTest20cDialog that is derived from CDialog. The controls, data members, and
message map function are shown in the following table.
2. Code the OnBnClickedCompute function to call the DLL's exported function. Edit the generated function in
Test20cDialog.cpp as shown here:
void CTest20cDialog::OnBnClickedCompute()
{
UpdateData(TRUE);
m_dOutput = Ex20cSquareRoot(m_dInput);
UpdateData(FALSE);
}
You must declare the Ex20cSquareRoot function as an imported function. Add the following line to the Test20cDialog.h
file:
extern "C" __declspec(dllimport) double Ex20cSquareRoot(double d);
3. Integrate the CTest20cDialog class into the Ex20b application. You must add a top-level menu, Test, and an Ex20c DLL
Control ID Type Data Member Message Map Function
IDC_INPUT Edit control m_dInput
(double)

IDC_OUTPUT Edit control m_dOutput
(double)

IDC_COMPUTE Button

OnBnClickedCompute
Page 125 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
option with the ID ID_TEST_EX20CDLL. Use Class View's Properties window to map this option to a member function in the
CEx20bView class, and then code the handler in Ex20bView.cpp as follows:
void CEx20bView::OnTestEx20cdll()
{
CTest20cDialog dlg;
dlg.DoModal();
}
Of course, you must add the following line to the Ex20bView.cpp file:
#include "Test20cDialog.h"
4. Add the Ex20c import library to the linker's input library list. Choose Add Existing Item from the Visual Studio .NET
Project menu, and then add \vcppnet\Ex20c\Debug\ Ex20c.lib to the project. Now the program should implicitly link to both
the Ex20a DLL and the Ex20c DLL. As you can see, the client doesn't care whether the DLL is a regular DLL or an extension
DLL. You just specify the LIB name to the linker.
5. Build and test the updated Ex20b application. Choose Ex20c DLL from the Test menu. Type a number in the Input edit
control, and then click the Compute Sqrt button. The result should appear in the Output control.
A Custom Control DLL
Programmers have been using DLLs for custom controls since the early days of Windows because custom controls are neatly self-
contained. The original custom controls were written in pure C and configured as standalone DLLs. Today, you can use the features
of the MFC library in your custom controls, and the wizards help make coding easier. A regular DLL is the best choice for a custom
control because the control doesn't need a C++ interface and it can be used by any development system that accepts custom
controls (such as the Borland C++ compiler). You'll probably want to use the MFC dynamic linking option because the resulting DLL
will be small and quick to load.
What Is a Custom Control?
You've seen ordinary controls in Chapter 7 Windows common controls in Chapter 8 and ActiveX controls in Chapter 9 The custom
control acts like an ordinary control, such as the edit control, in that it sends WM_COMMAND notification messages to its parent
window and receives user-defined messages. The dialog editor lets you position custom controls in dialog templates. That's what
the custom control button on the control palette is for.
You have a lot of freedom in designing your custom control. You can paint anything you want in its window (which is managed by
the client application), and you can define any notification and inbound messages you need. You can use Class View's Properties
window to map normal Windows messages in the control (WM_LBUTTONDOWN, for example), but you must manually map the
user-defined messages and manually map the notification messages in the parent window class.
A Custom Control's Window Class
A dialog resource template specifies its custom controls by their symbolic window class names. Don't confuse the Win32 window
class with the C++ class; the only similarity is the name. A window class is defined by a structure that contains the following:
The name of the class
A pointer to the WndProc function that receives messages sent to windows of the class
Miscellaneous attributes, such as the background brush
The Win32 RegisterClass function copies the structure into process memory so that any function in the process can use the class to
create a window. When the dialog window is initialized, Windows creates the custom control child windows from the window class
names stored in the template.
Suppose that the control's WndProc function is inside a DLL. When the DLL is initialized (by a call to DllMain), it can call
RegisterClass for the control. Because the DLL is part of the process, the client program can create child windows of the custom
control class. To summarize, the client knows the name string of a control window class and it uses that class name to construct the
child window. All the code for the control, including the WndProc function, is inside the DLL. All that's necessary is that the client
load the DLL before creating the child window.
The MFC Library and the WndProc Function
Okay, so Windows calls the control's WndProc function for each message sent to that window. But you really don't want to write an
old-fashioned switch-case statementyou want to map those messages to C++ member functions, as you've been doing all along.
Now, in the DLL, you must rig up a C++ class that corresponds to the control's window class. Once you've done that, you can use
Class View's Properties window to map messages.
The obvious part is the writing of the C++ class for the control. You simply use the Add Class Wizard to create a new class that's
derived from CWnd. The tricky part is wiring the C++ class to the WndProc function and to the application framework's message




Page 126 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
pump. You'll see a real WndProc in the Ex20d example, but here's the pseudocode for a typical control WndProc function:
LRESULT MyControlWndProc(HWND hWnd, UINT message
WPARAM wParam, LPARAM lParam)
{
if (this is the first message for this window) {
CWnd* pWnd = new CMyControlWindowClass();
attach pWnd to hWnd
}
return AfxCallWndProc(pWnd, hWnd, message, WParam, lParam);
}
The MFC AfxCallWndProc function passes messages to the framework, which dispatches them to the member functions mapped in
CMyControlWindowClass.
Custom Control Notification Messages
The control communicates with its parent window by sending it special WM_COMMAND notification messages with parameters, as
shown here:
The meaning of the notification code is arbitrary and depends on the control. The parent window must interpret the code based on
its knowledge of the control. For example, the code 77 might mean that the user typed a character while positioned on the control.
The control might send a notification message such as this:
GetParent()->SendMessage(WM_COMMAND,
GetDlgCtrlID() | ID_NOTIFYCODE << 16, (LONG) GetSafeHwnd());
On the client side, you map the message with the MFC ON_CONTROL macro, like this:
ON_CONTROL(ID_NOTIFYCODE, IDC_MYCONTROL, OnClickedMyControl)
You then declare the handler function like this:
afx_msg void OnClickedMyControl();
User-Defined Messages Sent to the Control
User-defined messages (described in Chapter 7) are the means by which the client program communicates with the control.
Because a standard message returns a 32-bit value if it is sent rather than posted, the client can obtain information from the control.
The Ex20d Example: A Custom Control
The Ex20d program is an MFC regular DLL that implements a traffic light control indicating off, red, yellow, and green states. When
clicked with the left mouse button, the DLL sends a clicked notification message to its parent and responds to two user-defined
messages, RYG_SETSTATE and RYG_GETSTATE. The state is an integer that represents the color. Credit for this example goes
to Richard Wilton, who included the original C-language version of this control in his book Windows 3 Developer's Workshop
(Microsoft Press, 1991).
The Ex20d project was originally generated using the MFC DLL Wizard, with linkage to the shared MFC DLL, just like Ex20c. The
following is the code for the primary source file, with the added code in the InitInstance function in boldface. The dummy exported
Ex20dEntry function exists solely to allow the DLL to be implicitly linked. The client program must include a call to this function. That
call must be in an executable path in the program or the compiler will eliminate the call. Alternatively, the client program can call the
Win32 LoadLibrary function in its InitInstance function to explicitly link the DLL.
Ex20d.cpp
// Ex20d.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include "Ex20d.h"

#include "rygwnd.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif
Parameter Usage
(HIWORD) wParam Notification code
(LOWORD) wParam Child window ID
lParam Child window handle
Page 127 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

extern "C" __declspec(dllexport) void Ex20dEntry() {} // dummy function

// Application Wizard comments removed.

// CEx20dApp

BEGIN_MESSAGE_MAP(CEx20dApp, CWinApp)
END_MESSAGE_MAP()

// CEx20dApp construction
CEx20dApp::CEx20dApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

// The one and only CEx20dApp object
CEx20dApp theApp;

// CEx20dApp initialization
BOOL CEx20dApp::InitInstance()
{
CRygWnd::RegisterWndClass(AfxGetInstanceHandle());
CWinApp::InitInstance();

return TRUE;
}
The following is the code for the CRygWnd class, including the global RygWndProc function. You can use the Add Class Wizard to
create this class by choosing Add Class from the Project menu. The code that paints the traffic light isn't very interesting, so we'll
concentrate on the functions that are common to most custom controls. The static RegisterWndClass member function actually
registers the RYG window class and must be called as soon as the DLL is loaded. The OnLButtonDown handler is called when the
user presses the left mouse button inside the control window. It sends the clicked notification message to the parent window. The
overridden PostNcDestroy function is important because it deletes the CRygWnd object when the client program destroys the
control window. The OnGetState and OnSetState functions are called in response to user-defined messages sent by the client.
Remember to copy the DLL to your system directory.
RygWnd.h
#pragma once

#define RYG_SETSTATE WM_USER + 0
#define RYG_GETSTATE WM_USER + 1

LRESULT CALLBACK AFX_EXPORT
RygWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

// CRygWnd
class CRygWnd : public CWnd
{
private:
int m_nState; // 0=off, 1=red, 2=yellow, 3=green

static CRect s_rect;
static CPoint s_point;
static CRect s_rColor[3];
static CBrush s_bColor[4];

public:
static BOOL RegisterWndClass(HINSTANCE hInstance);
DECLARE_DYNAMIC(CRygWnd)

public:
CRygWnd();
virtual ~CRygWnd();

private:
void SetMapping(CDC* pDC);
void UpdateColor(CDC* pDC, int n);

protected:
Page 128 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
afx_msg LRESULT OnSetState(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnGetState(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()

};
RygWnd.cpp
// RygWnd.cpp : implementation file
//
#include "stdafx.h"
#include "Ex20d.h"
#include "RygWnd.h"

LRESULT CALLBACK AFX_EXPORT
RygWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());

CWnd* pWnd;

pWnd = CWnd::FromHandlePermanent(hWnd);
if (pWnd == NULL) {
// Assume that client created a CRygWnd window
pWnd = new CRygWnd();
pWnd->Attach(hWnd);
}
ASSERT(pWnd->m_hWnd == hWnd);
ASSERT(pWnd == CWnd::FromHandlePermanent(hWnd));
LRESULT lResult = AfxCallWndProc(pWnd, hWnd, message,
wParam, lParam);
return lResult;
}


// static data members
CRect CRygWnd::s_rect(-500, 1000, 500, -1000); // outer rectangle
CPoint CRygWnd::s_point(300, 300); // rounded corners
CRect CRygWnd::s_rColor[] = {CRect(-250, 800, 250, 300),
CRect(-250, 250, 250, -250),
CRect(-250, -300, 250, -800)};
CBrush CRygWnd::s_bColor[] = {RGB(192, 192, 192),
RGB(0xFF, 0x00, 0x00),
RGB(0xFF, 0xFF, 0x00),
RGB(0x00, 0xFF, 0x00)};

BOOL CRygWnd::RegisterWndClass(HINSTANCE hInstance) // static member
// function
{
WNDCLASS wc;
wc.lpszClassName = "RYG"; // matches class name in client
wc.hInstance = hInstance;
wc.lpfnWndProc = RygWndProc;
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hIcon = 0;
wc.lpszMenuName = NULL;
wc.hbrBackground = (HBRUSH) ::GetStockObject(LTGRAY_BRUSH);
wc.style = CS_GLOBALCLASS;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
return (::RegisterClass(&wc) != 0);
}

// CRygWnd
IMPLEMENT_DYNAMIC(CRygWnd, CWnd)
CRygWnd::CRygWnd()
{
m_nState = 0;
TRACE("CRygWnd constructor\n");
}

CRygWnd::~CRygWnd()
Page 129 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
{
TRACE("CRygWnd destructor\n");
}

BEGIN_MESSAGE_MAP(CRygWnd, CWnd)
ON_MESSAGE(RYG_SETSTATE, OnSetState)
ON_MESSAGE(RYG_GETSTATE, OnGetState)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

void CRygWnd::SetMapping(CDC* pDC)
{
CRect clientRect;
GetClientRect(clientRect);
pDC->SetMapMode(MM_ISOTROPIC);
pDC->SetWindowExt(1000, 2000);
pDC->SetViewportExt(clientRect.right, -clientRect.bottom);
pDC->SetViewportOrg(clientRect.right / 2, clientRect.bottom / 2);
}

void CRygWnd::UpdateColor(CDC* pDC, int n)
{
if (m_nState == n + 1) {
pDC->SelectObject(&s_bColor[n+1]);
}
else {
pDC->SelectObject(&s_bColor[0]);
}
pDC->Ellipse(s_rColor[n]);
}

// CRygWnd message handlers
void CRygWnd::OnPaint()
{
int i;
CPaintDC dc(this); // device context for painting
SetMapping(&dc);
dc.SelectStockObject(DKGRAY_BRUSH);
dc.RoundRect(s_rect, s_point);
for (i = 0; i < 3; i++) {
UpdateColor(&dc, i);
}
}

void CRygWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
// Notification code is HIWORD of wParam, 0 in this case
GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID(),
(LONG) GetSafeHwnd()); // 0
}

void CRygWnd::PostNcDestroy()
{
TRACE("CRygWnd::PostNcDestroy\n");
delete this; // CWnd::PostNcDestroy does nothing
}

LRESULT CRygWnd::OnSetState(WPARAM wParam, LPARAM lParam)
{
TRACE("CRygWnd::SetState, wParam = %d\n", wParam);
m_nState = (int) wParam;
Invalidate(FALSE);
return 0L;
}

LRESULT CRygWnd::OnGetState(WPARAM wParam, LPARAM lParam)
{
TRACE("CRygWnd::GetState\n");
return m_nState;
}
Page 130 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
Revising the Updated Ex20b Example: Adding Code to Test Ex20d.dll
The Ex20b program already links to the Ex20a and Ex20c DLLs. Now we'll revise the project to implicitly link to the Ex20d custom
control.
Here are the steps for updating the Ex20b example:
1. Add a new dialog resource and class to the Ex20b project.Use the dialog editor to create the IDD_EX20D template with a
custom control with the child window ID IDC_RYG, as shown here:

Specify RYG as the window class name of the custom control using the dialog editor's Properties window.
Then use the Add Class Wizard to generate a class CTest20dDialog that is derived from CDialog.
2. Edit the Test20dDialog.h file. Add the following private data member:
enum { OFF, RED, YELLOW, GREEN } m_nState;
Also add the following import and user-defined message IDs:
extern "C" __declspec(dllimport) void Ex20dEntry(); // dummy
// function
#define RYG_SETSTATE WM_USER + 0
#define RYG_GETSTATE WM_USER + 1
3. Edit the constructor in Test20dDialog.cpp to initialize the state data member. Add the following boldface code:
CTest20dDialog::CTest20dDialog(CWnd* pParent /*=NULL*/)
: CDialog(CTest20dDialog::IDD, pParent)
{
m_nState = OFF;
Ex20dEntry(); // Make sure DLL gets loaded
}
4. Map the control's clicked notification message. You can't use Class View's Properties window here, so you must add the
message map entry and handler function in the Test20dDialog.cpp file, as shown here:
void CTest20dDialog::OnClickedRyg()
{
switch(m_nState) {
case OFF:
m_nState = RED;
break;
case RED:
m_nState = YELLOW;
break;
case YELLOW:
m_nState = GREEN;
break;
case GREEN:
m_nState = OFF;
break;
}
GetDlgItem(IDC_RYG)->SendMessage(RYG_SETSTATE, m_nState);
return;
}

BEGIN_MESSAGE_MAP(CTest20dDialog, CDialog)
ON_CONTROL(0, IDC_RYG, OnClickedRyg) // Notification code is 0
END_MESSAGE_MAP()
When the dialog box gets the clicked notification message, it sends the RYG_SETSTATE message back to the control in
Page 131 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
order to change the color. Don't forget to add this prototype in the Test20dDialog.h file:
afx_msg void OnClickedRyg();
5. Integrate the CTest20dDialog class into the Ex20b application.
6. You'll need to add a second command to the Test menuan Ex20d DLL option with the ID ID_TEST_EX20DDLL. Use
Class View's Properties window to map this option to a member function in the CEx20bView class, and then code the handler
in Ex20bView.cpp as follows:
void CEx20bView::OnTestEx20ddll()
{
CTest20dDialog dlg;
dlg.DoModal();
}
Of course, you have to add the following line to Ex20bView.cpp:
#include "Test20dDialog.h"
7. Add the Ex20d import library to the linker's input library list. Choose Add Existing Item from the Project menu. Add
\vcppnet\Ex20d\Debug\Ex20.lib to the project. With this addition, the program should implicitly link to all three DLLs.
8. Build and test the updated Ex20b application. Choose Ex20d DLL from the Test menu. Try clicking the traffic light with
the left mouse button. The traffic-light color should change.
Chapter 21: MFC Programs Without Document or View Classes
Overview
The document-view architecture is useful for many applications, but sometimes a simpler program structure is sufficient. This
chapter includes three sample applications: a dialog boxbased program, a Single Document Interface (SDI) program, and a
Multiple Document Interface (MDI) program. None of these programs uses document, view, or document-template classes, but they
all use command routing and some other Microsoft Foundation Class (MFC) library features. In Microsoft Visual C++ .NET, you can
create all three types of applications using the MFC Application Wizard.
In each example, we'll look at how the MFC Application Wizard generates code that doesn't rely on the document-view architecture
and how you can add your own code.
The Ex21a Example: A Dialog BoxBased Application
For many applications, a dialog box is a sufficient user interface. The dialog box appears when the user starts the application. The
user can minimize the dialog box, and as long as the dialog box is not system modal, the user can freely switch to other
applications.
In this example, the dialog box functions as a simple calculator, as shown in Figure 21-1. The Add Member Variable Wizard takes
care of defining the class data members and generating the DDX (Dialog Data Exchange) function callseverything but the coding
of the compute function. The application's resource script, Ex21a.rc, defines an icon as well as the dialog box.

Figure 21-1: The Ex21a Calculator dialog box
The MFC Application Wizard gives you the option of generating a dialog boxbased application. Here are the steps for building the
Ex21a example:
1. Run the MFC Application Wizard to produce the Ex21a project. Select the Dialog Based option on the Application Type
page, as shown here:








Page 132 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

On the User Interface Features page, enter Ex21a Calculator as the dialog box title.
2. Edit the IDD_EX21A_DIALOG resource. Referring to Figure 21-1 as a guide, use the dialog editor to assign IDs to the
controls shown in the following table. Then open the dialog box's Properties window. Set the System Menu and Minimize Box
properties to True.
3. Use the Add Member Variable Wizard to add member variables, and use Class View's Properties window to add a
command handler. The MFC Application Wizard has already generated a class CEx21aDlg. Add the following data
members:
Add the message handler OnBnClickedCompute for the IDC_COMPUTE button.
4. Code the OnBnClickedCompute member function in the Ex21aDlg.cpp file. Add the following boldface code:
void CEx21aDlg::OnBnClickedCompute()
{
UpdateData(TRUE);
if(m_nOperation == 0) {
m_dResult = m_dLeft + m_dRight;
} else if(m_nOperation == 1) {
m_dResult = m_dLeft - m_dRight;
} else if(m_nOperation == 2) {
m_dResult = m_dLeft * m_dRight;
} else if(m_nOperation == 3) {
if(m_dRight == 0) {
AfxMessageBox("Divide by zero");
} else {
m_dResult = m_dLeft / m_dRight;
}
}
UpdateData(FALSE);
}
5. Build and test the Ex21a application. Notice that the program's icon appears on the Windows taskbar. Verify that you can
minimize the dialog box.
Control ID
Left operand edit control IDC_LEFT
Right operand edit control IDC_RIGHT
Result edit control IDC_RESULT
First radio button (group property set) IDC_OPERATION
Compute button IDC_COMPUTE
Control ID Member Variable Type
IDC_LEFT m_dLeft Double
IDC_RIGHT m_dRight Double
IDC_RESULT m_dResult Double
IDC_OPERATION m_nOperation int
Page 133 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
The Application Class InitInstance Function
The critical element of the Ex21a application is the CEx21aApp::InitInstance function generated by the MFC Application Wizard. A
normal InitInstance function creates a main frame window and returns TRUE, which allows the program's message loop to run. The
Ex21a version constructs a modal dialog object, calls DoModal, and then returns FALSE. This means that the application exits after
the user exits the dialog box. The DoModal function lets the Windows dialog procedure get and dispatch messages, as it always
does. Note that the MFC Application Wizard does not generate a call to CWinApp::SetRegistryKey.
Here's the generated InitInstance code from Ex21a.cpp:
BOOL CEx21aApp::InitInstance()
{
// InitCommonControls() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
InitCommonControls();
CWinApp::InitInstance();
AfxEnableControlContainer();

CEx21aDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
The Dialog Class and the Program Icon
The generated CEx21aDlg class contains these two message map entries:
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
The associated handler functions take care of displaying the application's icon when the user minimizes the program. This code
applies only to Microsoft Windows NT version 3.51, in which the icon is displayed on the desktop. You don't need these handlers for
Windows 95/98/Me or Windows NT 4.0/2000/XP because those versions of Windows display the program's icon directly on the
taskbar.
There is some icon code that you do need. It's in the dialog box's OnInitDialog handler, which is generated by the MFC Application
Wizard. Notice the two SetIcon calls in the OnInitDialog function code shown below. If you selected the About box option, the MFC
Application Wizard will generate code to add an About box to the System menu. The variable m_hIcon is a data member of the
dialog class that is initialized in the constructor.
BOOL CEx21aDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX, strAboutMenu);
Page 134 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
}
}
// Set the icon for this dialog. The framework does this
// automatically when the application's main window
// is not a dialog.
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
The Ex21b Example: An SDI Application
This SDI "Hello, world!" example builds on the code you saw way back in Chapter 2. The application has only one windowan
object of a class derived from CFrameWnd. All drawing occurs inside the frame window, and all messages are handled there.
1. Run the MFC Application Wizard to produce the Ex21b project. Select the Single Document option on the Application
Type page and deselect the Document/View Architecture Support option, as shown here:

2. Add code to paint in the view. Add the following boldface code to the CChildView::OnPaint function in the ChildView.cpp
source code file:
void CChildView::OnPaint()
{
CPaintDC dc(this); // device context for painting

dc.TextOut(0, 0, "Hello, world!");

// Do not call CWnd::OnPaint() for painting messages
}
3. Compile and run the application. You now have a complete SDI application that has no dependencies on the document-
view architecture.
The MFC Application Wizard automatically takes out dependencies on the document-view architecture and generates an application
for you that has the following elements:
Main menu You can have a Windows-based application without a menuyou don't even need a resource script. But Ex21b
has both. The application framework routes menu commands to message handlers in the frame class.
Icon An icon is useful if the program is to be activated from Windows Explorer. It's also useful when the application's main
frame window is minimized. The icon is stored in the resource, along with the menu.
Window close message command handler Many applications need to do special processing when the main window is
closed. If you're using documents, you can override the CDocument::SaveModified function. But here, to take control of the
close process, the MFC Application Wizard creates message handlers to process close messages sent as a result of user
actions and by Windows itself when it shuts down.
Toolbar and status bar The MFC Application Wizard automatically generates a default toolbar and status bar for you and sets
up the routing even though there are no document-view classes.
Several interesting features in the SDI application have no document-view support, including:
CChildView class Contrary to its name, this class is actually a CWnd derivative that is declared in ChildView.h and
implemented in ChildView.cpp. CChildView implements only a virtual OnPaint member function, which contains any code
that you want to draw in the frame window (as illustrated in step 2 of the Ex21b sample).




Page 135 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm
CMainFrame class This class contains a data member, m_wndView, that is created and initialized in the
CMainFrame::OnCreate member function.
CMainFrame::OnSetFocus function This function makes sure the focus is translated to the CChildView:
void CMainFrame::OnSetFocus(CWnd* pOldWnd)
{
// forward focus to the view window
m_wndView.SetFocus();
}
CMainFrame::OnCmdMsg function This function gives the view a chance to handle any command messages first:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
// let the view have first crack at the command
if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// otherwise, do default handling
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
The Ex21c Example: An MDI Application
Now let's create an MDI application that doesn't use the document-view architecture.
1. Run the MFC Application Wizard to produce the Ex21c project. Select the Multiple Documents option on the Application
Type page and deselect Document/View Architecture Support.
2. Add code to paint in the dialog box.Add the following boldface code to the CChildView::OnPaint function in the
ChildView.cpp source code file:
void CChildView::OnPaint()
{
CPaintDC dc(this); // device context for painting

dc.TextOut(0, 0, "Hello, world!");

// Do not call CWnd::OnPaint() for painting messages
}
3. Compile and run the application. You now have a complete MDI application without any dependencies on the document-
view architecture.
As in Ex21b, this example automatically creates a CChildView class. The main difference between Ex21b and Ex21c is that
in Ex21c the CChildView class is created in the CChildFrame::OnCreate function instead of in the CMainFrame class.
Now that you've learned how to create three kinds of applications that do not depend on the document-view architecture, you can
examine how they're generated to learn how MFC works. Try comparing the generated results to similar applications with document-
view architecture support to get a complete picture of how the document-view classes work with the rest of MFC.






Page 136 of 136 Part III: MFC's Document-View Architecture
03-05-2014 file://L:\TEMP\~hhA60B.htm

You might also like