Special Edition Using Visual C++ 5 - Chapter 5

## Chapter 5

## Documents and Views

- How document objects declare their data

  Any information your application plans to save must be added to the document class.

- How view objects access the document's data

  It is the view class that shows the data to the user: it asks the document for the data.

- Other view classes in MFC

  MFC provides 10 different view classes from which your class can inherit.

- Document templates

  Connections between views and documents are simple to arrange.

When you generate your source code with AppWizard, you get an application featuring all the bells and whistles of a commercial Windows 95 application, including a toolbar, a status bar, ToolTips, menus, and even an About dialog box. However, in spite of all those features, the application really doesn't do anything useful. In order to create an application that does more than look pretty on your desktop, you've got to modify the code that AppWizard generates. This task can be easy or complex, depending upon how you want your application to look and act.

Probably the most important set of modifications are those related to the document—the information the user can save from your application and restore later—and to the view—the way that information is presented to the user. MFC's document/view architecture separates an application's data from the way the user actually views and manipulates that data. Simply, the document object is responsible for storing, loading, and saving the data, whereas the view object (which is just another type of window) enables the user to see the data on-screen and to edit that data in a way that is appropriate to the application. In this chapter, you learn the basics of how MFC's document/view architecture works.

### Understanding the Document Class

SDI and MDI applications created with AppWizard are document/view applications. That means that AppWizard generates a class for you, derived from CDocument, and delegates certain tasks to this new document class. It also creates a view class, derived from CView, and delegates other tasks to the your new view class. Let's look through an AppWizard starter application and see what you get.

Choose File, New, and select the Projects tab. Fill in the project name as App1 and fill in an appropriate directory for the project files. Make sure that MFC AppWizard (exe) is selected. Click OK.

Move through the AppWizard dialog boxes, changing the settings to match those in the following list, then click Next to continue:

Step 1: Multiple documents

Step 2: Don't change the defaults presented by AppWizard

Step 3: Don't change the defaults presented by AppWizard

Step 4: Deselect all check boxes except Printing and Print Preview

Step 5: Don't change the defaults presented by AppWizard

Step 6: Don't change the defaults presented by AppWizard

After you click Finish on the last step, the New project information box summarizes your work. Click OK to create the project. Expand the App1 classes in ClassView, and you see that six classes have been created: CAboutDlg, CApp1App, CApp1Doc, CApp1View, CChildFrame, and CMainframe.

CApp1Doc represents a document; it holds the application's document data. You add storage for the document by adding data members to the CApp1Doc class. To see how this works, look at Listing 5.1, which shows the header file AppWizard creates for the CApp1Doc class.

Listing 5.1—APP1DOC.H—The Header File for the CApp1Doc Class

    // App1Doc.h : interface of the CApp1Doc class

    //

    /////////////////////////////////////////////////////////////////////////////

    #if !defined(AFX_APP1DOC_H__43BB481D_64AE_11D0_9AF3_0080C81A397C__INCLUDED_)

    #define AFX_APP1DOC_H__43BB481D_64AE_11D0_9AF3_0080C81A397C__INCLUDED_

    class CApp1Doc : public CDocument

    {

    protected: // create from serialization only

         CApp1Doc();

         DECLARE_DYNCREATE(CApp1Doc)

    // Attributes

    public:

    // Operations

    public:

    // Overrides

         // ClassWizard generated virtual function overrides

         //{{AFX_VIRTUAL(CApp1Doc)

         public:

         virtual BOOL OnNewDocument();

         virtual void Serialize(CArchive& ar);

         //}}AFX_VIRTUAL

    // Implementation

    public:

         virtual ~CApp1Doc();

    #ifdef _DEBUG

         virtual void AssertValid() const;

         virtual void Dump(CDumpContext& dc) const;

    #endif

    protected:

    // Generated message map functions

    protected:

         //{{AFX_MSG(CApp1Doc)

              // NOTE - the ClassWizard will add and remove member functions here.

              //    DO NOT EDIT what you see in these blocks of generated code !

         //}}AFX_MSG

         DECLARE_MESSAGE_MAP()

    };

    /////////////////////////////////////////////////////////////////////////////

    //{{AFX_INSERT_LOCATION}}

    // Microsoft Developer Studio will insert additional declarations immediately before the previous line.

    #endif // !defined(AFX_APP1DOC_H__43BB481D_64AE_11D0_9AF3_0080C81A397C__INCLUDED_)

