Chapter 6

The Document/View Paradigm


CONTENTS

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, tool tips, 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.

Before you can perform any modifications, however, you have to know about MFC's document/view architecture, which is a way to separate 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 the screen and to edit that data as is appropriate to the application. In the sections that follow, you learn the basics of how MFC's document/view architecture works.

Understanding the Document Class

Suppose you create a basic AppWizard application named App1, after which you examine the various files generated by AppWizard. You find a class called CApp1Doc, which was derived from MFC's CDocument class. In the App1 application, CApp1Doc is the class from which the application instantiates its document object, which is responsible for holding the application's document data. It's up to you to add storage for the document by adding data members to the CApp1Doc class.

To see how this works, look at Listing 6.1, which shows the header file AppWizard creates for the CApp1Doc class.


Listing 6.1  APP1DOC.H-The Header File for the CApp1Doc Class

// app1Doc.h : interface of the CApp1Doc class

//

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



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()

};



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


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];

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 exists). You can use this function to perform whatever initialization must be performed on your document's data. The Serialize() member function is where the document class loads and saves its data.

Understanding the View Class

As I mentioned previously, the view class is responsible for displaying and enabling the user to modify the data stored in the document object. To do this, the view object must be able to obtain a pointer to the document object. After obtaining this pointer, the view object can access the document's data members in order to display or modify them. If you look at Listing 6.2, you can see how the view class obtains a pointer to the document object.


Listing 6.2  APP1VIEW.H-The Header File for the CApp1View Class

// app1View.h : interface of the CApp1View class

//

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



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



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


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 could, of course, do this a little differently 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.

Notice that the view class, like the document class, also 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). Finally, the OnPreparePrinting() function enables you to modify the Print dialog box before it's displayed to 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().

NOTE
When you first start using an application framework like 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 contains functions and data, 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. Follow the first steps to create the basic Rectangles application and modify its resources:

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

  1. Use AppWizard to create the basic files for the Rectangles program, selecting the options listed in the following table. When you're done, the New Project Information dialog box appears; it should look like Figure 6.1. Click the OK button to create the project files.
    Figure 6.1 : Your Project Information dialog box should look like this.

Dialog Box NameOptions to Select
New ProjectName 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 1Select Single Document.
Step 2 of 6Leave set to defaults.
Step 3 of 6Leave set to defaults.
Step 4 of 6Turn off all application features except Printing and Print Preview.
Step 5 of 6Leave set to defaults.
Step 6 of 6Leave set to defaults.

  1. Select the ResourceView tab in the project workspace window. Visual C++ displays the ResourceView window, as shown in Figure 6.2.
    Figure 6.2 : The ResourceView tab displays the ResourceView window.

  2. In the ResourceView window, click the plus sign next to recs resources to display the application's resources. Click the plus sign next to Menu, and then double-click the IDR_MAINFRAME menu ID. Visual C++'s menu editor appears.
  3. Click the Rectangles application's Edit menu (not Visual C++'s Edit menu), and then press your keyboard's Delete key to delete the Edit menu. When you do, a dialog box asks for verification of the delete command. Click the OK button.
  4. Double-click the About recsÉ item in the Help menu, and change it to About Rectangles. Close the menu editor.
  5. Double-click the Accelerator resource in the ResourceView window. Double-click the IDR_MAINFRAME accelerator ID to bring up the accelerator editor.
  6. Using your keyboard's arrow and Delete keys, delete all accelerators except ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_PRINT, and ID_FILE_SAVE. Close the accelerator editor.
  7. Double-click the Dialog resource in the ResourceView window. Double-click the IDD_ABOUTBOX dialog-box ID to bring up the dialog-box editor.
  8. Modify the dialog box by changing the title to About Rectangles, changing the first static text string to "Rectangles, Version 1.0" and adding the static string "by Macmillan Computer Publishing," as shown in Figure 6.3. Close the dialog-box editor.
    Figure 6.3 : Rectangles' About the box should look like this.

  9. Double-click the String Table resource in the ResourceView window. Double-click the String Table ID to bring up the string table editor.
  10. Double-click the IDR_MAINFRAME string, and then change the first segment of the string to "Rectangles," as shown in Figure 6.4. Close the string-table editor.
    Figure 6.4 : The first segment of the IDR_MAINFRAME string appears in your main window's title bar.

