Chapter 3

Documents and Views


In Chapter 2, "Using AppWizard to Create an MFC Program," you learned the basics of generating an application with AppWizard. The example application featured 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 didn'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'll learn the basics of how MFC's document/view architecture works.

Understanding the Document Class

In Chapter 2, you created a basic AppWizard application. When you looked over the various files generated by AppWizard, you discovered 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. Because you didn't modify the AppWizard-generated files, the CApp1Doc class really holds no 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 3.1, which shows the header file, AppWizard, created for the CApp1Doc class.

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

#if !defined(APP1_H__99206D24_7535_11D0_847F_444553540000__INCLUDED_)
#define APP1_H__99206D24_7535_11D0_847F_444553540000__INCLUDED_
// app1.h : main header file for the APP1 application
//
#ifndef __AFXWIN_H__
    #error include 'stdafx.h' before including this file for PCH
#endif
#include "resource.h"       // main symbols
/////////////////////////////////////////////////////////////////////////////
// CApp1App:
// See app1.cpp for the implementation of this class
//
class CApp1App : public CWinApp
{
public:
    CApp1App();
// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CApp1App)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL
// Implementation
    //{{AFX_MSG(CApp1App)
    afx_msg void OnAppAbout();
        // 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(APP1_H__99206D24_7535_11D0_847F_444553540000__INCLUDED)

Near the top of Listing 3.1, 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 you'll create a little later in this chapter, in the section titled "Creating the Rectangles Application," 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 chooses 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. When you build this chapter's sample program, you won't believe how easy it is to load and save 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. (Yes, I realize that this sort of interactivity between classes breaks all the object-oriented programming (OOP) rules of data encapsulation, but sometimes you have to give up some things in order to gain others.) If you look at Listing 3.2, you can see how the CApp1View class, which you created in Chapter 2, obtains pointers to the document object.

See “Creating Your First MFC Program,” (ch. 2)

 

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

#if !defined(APP1VIEW_H__99206D2E_7535_11D0_847F_444553540000__INCLUDED_)
#define APP1VIEW_H__99206D2E_7535_11D0_847F_444553540000__INCLUDED_
// 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
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif // !defined(APP1VIEW_H__99206D2E_7535_11D0_847F_444553540000__INCLUDED)

 

Near the top of Listing 3.2, 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 the preceding 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 when the less clear GetDocument()->variable version makes code difficult 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().

When you first start using an application framework, such as 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 it is also an entity unto itself. The window element becomes associated with the window class when MFC calls the OnCreate() member function.

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 following steps, you'll build the Rectangles application, which, not only demonstrates the manipulation of documents and views, but also gives you a chance to edit an application's resources as well as use ClassWizard to add message response functions to an application.

Creating the Basic Rectangles Application

Follow the first steps to create the basic Rectangles application and modify its resources:

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

***BEGIN NUMBERED LIST

  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, as shown in Figure 3.1. Click the OK button to create the project files.

    FIG. 3.1

    Your New Project Information dialog box should look like this.
  2. 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.
  3. Select the ResourceView tab in the project workspace window. Visual C++ displays the ResourceView window, as shown in Figure 3.2.

    FIG. 3.2

    The ResourceView tab displays the ResourceView window.
  4. 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. The Visual C++ menu editor appears, as shown in Figure 3.3.

    FIG. 3.3

    The menu editor enables you to modify the application's default menu bar.
  5. Click the Rectangles application's Edit menu (not the Visual C++ Edit menu), and then press your keyboard's Delete key to delete the E dit menu. When you do, a dialog box asks for verification of the delete command (see Figure 3.4). Click the OK button.

    FIG. 3.4

    The menu editor enables you to delete items from the menus.
  6. Double-click the About recs... item in the Help menu, and change it to About Rectangles... (by typing &About Rectangles... in the C aption box), as shown in Figure 3.5. Close the menu editor.

    FIG. 3.5

    You should change the About command to include the application's actual name.
  7. Double-click the Accelerator resource in the ResourceView window. Double-click the IDR_MAINFRAME accelerator ID to bring up the accelerator editor, as shown in Figure 3.6.

    FIG. 3.6

    The accelerator editor enables you to edit your application's hotkeys.
  8. 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 (see Figure 3.7). Close the accelerator editor.

    FIG. 3.7