Near the top of the listing, you can see the class declaration's Attributes section, which is followed by the public keyword. This is where you declare the data members that will hold your application's data. In the program that you create a little later in this chapter, the application must store an array of CPoint objects as the application's data. That array is declared as a member of the document class like this:

    // Attributes

    public:

        CPoint points[100];

CPoint is an MFC class that encapsulates the information relevant to a point on the screen, most importantly the x and y co-ordinates of the point.

Notice also in the class's header file that the CApp1Doc class includes two virtual member functions called OnNewDocument() and Serialize(). MFC calls the OnNewDocument() function whenever the user selects the File, New command (or its toolbar equivalent, if a New button has been implemented in the application). You can use this function to perform whatever initialization must be performed on your document's data. In an SDI application, the open document is closed and a new blank document is loaded into the same object; in an MDI application a blank document is opened in addition to documents that are already open. The Serialize() member function is where the document class loads and saves its data. This is discussed in Chapter 8, "Persistence and File I/O."

### Understanding the View Class

As mentioned previously, the view class displays the data stored in the document object and enables the user to modify this data. The view object keeps a pointer to the document object, which it uses to access the document's member variables in order to display or modify them. Listing 5.2 is the header file for Capp1View, as generated by AppWizard.

> Most MFC programmers add public member variables to their documents to make it easy for the view class to access them. A more object-oriented approach is to add private or protected member variables, and then add public functions to get or change the values of these variables.

Listing 5.2—APP1VIEW.H—The Header File for the CApp1View Class

    // App1View.h : interface of the CApp1View class

    //

    /////////////////////////////////////////////////////////////////////////////

    #if !defined(AFX_APP1VIEW_H__43BB481F_64AE_11D0_9AF3_0080C81A397C__INCLUDED_)

    #define AFX_APP1VIEW_H__43BB481F_64AE_11D0_9AF3_0080C81A397C__INCLUDED_

    class CApp1View : public CView

    {

    protected: // create from serialization only

         CApp1View();

         DECLARE_DYNCREATE(CApp1View)

    // Attributes

    public:

         CApp1Doc* GetDocument();

    // Operations

    public:

    // Overrides

         // ClassWizard generated virtual function overrides

         //{{AFX_VIRTUAL(CApp1View)

         public:

         virtual void OnDraw(CDC* pDC);  // overridden to draw this view

    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

         protected:

         virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

         virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

         virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

         //}}AFX_VIRTUAL

    // Implementation

    public:

         virtual ~CApp1View();

    #ifdef _DEBUG

         virtual void AssertValid() const;

         virtual void Dump(CDumpContext& dc) const;

    #endif

    protected:

    // Generated message map functions

    protected:

         //{{AFX_MSG(CApp1View)

              // NOTE - the ClassWizard will add and remove member functions here.

              //    DO NOT EDIT what you see in these blocks of generated code !

         //}}AFX_MSG

         DECLARE_MESSAGE_MAP()

    };

    #ifndef _DEBUG  // debug version in App1View.cpp

    inline CApp1Doc* CApp1View::GetDocument()

       { return (CApp1Doc*)m_pDocument; }

    #endif

    /////////////////////////////////////////////////////////////////////////////

    //{{AFX_INSERT_LOCATION}}

    // Microsoft Developer Studio will insert additional declarations immediately before the previous line.

    #endif // !defined(AFX_APP1VIEW_H__43BB481F_64AE_11D0_9AF3_0080C81A397C__INCLUDED_)