Now that you have the application's resources the way you want them, it's time to add code to the document and view classes in order to create an application that actually does something. 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 FileView tab to display the FileView window. Then, display the project's files by clicking the plus sign next to the folder labeled "recs files," as shown in Figure 6.5.
    Figure 6.5 : The FileView window lists the source files that make up your project.

  2. Double-click the recsDoc.cpp entry in the file list. The RECSDOC.CPP file appears in the code window. Click the Open Header button to display the class's header file.
  3. Add the following lines to the CRecsDoc class's attributes section, right after the public keyword:
    CPoint m_points[100];
    UINT m_pointIndex;
    These lines declare the document class's data members, which will store the application's data. In the case of the Rectangles application, the data in the m_points[] array represents 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.
  4. Close the RECSDOC.H file so that you can view and edit the RECSDOC.CPP file. Add the following line to the OnNewDocument() function, right after the (SDI documents will reuse this document) comment:
    m_pointIndex = 0;
    This line initializes the index variable each time a new document is started, ensuring that it always starts off by indexing the first element in the m_points[] array.
  5. Add the lines shown in Listing 6.3 to the Serialize() function, right after the TODO: add storing code here comment.

Listing 6.3  LST6_3.TXT-Code for Saving the Document's Data

ar << m_pointIndex;



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

{

    ar << m_points[i].x;

    ar << m_points[i].y;

}



    This is the code that saves the document's data. As you can see, you have to do nothing more than use the << operator to direct the data to the archive object. You'll look at this code in more detail later in this chapter.
  1. Add the lines shown in Listing 6.4 to the Serialize() function, right after the TODO: add loading code here comment.

Listing 6.4  LST6_4.TXT-Code for Loading the Document's Data

ar >> m_pointIndex;



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

{

    ar >> m_points[i].x;

    ar >> m_points[i].y;

}



UpdateAllViews(NULL);



    This is the code that loads the document's data. You need only use the >> operator to direct the data from the archive object into the document's data storage. You'll look at this code in more detail later in this chapter.

This finishes the modifications you must make to the document class. In the following steps, you make the appropriate changes to the view class, enabling the class to display, modify, and print the data stored in the document class:

  1. Load the RECSVIEW.CPP file, and add the lines shown in Listing 6.5 to the OnDraw() function, right after the TODO: add draw code for native data here comment.

Listing 6.5  LST6_5.TXT-Code for Displaying the Application's Data

    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);

    }


  1. The preceding code, which iterates through the document object's m_points[] array and displays rectangles at the coordinates it finds in the array, is executed whenever the application's window needs repainting.
  2. Add the following line to the very beginning of the OnPreparePrinting() function:
    pInfo->SetMaxPage(1);
    This line modifies the common Print dialog box such that the user cannot try to print more than one page.
  3. Click the toolbar's ClassWizard button, or select the View, ClassWizard command from the menu bar. The ClassWizard property sheet appears.
  4. 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.
    The OnLButtonDown() function is now associated with Windows' WM_LBUTTONDOWN message, which means MFC will call OnLButtonDown() whenever the application receives a WM_LBUTTONDOWN message.
  5. Click the Edit Code button to jump to the OnLButtonDown() function in your code. Then, add the lines shown in Listing 6.6 to the function, right after the TODO: Add your message handler code here and/or call default comment.

Listing 6.6  LST6_6.TXT-Code to Handle Left-Button Clicks

    CRecsDoc *pDoc = GetDocument();



    if (pDoc->m_pointIndex == 100)

        return;



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

    ++pDoc->m_pointIndex;

    pDoc->SetModifiedFlag();

    Invalidate();

CRecsDoc *pDoc = GetDocument();



    if (pDoc->m_pointIndex == 100) // if the index is greater than the 100 rect           	      ›;maximum leave the function

        return;



    pDoc->m_points[pDoc->m_pointIndex] = point; // get the present point

    ++pDoc->m_pointIndex;  //increment the pointIndex

    pDoc->SetModifiedFlag(); //"dirty" the file, see reference below for more              		         ›;infor about dirty files

    Invalidate(); //redraw the displayed window



    The preceding code segment adds a point to the document's point array each time the user clicks the left mouse button over the view window. The call to Invalidate() causes MFC to call the OnDraw() function, where the window's display is redrawn with the new data.