    You can use the accelerator editor to delete unwanted hotkeys.
  9. Double-click the Dialog resource in the ResourceView window. Double-click the IDD_ABOUTBOX dialog box ID to bring up the dialog box editor (see Figure 3.8).

    FIG. 3.8

    In the dialog box editor, you can modify your application's About dialog box.
  10. 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 3.9. Close the dialog box editor.

    FIG. 3.9

    The About Rectangles dialog box should end up like this.
  11. Double-click the String Table resource in the ResourceView window. Double-click the String Table ID to bring up the string table editor, as shown in Figure 3.10.

    FIG. 3.10

    The string table holds the various messages that appear in your application's windows.
  12. Double-click the IDR_MAINFRAME string, and then change the first segment of the string to Rectangles, as shown in Figure 3.11. Close the string table editor.


FIG. 3.11

The first segment of the IDR_MAINFRAME string appears in your main window's title bar.

Modifying the Document Class

Now that you have the application's resources the way you want them, it's time to add code to the document and to the view classes in order to create an application that actually does something. Perform the following steps to add the code that modifies the document class to handle the application's data (which is an array of CPoint objects that determines where rectangles should be drawn in the view window):

  1. Click the FileView tab to display the FileView window. Then, click the plus sign next to the folder labeled "recs files" to display the project's file folders, as shown in Figure 3.12.

    FIG. 3.12

    The FileView window lists folders for the source files that make up your project.
  2. Double-click the Header Files folder, and then double-click the recsDoc.h entry in the file list. The recsDoc.h file appears in the code window, as shown in Figure 3.13.

    FIG. 3.13

    By clicking entries in the FileView window, you can load and display source code files in the code window.
  3. Add the following lines to the attributes section of the CRecsDoc class right after the public keyword:
        CPoint m_points[100];
        UINT m_pointIndex;
  4. These lines declare the data members of the document class, 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.
  5. Double-click Source Files in the FileView window, and then double-click recsDoc.cpp to open the document class's implementation file. Add the following line to the OnNewDocument() function right after the (SDI documents will reuse this document) comment:

    m_pointIndex = 0;

  6. 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.
  7. Add the lines shown in Listing 3.3 to the Serialize() function right after the TODO: add storing code here comment.

    Listing 3.3 LST03_03.CPP—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;
            }
  8. 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, in the section titled "Serializing Document Data."
  9. Add the lines shown in Listing 3.4 to the Serialize() function right after the TODO: add loading code here comment.

    Listing 3.4 LST03_04.CPP—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);
  10. 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, in the section titled "Serializing Document Data."

 

Modifying the View Class

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 3.5 to the OnDraw() function right after the TODO: add draw code for native data here comment.

    Listing 3.5 LST03_05.CPP—Code for Displaying the Application's Data (Rectangles)

        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 code in Listing 3.5, 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);

  3. This line modifies the common Print dialog box such that the user cannot try to print more than one page.
  4. Choose View, ClassWizard. The ClassWizard property sheet appears, as shown in Figure 3.14.

    FIG. 3.14

    You can use ClassWizard to add functions to a class.
  5. 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, as shown in Figure 3.15.

    FIG. 3.15

    ClassWizard takes all the work out of creating response functions for Windows messages.
  6. 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.
  7. Click the Edit Code button to jump to the OnLButtonDown() function in your code. Then, add the lines shown in Listing 3.6 to the function right after the TODO: Add your message handler code here and/or call default comment.

Listing 3.6 LST03_06.CPP—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();

 

The code in Listing 3.6 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 Build button on the toolbar or choose the Build, Build command from the menu bar to compile and link the application.

Running the Rectangles Application

Once you have the Rectangles application compiled and linked, run it by choosing Build, Execute. When you do, you'll 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 Figure 3.16).


FIG. 3.16

The Rectangles application's document consists of rectangles that the user places on the screen.

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


FIG. 3.17

The About dialog box provides information about the application.

Exploring the Rectangles Application

If this is your first 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'll examine the key parts of the Rectangles application. Keep in mind that what you learn here is only the first step toward understanding MFC. Only the primary issues of using AppWizard and MFC are covered in the following section. Subsequent chapters in this book solve many of the remaining MFC mysteries.

Declaring Storage for Document Data

As you've read again and again since the beginning of this book, 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 3.7 shows the relevant code.

Listing 3.7 LST03_07.CPP—Declaring the Document Data of the Rectangles Application