Near the top of the listing, you can see the class's public attributes, where it declares the GetDocument() function as returning a pointer to a CApp1Doc object. Anywhere in the view class that you need to access the document's data, you can call GetDocument() to obtain a pointer to the document. For example, to add a CPoint object to the aforementioned array of CPoint objects stored as the document's data, you might use the following line:

    GetDocument()->m_points[x] = point;

You also can do this a little differently, of course, by storing the pointer returned by GetDocument() in a local pointer variable and then using that pointer variable to access the document's data, like this:

    pDoc = GetDocument();

    pDoc->m_points[x] = point;

The second version is more convenient when you need to use the document pointer in several places in the function, or if using the less clear GetDocument()->variable version makes the code hard to understand.

> In release versions of your program, the GetDocument() function is inline, which means there is no performance advantage to saving the pointer like this, but it does improve readability. Inline functions are expanded into your code like macros, but offer type checking and other advantages, as discussed in the electronic book C++ By Example, included on the CD with this book.

Notice that the view class, like the document class, overrides a number of virtual functions from its base class. As you'll soon see, the OnDraw() function, which is the most important of these virtual functions, is where you paint your window's display. As for the other functions, MFC calls PreCreateWindow() before the window element (that is, the actual Windows window) is created and attached to the MFC window class, giving you a chance to modify the window's attributes (such as size and position). These two functions are discussed in more detail in Chapter 6, "Drawing on the Screen." OnPreparePrinting() is used to modify the Print dialog box before it displays for the user; the OnBeginPrinting() function gives you a chance to create GDI objects like pens and brushes that you need to handle the print job; and OnEndPrinting() is where you can destroy any objects you may have created in OnBeginPrinting(). These three functions are discussed in Chapter 7, "Printing and Print Preview."

> When you first start using an application frameworklike MFC, it's easy to get confused about the difference between an object instantiated from an MFC class and the Windows element it represents. For example, when you create an MFC frame-window object, you're actually creating two things: the MFC object that has member functions and member variables, and a Windows window that you can manipulate using the functions of the MFC object. The window element is associated with the MFC class, but is also an entity unto itself.

### Creating the Rectangles Application

Now that you've had an introduction to documents and views, a little hands-on experience should help you better understand how these classes work. In the steps that follow, you build the Rectangles application, which demonstrates the manipulation of documents and views. When you first run this application, it will draw an empty window. Wherever you click in the window, a small rectangle will be drawn. You can resize the window, or minimize and restore it, and the rectangles will be redrawn at all the coordinates where you clicked. This is accomplished by keeping an array of coordinate points in the document and using that array in the view.

> The complete source code and executable file for the Rectangles application can be found in the CHAP06\RECS directory of this book's CD-ROM.