You've now finished the complete application. Click the toolbar's Build button or select the Build, Build command from the menu bar to compile and link the application.

Running the Rectangles Application

After you have the Rectangles application compiled and linked, run it by selecting Build, Execute from the menu bar. When you do, you see the application's main window. Place your mouse pointer over the window's client area and left click. A rectangle appears. Go ahead and keep clicking. You can place up to 100 rectangles in the window (see fig. 6.6).

Figure 6.6 : You can place up to 100 rectangles in the application's window.

To save your work (this is work?), select the File, Save command. You can view your document in print preview by selecting the File, Print Preview command, or just go ahead and print by selecting the File, Print command. Of course, you can create a new document by selecting File, New, or load a document you previously saved by selecting File, Open. Finally, if you click Help, About Rectangles, you see the application's About dialog box.

Exploring the Rectangles Application

If you don't have much experience with AppWizard and MFC, you're probably amazed at how much you can do with a few mouse clicks and a couple of dozen lines of code. You're also probably still a little fuzzy on how the program actually works, so in the following sections, you examine the key parts of the Rectangles application.

Declaring Storage for Document Data

As you've heard already in this chapter, it is the document object in an AppWizard-generated MFC program that is responsible for maintaining the data that makes up the application's document. For a word processor, this data would be strings of text, whereas for a paint program, this data might be a bitmap. For the Rectangles application, the document's data is the coordinates of rectangles displayed in the view window.

The first step in customizing the document class, then, is to provide the storage you need for your application's data. How you do this, of course, depends on the type of data you must use. But, in every case, the variables that will hold that data should be declared as data members of the document class, as is done in the Rectangles application. Listing 6.7 shows the relevant code.


Listing 6.7  LST6_7.TXT-Declaring the Rectangles Application's Document Data

// Attributes

public:



    CPoint m_points[100];

    UINT m_pointIndex;


In the preceding listing, the document-data variables m_points[] and m_pointIndex are declared as public members of the document class. (The m prefix indicates that the variables are members of the class, rather than global or local variables. This is a tradition that Microsoft started. You can choose to follow it or not.) The m_points[] array holds the coordinates of the rectangles displayed in the view window, and the m_pointIndex variable holds the number of the next empty element in the array. You can also think of m_pointIndex as the current rectangle count. These variables are declared as public so that the view class can access them. If you were to declare the data variables as protected or private, your compiler would whine loudly when you tried to access the variables from your view class's member functions.

The data storage for the Rectangles application is pretty trivial in nature. For a commercial- grade application, you'd almost certainly need to keep track of much more complex types of data. But the method of declaring storage for the document would be the same. You may, of course, also declare data members that you use only internally in the document class, variables that have little or nothing to do with the application's actual document data. However, you should declare such data members as protected or private.

Initializing Document Data

After you have your document's data declared, you usually need to initialize it in some way each time a new document is created. For example, in the Rectangles application, the m_pointIndex variable must be initialized to zero when a new document is started. Otherwise, m_pointIndex may contain an old value from a previous document, which could make correctly accessing the m_points[] array as tough as getting free cash from an ATM. In the Rectangles application, m_pointIndex gets initialized in the OnNewDocument() member function, as shown in Listing 6.8.


Listing 6.8  LST6_8.TXT-The Rectangles Application's OnNewDocument() Function

BOOL CRecsDoc::OnNewDocument()

{

    if (!CDocument::OnNewDocument())

        return FALSE;



    // TODO: add reinitialization code here

    // (SDI documents will reuse this document)

    m_pointIndex = 0;



    return TRUE;

}


MFC calls the OnNewDocument() function whenever the user starts a new document, usually by selecting File, New. As you can see, OnNewDocument() first calls the base class's OnNewDocument(), which calls DeleteContents() and then marks the new document as clean (meaning it doesn't yet need to be saved due to changes).

What's DeleteContents()? It's another virtual member function of the CDocument class. If you want to be able to delete the contents of a document without actually destroying the document object, you can override DeleteContents() to handle this task.