// Attributes
public:
    CPoint m_points[100];
    UINT m_pointIndex;

In Listing 3.7, 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

Once 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, the 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 3.8.

Listing 3.8 LST03_08.CPP—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)
    ///////////////////////////////////////
    ///////////////////////////////////////
    // START CUSTOM CODE
    ///////////////////////////////////////
    ///////////////////////////////////////
    m_pointIndex = 0;
    ///////////////////////////////////////
    ///////////////////////////////////////
    // END CUSTOM CODE
    ///////////////////////////////////////
    ///////////////////////////////////////
    return TRUE;
}

 

In many of the listings in this chapter, code that you added to a function is located between START CUSTOM CODE and END CUSTOM CODE comment blocks. That is, all of the code in Listing 3.8, except the line m_pointIndex = 0; was created by AppWizard.

MFC calls the OnNewDocument() function whenever the user starts a new document, usually by choosing 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, you can perform general document initialization in the class's constructor in an MDI application.

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 book 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. 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 3.9 shows the Rectangles application's Serialize() function.

Listing 3.9 LST03_09.CPP—The Rectangles Application's Serialize() Function

void CRecsDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        // TODO: add storing code here
        ///////////////////////////////////////
        ///////////////////////////////////////
        // START CUSTOM CODE
        ///////////////////////////////////////
        ///////////////////////////////////////
        ar << m_pointIndex;
        for (UINT i=0; i<m_pointIndex; ++ i)
        {
            ar << m_points[i].x;
            ar << m_points[i].y;
        }
        ///////////////////////////////////////
        ///////////////////////////////////////
        // END CUSTOM CODE
        ///////////////////////////////////////
        ///////////////////////////////////////
    }
    else
    {
        // TODO: add loading code here
        ///////////////////////////////////////
        ///////////////////////////////////////
        // START CUSTOM CODE
        ///////////////////////////////////////
        ///////////////////////////////////////
        ar >> m_pointIndex;
        for (UINT i=0; i<m_pointIndex; ++ i)
        {
            ar >> m_points[i].x;
            ar >> m_points[i].y;
        }
        UpdateAllViews(NULL);
        ///////////////////////////////////////
        ///////////////////////////////////////
        // END CUSTOM CODE
        ///////////////////////////////////////
        ///////////////////////////////////////
    }
}

 

As you can see in Listing 3.9, 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++ input/output objects. If you look at the Serialize() function, you'll 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 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.

Displaying Document Data

Now that you've got your document class 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 OnDraw() member function of the view class that is responsible for displaying data, either on the screen or the printer. Listing 3.10 shows the Rectangles application's version of OnDraw().

Listing 3.10 LST03_10.CPP—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
    ///////////////////////////////////////
    ///////////////////////////////////////
    // START CUSTOM CODE
    ///////////////////////////////////////
    ///////////////////////////////////////
    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);
    }
    ///////////////////////////////////////
    ///////////////////////////////////////
    // END CUSTOM CODE
    ///////////////////////////////////////
    ///////////////////////////////////////
}

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.

See “Exploring the Paint1 Application,” (ch. 5)

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 3.11 shows the OnLButtonDown() function where this data update occurs.

Listing 3.11 LST03_11.CPP—The Rectangles Application's OnLButtonDown() Function

void CRecsView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    
    ///////////////////////////////////////
    ///////////////////////////////////////
    // START CUSTOM CODE
    ///////////////////////////////////////
    ///////////////////////////////////////
    CRecsDoc *pDoc = GetDocument();
    if (pDoc->m_pointIndex == 100)
        return;
    pDoc->m_points[pDoc->m_pointIndex] = point;
    ++pDoc->m_pointIndex;
    pDoc->SetModifiedFlag();
    Invalidate();
    ///////////////////////////////////////
    ///////////////////////////////////////
    // END CUSTOM CODE
    ///////////////////////////////////////
    ///////////////////////////////////////
    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.

In this chapter, you got a quick look at 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. Because AppWizard can disguise much of what is going on in an MFC application, the rest of this book concentrates on writing MFC applications without AppWizard's help. However, most of what you'll learn in the upcoming chapters can be applied to AppWizard-generated applications, as well. Remember that although AppWizard is a useful tool, there's really nothing magical about it: It creates the same type of MFC code that you can create on your own.