First, use AppWizard to create the basic files for the Rectangles program, selecting the options listed in the following table. (AppWizard is first discussed in Chapter 1, "Building Your First Application." When you're done, the New Project Information dialog box appears; it should look like Figure 5.1. Click the OK button to create the project files.

Dialog Box Name | Options to Select
New Project | Name the project recs, and set the project path to the directory into which you want to store the project's files. Leave the other options set to their defaults.
Step 1 | Select Single Document.
Step 2 of 6 | Leave set to defaults.
Step 3 of 6 | Leave set to defaults.
Step 4 of 6 | Turn off all application features except Printing and Print Preview.
Step 5 of 6 | Leave set to defaults.
Step 6 of 6 | Leave set to defaults.

Fig. 5.1

When you create an SDI application with AppWizard, the project information summary confirms your settings

Now that you have a starter application, it's time to add code to the document and view classes in order to create an application that actually does something. This application will draw many rectangles in the view and save the coordinates of the rectangles in the document.

Follow these steps to add the code that modifies the document class to handle the application's data, which is an array of CPoint objects that determine where rectangles should be drawn in the view window:

1. Click the ClassView tab to display the ClassView in the project workspace window at the left of the screen.

2. Expand the Recs classes by clicking on the + sign before them.

3. Right-click the CRecsDoc class, and choose Add Member Variable from the shortcut menu that appears.

4. Fill in the Add Member Variable dialog box. For Variable Type, enter CPoint. For Variable Declaration, enter m_points[100]. Make sure the Public radio button is selected. Click OK.

5. Again, right-click the CRecsDoc class, and choose Add Member Variable.

6. For Variable Type, enter UINT. For Variable Declaration, enter m_pointIndex. Make sure the Public radio button is selected. Click OK.

7. Click the + next to CRecsDoc in ClassView to see the member variables and functions. The two member variables you added are now listed.

The m_points[] array holds the locations of rectangles displayed in the view window. The m_pointIndex data member holds the index of the next empty element of the array.

> If you've programmed in C++ before and are not used to the ClassView, you can open RecsDoc.h from the FileView and add (after a public: specifier) the two lines of code that declare these variables:

>     UINT m_pointIndex;

>          CPoint m_points[100];

Now you need to get these variables initialized to appropriate values and then use them to draw the view. MFC applications that use the document/view paradigm initialize document data in a function called OnNewDocument(), which is called automatically when the application first runs and whenever the user chooses File, New.

The list of member variables and functions of CRecsDoc should still be displayed in ClassView. Double-click OnNewDocument() in that list to edit the code. Using Listing 5.3 as a guide, remove the comments left by AppWizard and initialize m_pointIndex to zero.

Listing 5.3—RecsDoc.cpp—CRecsDoc::OnNewDocument()

    BOOL CRecsDoc::OnNewDocument()

    {

         if (!CDocument::OnNewDocument())

              return FALSE;

        m_pointIndex = 0;

         return TRUE;

    }

There is no need to initialize the array of points, since the index into the array will be used to ensure no code tries to use an uninitialized element of the array. At this point your modifications to the document class are complete. As you'll see in Chapter 9, "Persistence and File I/O," there are a few simple changes to make if you want this information actually saved in the document. In order to focus on the way documents and views work together, you will not be making those changes to the Recs application.

Now turn your attention to the view class. It will use the document data to draw rectangles on-screen. A full discussion of the way that drawing works must wait for Chapter 6, "Drawing on the Screen." For now it is enough to know that the OnDraw() function of your view class does the drawing. Expand the CRecsView class in ClassView and double-click OnDraw(). Using Listing 5.4 as a guide, remove the comments left by AppWizard and add code to draw a rectangle at each point in the array.

Listing 5.4—RECSVIEW.CPP—CRecsView::OnDraw()

    void CRecsView::OnDraw(CDC* pDC)

    {

        CRecsDoc* pDoc = GetDocument();

        ASSERT_VALID(pDoc);

        UINT pointIndex = pDoc->m_pointIndex;

        for (UINT i=0; i<pointIndex; ++i)

        {

            UINT x = pDoc->m_points[i].x;

            UINT y = pDoc->m_points[i].y;

            pDC->Rectangle(x, y, x+20, y+20);

        }

    }

Your modifications to the starter application generated by AppWizard are almost complete. You have added member variables to the document, initialized those variables in the document's OnNewDocument() function, and used those variables in the view's OnDraw() function. All that remains is to enable the user to add points to the array. You catch the mouse message with ClassWizard and then add code to the message handler. Follow these steps:

1. Choose View, ClassWizard. The ClassWizard dialog box appears.

2. Make sure that CRecsView is selected in the Class Name and Object IDs boxes. Then, double-click WM_LBUTTONDOWN in the Messages box to add the OnLButtonDown() message-response function to the class. Whenever the application receives a WM_LBUTTONDOWN message, it will call OnLButtonDown().

3. Click the Edit Code button to jump to the OnLButtonDown() function in your code. Then, add the code shown in Listing 5.5 to the function.

Listing 5.5—RECSVIEW.CPP—CRecsView::OnLButtonDown()

    void CRecsView::OnLButtonDown(UINT nFlags, CPoint point)

    {

        CRecsDoc *pDoc = GetDocument();

        // don't go past the end of the 100 points allocated

        if (pDoc->m_pointIndex == 100)

           return;

        //store the click location

        pDoc->m_points[pDoc->m_pointIndex] = point;

        pDoc->m_pointIndex++;

        pDoc->SetModifiedFlag();

        Invalidate();

        CView::OnLButtonDown(nFlags, point);

    }

The new OnLButtonDown() adds a point to the document's point array each time the user clicks the left mouse button over the view window. It increments m_pointIndex so that the next click goes into the point on the array after this one.

The call to SetModifiedFlag() marks this document as modified, or "dirty." MFC automatically prompts the user to save any dirty files on exit. (The details are in Chapter 8, "Persistence and File I/O.") Any code you write that changes any document variables should call SetModifiedFlag().

> Earlier in this chapter you were reminded that public access functions in the document had some advantages. One such advantage: any document member function that changed a variable also could call SetModifiedFlag(), thus guaranteeing no programmer could forget it.

Finally, the call to Invalidate() causes MFC to call the OnDraw() function, where the window's display is redrawn with the new data. Invalidate() takes a single parameter (with the default value TRUE) that determines if the background is erased before calling OnDraw(). On rare occasions you may choose to call Invalidate(FALSE), so that OnDraw() draws over whatever was already on the screen.

Finally, a call to the base class OnLButtonDown() takes care of the rest of the work involved in handling a mouse click.

You've now finished the complete application. Click the toolbar's Build button, or choose Build, Build command from the menu bar, to compile and link the application. After you have the Rectangles application compiled and linked, run it by choosing Build, Execute. When you do, you see the application's main window. Place your mouse pointer over the window's client area and click. A rectangle appears. Go ahead and keep clicking. You can place up to 100 rectangles in the window (see Figure 5.2).

Fig. 5.2

The Rectangles application draws rectangles wherever you click.

### Other View Classes

The view classes generated by AppWizard in this chapter's sample applications have been derived from MFC's CView class. There are cases, however, when it is to your advantage to derive your view class from one of the other MFC view classes that are themselves derived from CView. These additional classes provide your view window with special capabilities such as scrolling and text editing. Table 5.1 lists the various view classes along with their descriptions.

Table 5.1—View classes

Class | Description

    CView

The base view class from which the specialized view classes are derived

    CScrollView

A view class that provides scrolling capabilities

    CCtrlView

A base class from which view classes that implement new Windows 95 common controls (such as the ListView, TreeView, and RichEdit controls) are derived

    CEditView

A view class that provides basic text-editing features

    CRichEditView

A view class that provides more sophisticated text-editing capabilities by using the Windows 95 RichEdit control

    CListView

A view class that displays a Windows 95 ListView control in its window

    CTreeView

A view class that displays a Windows 95 TreeView control in its window

    CFormView

A view class that implements a form-like window using a dialog box resource

    CRecordView

A view class that can display database records along with controls for navigating the database

    CDaoRecordView

Same as CRecordView, except used with the new DAO database classes

To use one of these classes, you just substitute the desired class for the CView class in the application's project. When using AppWizard to generate your project, you can specify the view class you want in the wizard's Step 6 of 6 dialog box, as shown in Figure 5.3. Once you have the desired class installed as the project's view class, you can use the specific class's member functions to control the view window. Chapter 6, "Drawing on the Screen," demonstrates using the CScrollView class to implement a scrolling view.

Fig. 5.3

You can use AppWizard to select your application's base view class.

A CEditView object, on the other hand, gives you all the features of a Windows edit control in your view window. Using this class, you can handle various editing and printing tasks, including find-and-replace. You can retrieve or set the current printer font by calling the GetPrinterFont() or SetPrinterFont() member function or get the currently selected text by calling GetSelectedText(). Moreover, the FindText() member function locates a given test string, and OnReplaceAll() replaces all occurrences of a given text string with another string.

The CRichEditView class adds many features to an edit view, including paragraph formatting (such as centered, right-aligned, and bulleted text), character attributes (including underlined, bold, and italic), and the capability to set margins, fonts, and paper size. As you may have guessed, the CRichEditView class features a rich set of methods you can use to control your application's view object.

Figure 5.4 shows how the view classes fit into MFC's class hierarchy. Describing these various view classes fully is beyond the scope of this chapter. However, you can find plenty of information about them in your Visual C++ online documentation.

Fig. 5.4

The view classes all trace their ancestry back to CView.

### Document Templates, Views, and Frame Windows

Because you've been working with AppWizard-generated applications in this chapter, you've taken for granted a lot of what goes on in the background of an MFC document/view program. That is, much of the code that enables the frame window (your application's main window), the document, and the view window to work together is automatically generated by AppWizard and manipulated by MFC.

For example, if you look at the InitInstance() method of the Rectangles application's CRecsApp class, you see (among other things) the lines shown in Listing 5.6.

Listing 5.6—RECS.CPP—Initializing an Application's Document

    CSingleDocTemplate* pDocTemplate;

    pDocTemplate = new CSingleDocTemplate(

         IDR_MAINFRAME,

         RUNTIME_CLASS(CRecsDoc),

         RUNTIME_CLASS(CMainFrame),

         RUNTIME_CLASS(CRecsView));

    AddDocTemplate(pDocTemplate);

In Listing 5.6, you discover one of the secrets that makes the document/view system work. In that code, the program creates a document-template object. These document templates have nothing to do with C++ templates. They pull together the following objects:

- A resource ID identifying a menu resource: IDR_MAINFRAME in this case
- A document class: CRecsDoc in this case
- A frame window class: always CMainFrame
- A view class: CRecsView in this case

Notice that you are not passing an object, or a pointer to an object. You are passing the name of the class to a macro called RUNTIME_CLASS. It enables the framework to create instances of a class at runtime, which the application object must be able to do in a program that uses the document/view architecture. In order for this macro to work, the classes that will be created dynamically must be declared and implemented as such. To do this, the class must have the DECLARE_DYNCREATE macro in its declaration (in the header file) and the IMPLEMENT_DYNCREATE macro in its implementation. AppWizard takes care of this for you.

For example, If you look at the header file for the Rectangles application's CMainFrame class, you see the following line near the top of the class's declaration:

    DECLARE_DYNCREATE(CMainFrame)

As you can see, the DECLARE_DYNCREATE macro requires the class's name as its single argument.

Now, if you look near the top of CMainFrame's implementation file (MAINFRM.CPP), you see this line:

    IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

The IMPLEMENT_DYNCREATE macro requires as arguments the name of the class and the name of the base class.

If you explore the application's source code further, you find that the document and view classes also contain the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros.

If you haven't heard of frame windows before, you should know that they contain all the windows involved in the applications—this means control bars as well as views. They also route messages and commands to views and documents, as discussed in Chapter 4, "Messages and Commands."

The last line of Listing 5.6 calls AddDocTemplate() in order to pass the object on to the application object, CRecsApp, which keeps a list of documents. AddDocTemplate() adds this document to this list and uses the document template to create the document object, the frame, and the view window.

Since this is a Single Document Interface, a single document template (CSingleDocTemplate) is created. Multiple Document Interface applications use one CMultiDocTemplate object for each kind of document they support. For example, a spreadsheet program might have two kinds of documents: tables and graphs. Each would have its own view and its own set of menus. Two instances of CMultiDocTemplate would be created in InitInstance(), each pulling together the menu, document, and view that belong together. If you've ever seen the menus in a program change as you switched from one view or document to another, now you know how you can achieve the same effect: simply associate them with different menu resource IDs as you build the document templates.

### From Here...

In this chapter, you examined how an AppWizard-generated application uses MFC to coordinate an application's document and view objects. There is, of course, a great deal more to learn about MFC before you can create your own sophisticated Windows 95 applications. If you'd like to learn more about some topics presented in this chapter, refer to the following:

- Chapter 6, "Drawing on the Screen," demonstrates how to display data in an application's window.
- Chapter 7, "Printing and Print Preview," shows how to include these powerful features in your application.
- Chapter 8, "Persistence and File I/O," provides the details of how to use MFC's file-handling classes.
- Appendix B, "The Developer Studio User Interface," explains how to control your programming projects with Developer Studio.

© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.