Keep in mind that how you use OnNewDocument() and DeleteContents() depends on whether you're writing an SDI or MDI application. In an SDI application, the OnNewDocument() function indirectly destroys the current document by reinitializing it in preparation for new data. An SDI application, after all, can contain only a single document at a time. In an MDI application, OnNewDocument() simply creates a brand new document object, leaving the old one alone. For this reason, in an MDI application, you can perform general document initialization in the class's constructor.

Serializing Document Data

If your application is going to be useful, it must be able to do more than display data; it must also be able to load and save data sets created by the user. Writing this chapter would have been a nightmare if my text disappeared every time I shut down my word processor! The act of loading and saving document data with MFC is called serialization. And, in spite of the complications you may have experienced with files in the past, loading and saving data with MFC is a snap, thanks to the CArchive class, an object of which is passed to the document class's Serialize() member function. Listing 6.9 shows the Rectangles application's Serialize() function.


Listing 6.9  LST6_9.TXT-The Rectangles Application's Serialize() Function

void CRecsDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        // TODO: add storing code here

        ar << m_pointIndex;



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

        {

            ar << m_points[i].x;

            ar << m_points[i].y;

        }

    }

    else

    {

        // TODO: add loading code here

        ar >> m_pointIndex;



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

        {

            ar >> m_points[i].x;

            ar >> m_points[i].y;

        }



        UpdateAllViews(NULL);

    }

}


As you can see in the listing, the Serialize() function receives a reference to a CArchive object as its single parameter. At this point, MFC has done all the file-opening work for you. All you have to do is use the CArchive object to load or save your data. How do you know which to do? MFC has already created the lines that call the CArchive object's IsStoring() member function, which returns TRUE if you need to save data and FALSE if you need to load data.

Thanks to the overloaded << and >> operators in the CArchive class, you can save and load data exactly as you're used to doing using C++ I/O objects. If you look at the Serialize() function, you may notice that about the only difference between the saving and loading of data is the operator that's used. One other difference is the call to UpdateAllViews() after loading data. UpdateAllViews() is the member function that notifies all views attached to this document that they need to redraw their data displays. When calling UpdateAllViews(), you'll almost always use NULL as the single parameter. If you should ever call UpdateAllViews() from your view class, you should send a pointer to the view as the parameter. You would only do this sort of thing, though, when using multiple views with a single document.

Displaying Document Data

Now that you've got your document class all ready to store, save, and load its data, you need to customize the view class so that it can display the document data, as well as enable the user to modify the data. In an MFC application using the document/view model, it's the view class's OnDraw() member function that is responsible for displaying data, either on the screen or the printer. Listing 6.10 shows the Rectangles application's version of OnDraw().


Listing 6.10  LST6_10.TXT-The Rectangles Application's OnDraw() Function

void CRecsView::OnDraw(CDC* pDC)

{

    CRecsDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);



    // TODO: add draw code for native data here

    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);

    }

}


The first thing you should notice about OnDraw() is that its single parameter is a pointer to a CDC object. A CDC object encapsulates a Windows' device context, automatically initializing the DC and providing many member functions with which you can draw your application's display. Because the OnDraw() function is responsible for updating the window's display, it's a nice convenience to have a CDC object all ready to go. (For more information on device contexts, see Chapter 11, "Drawing on the Screen.")

Also notice that, because an application that uses the document/view model stores its data in the document class, AppWizard has generously supplied the code needed to obtain a pointer to that class. In the custom code in OnDraw(), the function uses this document pointer to retrieve the value of the document's index variable (the number of rectangles currently displayed), and then uses it as a loop-control variable. The loop simply iterates through the document's m_points[] array, drawing rectangles at the coordinates contained in the CPoint objects stored in the array.

Modifying Document Data

The view object is not only responsible for displaying the application's document data; it must also (if appropriate) enable the user to edit that data. Exactly how you enable the user to edit an application's data depends a great deal upon the type of application you're building. The possibilities are endless. In the simple Rectangles application, the user can edit a document only by clicking in the view window, which adds another rectangle to the document. This happens in response to the WM_LBUTTONDOWN message, which Windows sends the application every time the user clicks the left mouse button when the mouse pointer is over the view window.

If you recall, you used ClassWizard to add the OnLButtonDown() function to the program. This is the function that MFC calls whenever the window receives a WM_LBUTTONDOWN message. It is in OnLButtonDown(), then, that the Rectangles application must modify its list of rectangles, adding the new rectangle at the window position the user clicked. Listing 6.11 shows the OnLButtonDown() function, where this data update occurs.


Listing 6.11  LST6_11.TXT-The Rectangles Application's OnLButtonDown() Function

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

{

    // TODO: Add your message handler code here and/or call default

    CRecsDoc *pDoc = GetDocument();



    if (pDoc->m_pointIndex == 100)

        return;



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

    ++pDoc->m_pointIndex;

    pDoc->SetModifiedFlag();

    Invalidate();



    CView::OnLButtonDown(nFlags, point);

}


Of the two parameters received by OnLButtonDown(), it is point that is most useful to the Rectangles application, because this CPoint object contains the coordinates at which the user just clicked. In the custom code you added to OnLButtonDown(), the function first obtains a pointer to the document object. Then, if the document object's m_pointIndex data member is equal to 100, there is no room for another rectangle. In this case, the function immediately returns, effectively ignoring the user's request to modify the document. Otherwise, the function adds the new point to the m_points[] array and increments the m_pointIndex variable.

Now that the document's data has been updated as per the user's modification, the document must be marked as "dirty" (needing saving) and the view must display the new data. A call to the document object's SetModifiedFlag() function takes care of the first task. If the user now tries to exit the program without saving the data, or tries to start a new document, MFC displays a dialog box warning the user of possible data loss. When the user saves the document's data, the document is set back to "clean." The call to Invalidate() notifies the view window that it needs repainting, which results in MFC calling the view object's OnDraw() function.

Other View Classes

Throughout this chapter, you've been using MFC's CView class for your view window. The truth is, however, that MFC offers several different view classes that are derived from CView. These additional classes provide your view window with special abilities such as scrolling and text editing. Table 6.1 lists the various view classes along with their descriptions.

Table 6.1  View Classes

ClassDescription
CViewThe base view class from which the specialized view classes are derived.
CScrollViewA view class that provides scrolling abilities.
CCtrlViewA base class from which view classes that implement new Windows 95 common controls (such as the ListView, TreeView, and RichEdit controls) are derived.
CEditViewA view class that provides basic text-editing features.
CRichEditViewA view class that provides more sophisticated text-editing abilities using the Windows 95 RichEdit control.
CListViewA view class that displays a Windows 95 ListView control in its window.
CTreeViewA view class that displays a Windows 95 TreeView control in its window.
CFormViewA view class that implements a form-like window using a dialog-box resource.
CRecordViewA view class that can display database records along with controls for navigating the database.
CDaoRecordViewSame 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 6.7. 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.

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

For example, when using the CScrollView class, you can call the SetScrollSizes() member function to set the view's dimensions, mapping mode, and the vertical and horizontal scroll amounts. In addition, you can retrieve the scroll position or the size of the view by calling the GetScrollPosition() or GetTotalSize() member functions, respectively. Other member functions provide additional abilities.

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 text 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 ability 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 appli-cation's view object.

Figure 6.8 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.

Figure 6.8 : 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'll see (among other stuff) the lines shown in Listing 6.12:


Listing 6.12  LST6_12.TXT-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 6.12, you discover one of the secrets that makes the document/view system work. In that code, the program creates a document-template object. (In this case, the template is of the CSingleDocTemplate class.) It is the document template that links together the application's resources, document class (in this case, CRecsDoc), frame window (always CMainFrame in an AppWizard program), and the view class (in this case, CRecsView).

What's all that RUNTIME_CLASS stuff? The RUNTIME_CLASS macro 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.

If you look at the header file for the Rectangles application's CMainFrame class, for example, 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'll find that the document and view classes also contain the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros.

After creating the document-template object, the program calls AddDocTemplate() in order to pass the object on to the application. The application then adds the document template to its list of documents. Finally, the application object uses the document template to create the document object, as well as the frame and view windows.

MFC programs support two types of document templates, one that represents SDI (single-document interface) applications, which can hold only one document at a time, and one that represents MDI (multiple-document interface) applications, which can handle multiple documents concurrently. MFC supplies two classes for these document types: CSingleDocTemplate and CMultiDocTemplate. Bet you can figure out which class goes with which document-template type!

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: