| Tenouk C & C++ | MFC Home | Reusable Frame Window Base Class 2 | Separating the Document from Its View 2 | Download | Site Index |


 

 

 

 

 

Module 10:

Separating the Document from Its View 1

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below:

  1. Separating the Document from Its View

  2. Document - View Interaction Functions

  3. The CView::GetDocument Function

  4. The CDocument::UpdateAllViews Function

  5. The CView::OnUpdate Function

  6. The CView::OnInitialUpdate Function

  7. The CDocument::OnNewDocument Function

  8. The Simplest Document - View Application

  9. The CFormView Class

  10. The CObject Class

  11. Diagnostic Dumping

  12. The TRACE Macro

  13. The afxDump Object

  14. The Dump Context and the CObject Class

  15. Automatic Dump of Undeleted Objects

  16. Window Subclassing for Enhanced Data-Entry Control

  17. The MYMFC15 Example

 

 

 

Separating the Document from Its View

 

Now you're finally going to see the interaction between documents and views. Module 13 gave you a preview of this interaction when it showed the routing of command messages to both view objects and document objects. In this module, you'll see how the document maintains the application's data and how the view presents the data to the user. You'll also learn how the document and view objects talk to each other while the application executes.

The two examples in this module both use the CFormView class as the base class for their views. The first example is as simple as possible, with the document holding only one simple object of class CStudent, which represents a single student record. The view shows the student's name and grade and allows editing. With the CStudent class, you'll get some practice writing classes to represent real-world entities. You'll also get to use the MFC Library version 6.0 diagnostic dump functions. The second example goes further by introducing pointer collection classes, the CObList and CTypedPtrList classes in particular. Now the document holds a collection of student records, and the view allows the sequencing, insertion, and deletion of individual records.

 

Document - View Interaction Functions

 

You already know that the document object holds the data and that the view object displays the data and allows editing. An SDI application has a document class derived from CDocument, and it has one or more view classes, each ultimately derived from CView. A complex handshaking process takes place among the document, the view, and the rest of the application framework. To understand this process, you need to know about five important member functions in the document and view classes. Two are non-virtual base class functions that you call in your derived classes; three are virtual functions that you often override in your derived classes. Let's look at these functions one at a time.

 

The CView::GetDocument Function

 

A view object has one and only one associated document object. The GetDocument() function allows an application to navigate from a view to its document. Suppose a view object gets a message that the user has entered new data into an edit control. The view must tell the document object to update its internal data accordingly. The GetDocument() function provides the document pointer that can be used to access document class member functions or public data embers.

The CDocument::GetNextView function navigates from the document to the view, but because a document can have more than one view, it's necessary to call this member function once for each view, inside a loop. You'll seldom call GetNextView() because the application framework provides a better method of iterating through a document's views.

When AppWizard generates a derived CView class, it creates a special type-safe version of the GetDocument() function that returns not a CDocument() pointer but a pointer to an object of your derived class. This function is an inline function, and it looks something like this:

 

CMyDoc* GetDocument()

{

    return (CMyDoc*)m_pDocument;

}

 

When the compiler sees a call to GetDocument() in your view class code, it uses the derived class version instead of the CDocument() version, so you do not have to cast the returned pointer to your derived document class. Because the CView::GetDocument function is not a virtual function, a statement such as:

 

pView->GetDocument(); // pView is declared CView*

 

Calls the base class GetDocument() function and thus returns a pointer to a CDocument object.

 

The CDocument::UpdateAllViews Function

 

If the document data changes for any reason, all views must be notified so that they can update their representations of that data. If UpdateAllViews() is called from a member function of a derived document class, its first parameter, pSender, is NULL. If UpdateAllViews() is called from a member function of a derived view class, set the pSender parameter to the current view, like this:

 

GetDocument()->UpdateAllViews(this);

 

The non-null parameter prevents the application framework from notifying the current view. The assumption here is that the current view has already updated itself. The function has optional hint parameters that can be used to give view-specific and application-dependent information about which parts of the view to update. This is an advanced use of the function. How exactly is a view notified when UpdateAllViews() gets called? Take a look at the next function, OnUpdate().

 

The CView::OnUpdate Function

 

This virtual function is called by the application framework in response to your application's call to the CDocument::UpdateAllViews function. You can, of course, call it directly within your derived CView class. Typically, your derived view class's OnUpdate() function accesses the document, gets the document's data, and then updates the view's data members or controls to reflect the changes. Alternatively, OnUpdate() can invalidate a portion of the view, causing the view's OnDraw() function to use document data to draw in the window. The OnUpdate() function might look something like this:

 

void CMyView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)

{

    CMyDocument* pMyDoc = GetDocument();

    CString lastName = pMyDoc->GetLastName();

    // m_pNameStatic is a CMyView data member

    m_pNameStatic->SetWindowText(lastName);

}

 

The hint information is passed through directly from the call to UpdateAllViews(). The default OnUpdate() implementation invalidates the entire window rectangle. In your overridden version, you can choose to define a smaller invalid rectangle as specified by the hint information. If the CDocument() function UpdateAllViews() is called with the pSender parameter pointing to a specific view object, OnUpdate() is called for all the document's views except the specified view.

 

The CView::OnInitialUpdate Function

 

This virtual CView function is called when the application starts, when the user chooses New from the File menu, and when the user chooses Open from the File menu. The CView base class version of OnInitialUpdate() does nothing but call OnUpdate(). If you override OnInitialUpdate() in your derived view class, be sure that the view class calls the base class's OnInitialUpdate() function or the derived class's OnUpdate() function. You can use your derived class's OnInitialUpdate() function to initialize your view object. When the application starts, the application framework calls OnInitialUpdate() immediately after OnCreate() (if you've mapped OnCreate() in your view class). OnCreate() is called once, but OnInitialUpdate() can be called many times.

 

The CDocument::OnNewDocument Function

 

The framework calls this virtual function after a document object is first constructed and when the user chooses New from the File menu in an SDI application. This is a good place to set the initial values of your document's data members. AppWizard generates an overridden OnNewDocument() function in your derived document class. Be sure to retain the call to the base class function.

 

The Simplest Document - View Application

 

Suppose you don't need multiple views of your document but you plan to take advantage of the application framework's file support. In this case, you can forget about the UpdateAllViews() and OnUpdate() functions. Simply follow these steps when you develop the application:

 

  1. In your derived document class header file (generated by AppWizard), declare your document's data members. These data members are the primary data storage for your application. You can make these data members public, or you can declare the derived view class a friend of the document class.

  2. In your derived view class, override the OnInitialUpdate() virtual member function. The application framework calls this function after the document data has been initialized or read from disk. (Module 11 discusses disk file I/O.) OnInitialUpdate() should update the view to reflect the current document data.

  3. In your derived view class, let your window message handlers, command message handlers and your OnDraw() function read and update the document data members directly, using GetDocument() to access the document object.

 

The sequence of events for this simplified document-view environment is as follows.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Sequence

Description

Application starts

CMyDocument object constructed

CMyView object constructed

View window created

CMyView::OnCreate called (if mapped)

CMyDocument::OnNewDocument called

CMyView::OnInitialUpdate called

View object initialized

View window invalidated

CMyView::OnDraw called

User edits data

CMyView functions update CMyDocument data members

User exits application

CMyView object destroyed

CMyDocument object destroyed

 

Table 1.

 

The CFormView Class

 

The CFormView class is a useful view class that has many of the characteristics of a modeless dialog window. Like a class derived from CDialog, a derived CFormView class is associated with a dialog resource that defines the frame characteristics and enumerates the controls. The CFormView class supports the same dialog data exchange and validation (DDX and DDV) functions that you saw in the CDialog examples in Module 5.

If AppWizard generates a Form View dialog, the properties are set correctly, but if you use the dialog editor to make a dialog for a form view, you must specify the following items in the Dialog Properties dialog:

 

  1. Style = Child.

  2. Border = None.

  3. Visible = unchecked.

 

A CFormView object receives notification messages directly from its controls, and it receives command messages from the application framework. This application framework command-processing ability clearly separates CFormView from CDialog and it makes controlling the view from the frame's main menu or toolbar easy.

The CFormView class is derived from CView (actually, from CScrollView) and not from CDialog. You can't, therefore, assume that CDialog member functions are supported. CFormView does not have virtual OnInitDialog(), OnOK(), and OnCancel() functions. CFormView() member functions do not call UpdateData() and the DDX functions. You have to call UpdateData() yourself at the appropriate times, usually in response to control notification messages or command messages.

Even though the CFormView class is not derived from the CDialog class, it is built around the Microsoft Windows dialog. For this reason, you can use many of the CDialog class member functions such as GotoDlgCtrl() and NextDlgCtrl(). All you have to do is cast your CFormView pointer to a CDialog pointer. The following statement, extracted from a member function of a class derived from CFormView, sets the focus to a specified control. GetDlgItem() is a CWnd function and is thus inherited by the derived CFormView class.

 

((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));

 

AppWizard gives you the option of using CFormView as the base class for your view. When you select CFormView, AppWizard generates an empty dialog with the correct style properties set. The next step is to use ClassWizard to add control notification message handlers, command message handlers, and update command UI handlers. (The example steps starting after Figure 16-2 show you what to do.) You can also define data members and validation criteria.

 

The CObject Class

 

If you study the MFC library hierarchy, you'll notice that the CObject class is at the top. Most other classes are derived from the CObject root class. When a class is derived from CObject, it inherits a number of important characteristics. The many benefits of CObject derivation will become clear as you read the modules that follow. In this module, you'll see how CObject derivation allows objects to participate in the diagnostic dumping scheme and allows objects to be elements in the collection classes.

 

Diagnostic Dumping

 

The MFC library gives you some useful tools for diagnostic dumping. You enable these tools when you select the Debug target. When you select the Win32 Release target, diagnostic dumping is disabled and the diagnostic code is not linked to your program. All diagnostic output goes to the Debug view in the debugger's Output window. To clear diagnostic output from the debugger's Output window, position the cursor in the Output window and click the right mouse button. Then choose Clear from the pop-up menu.

 

The TRACE Macro

 

You've seen the TRACE macro used throughout the preceding examples in this book. TRACE statements are active whenever the constant _DEBUG is defined (when you select the Debug target and when the afxTraceEnabled variable is set to TRUE). TRACE statements work like C language printf statements, but they're completely disabled in the release version of the program. Here's a typical TRACE statement:

 

int nCount = 9;

CString strDesc("total");

TRACE("Count = %d, Description = %s\n", nCount, strDesc);

 

The TRACE macro takes a variable number of parameters and is thus easy to use. If you look at the MFC source code, you won't see TRACE macros but rather TRACE0, TRACE1, TRACE2, and TRACE3 macros. These macros take 0, 1, 2, and 3 parameters, respectively, and are leftovers from the 16-bit environment, where it was necessary to conserve space in the data segment.

 

The afxDump Object

 

An alternative to the TRACE statement is more compatible with the C++ language. The MFC afxDump object accepts program variables with a syntax similar to that of cout, the C++ output stream object. You don't need complex formatting strings; instead, overloaded operators control the output format. The afxDump output goes to the same destination as the TRACE output, but the afxDump object is defined only in the Debug version of the MFC library. Here is a typical stream-oriented diagnostic statement that produces the same output as the TRACE statement above:

 

int nCount = 9;

CString strDesc("total");

#ifdef _DEBUG

    afxDump << "Count = " << nCount << ", Description = " << strDesc << "\n";

#endif // _DEBUG

 

Although both afxDump and cout use the same insertion operator (<<), they don't share any code. The cout object is part of the Microsoft Visual C++ iostream library, and afxDump is part of the MFC library. Don't assume that any of the cout formatting capability is available through afxDump.

Classes that aren't derived from CObject, such as CString, CTime, and CRect, contain their own overloaded insertion operators for CDumpContext objects. The CDumpContext class, of which afxDump is an instance, includes the overloaded insertion operators for the native C++ data types (int, double, char*, and so on). The CDumpContext class also contains insertion operators for CObject references and pointers, and that's where things get interesting.

 

The Dump Context and the CObject Class

 

If the CDumpContext insertion operator accepts CObject pointers and references, it must also accept pointers and references to derived classes. Consider a trivial class, CAction, that is derived from CObject, as shown here:

 

 

class CAction : public CObject

{

  public:

    int m_nTime;

};

 

What happens when the following statement executes?

 

#ifdef _DEBUG

    afxDump << action; // action is an object of class CAction

#endif // _DEBUG

 

The virtual CObject::Dump function gets called. If you haven't overridden Dump() for CAction, you don't get much except for the address of the object. If you have overridden Dump, however, you can get the internal state of your object. Here's a CAction::Dump function:

 

#ifdef _DEBUG

void CAction::Dump(CDumpContext& dc) const

{

    CObject::Dump(dc); // Always call base class function

    dc << "time = " << m_nTime << "\n";

}

#endif // _DEBUG

 

The base class (CObject) Dump() function prints a line such as this:

 

a CObject at $4115D4

 

If you have called the DECLARE_DYNAMIC macro in your CAction class definition and the IMPLEMENT_DYNAMIC macro in your CAction declaration, you will see the name of the class in your dump:

 

a CAction at $4115D4

 

Even if your dump statement looks like this:

 

#ifdef _DEBUG
    afxDump << (CObject&) action;
#endif // _DEBUG

 

The two macros work together to include the MFC library runtime class code in your derived CObject class. With this code in place, your program can determine an object's class name at runtime (for the dump, for example) and it can obtain class hierarchy information. The (DECLARE_SERIAL, IMPLEMENT_SERIAL) and (DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE) macro pairs provide the same runtime class features as those provided by the (DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC) macro pair.

 

Objects Automatic Dump of Undeleted Objects

 

With the Debug target selected, the application framework dumps all objects that are undeleted when your program exits. This dump is a useful diagnostic aid, but if you want it to be really useful, you must be sure to delete all your objects, even the ones that would normally disappear after the exit. This object cleanup is good programming discipline. The code that adds debug information to allocated memory blocks is now in the Debug version of the CRT (C runtime) library rather than in the MFC library. If you choose to dynamically link MFC, the MSVCRTD DLL is loaded along with the necessary MFC DLLs. When you add the line:

 

#define new DEBUG_NEW

 

at the top of a CPP file, the CRT library lists the filename and line number at which the allocations were made. AppWizard puts this line at the top of all the CPP files it generates.

 

Window Subclassing for Enhanced Data-Entry Control

 

What if you want an edit control (in a dialog or a form view) that accepts only numeric characters? That's easy. You just set the Number style in the control's property sheet. If, however, you want to exclude numeric characters or change the case of alphabetic characters, you must do some programming.

The MFC library provides a convenient way to change the behavior of any standard control, including the edit control. Actually, there are several ways. You can derive your own classes from CEdit, CListBox, and so forth (with their own message handler functions) and then create control objects at runtime. Or you can register a special window class, as a Win32 programmer would do, and integrate it into the project's resource file with a text editor. Neither of these methods, however, allows you to use the dialog editor to position controls in the dialog resource.

The easy way to modify a control's behavior is to use the MFC library's window subclassing feature. You use the dialog editor to position a normal control in a dialog resource, and then you write a new C++ class that contains message handlers for the events that you want to handle yourself.

 

Here are the steps for subclassing an edit control:

 

With the dialog editor, position an edit control in your dialog resource. Assume that it has the child window ID IDC_EDIT1. Write a new class, for example, CNonNumericEdit, derived from CEdit. Map the WM_CHAR message and write a handler like this:

 

void CNonNumericEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

    if (!isdigit(nChar))

    {

        CEdit::OnChar(nChar, nRepCnt, nFlags);

    }

}

 

In your derived dialog or form view class header, declare a data member of class CNonNumericEdit in this way:

 

private:

    CNonNumericEdit m_nonNumericEdit;

 

If you're working with a dialog class, add the following line to your OnInitDialog() override function:

 

m_nonNumericEdit.SubclassDlgItem(IDC_EDIT1, this);

 

If you're working with a form view class, add the following code to your OnInitialUpdate() override function:

 

if (m_nonNumericEdit.m_hWnd == NULL)

{

    m_nonNumericEdit.SubclassDlgItem(IDC_EDIT1, this);

}

 

The CWnd::SubclassDlgItem member function ensures that all messages are routed through the application framework's message dispatch system before being sent to the control's built-in window procedure. This technique is called dynamic subclassing and is explained in more detail in Technical Note #14 in the online documentation. The code in the preceding steps only accepts or rejects a character. If you want to change the value of a character, your handler must call CWnd::DefWindowProc, which bypasses some MFC logic that stores parameter values in thread object data members. Here's a sample handler that converts lowercase characters to uppercase:

 

void CUpperEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

    if (islower(nChar))

    {

        nChar = toupper(nChar);

    }

    DefWindowProc(WM_CHAR, (WPARAM) nChar, (LPARAM) (nRepCnt | (nFlags << 16)));

}

 

You can also use window subclassing to handle reflected messages, which were mentioned in Module 5. If an MFC window class doesn't map a message from one of its child controls, the framework reflects the message back to the control. Technical Note #62 in the online documentation explains the details.

If you need an edit control with a yellow background, for example, you can derive a class CYellowEdit from CEdit and use ClassWizard to map the WM_CTLCOLOR message in CYellowEdit. ClassWizard lists the message name with an equal sign in front to indicate that it is reflected. The handler code, shown below, is substantially the same as the non-reflected WM_CTLCOLOR handler. Member variable m_hYellowBrush is defined in the control class's constructor.

 

HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)

{

    pDC->SetBkColor(RGB(255, 255, 0)); // yellow

    return m_hYellowBrush;

}

 

The MYMFC15 Example

 

The first of this module's two examples shows a very simple document-view interaction. The CMymfc15Doc document class, derived from CDocument, allows for a single embedded CStudent object. The CStudent class represents a student record composed of a CString name and an integer grade. The CMymfc15View view class is derived from CFormView. It is a visual representation of a student record that has edit controls for the name and grade. The default Enter pushbutton updates the document with data from the edit controls. Figure 1 shows the MYMFC15 program window.

 

The MYMFC15 program in action.

 

Figure 1: The MYMFC15 program in action.

 

Listing 1 shows the code for the CStudent class (Student.h and Student.cpp). Most of the class's features serve MYMFC15, but a few items carry forward to MYMFC16 and the programs discussed in Module 11. For now, take note of the two data members, the default constructor, the operators, and the Dump() function declaration. The DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC macros ensure that the class name is available for the diagnostic dump.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

STUDENT.H

// student.h

 

#ifndef _INSIDE_VISUAL_CPP_STUDENT

#define _INSIDE_VISUAL_CPP_STUDENT

class CStudent : public CObject

{

    DECLARE_DYNAMIC(CStudent)

public:

    CString m_strName;

    int m_nGrade;

 

    CStudent()

    {

        m_nGrade = 0;

    }

 

    CStudent(const char* szName, int nGrade) : m_strName(szName)

    {

        m_nGrade = nGrade;

    }

 

    CStudent(const CStudent& s) : m_strName(s.m_strName)

    {

        // copy constructor

        m_nGrade = s.m_nGrade;

    }

 

    const CStudent& operator =(const CStudent& s)

    {

        m_strName = s.m_strName;

        m_nGrade = s.m_nGrade;

        return *this;

    }

 

    BOOL operator ==(const CStudent& s) const

    {

        if ((m_strName == s.m_strName) && (m_nGrade == s.m_nGrade)) {

            return TRUE;

        }

        else {

            return FALSE;

        }

    }

 

    BOOL operator !=(const CStudent& s) const

    {

        // Let's make use of the operator we just defined!

        return !(*this == s);

    }

#ifdef _DEBUG

    void Dump(CDumpContext& dc) const;

#endif // _DEBUG

};

 

#endif // _INSIDE_VISUAL_CPP_STUDENT

 

-------------------------------------------------------------------------------------------------------------------------------------------------

 

STUDENT.CPP

#include "stdafx.h"

#include "student.h"

 

IMPLEMENT_DYNAMIC(CStudent, CObject)

 

#ifdef _DEBUG

void CStudent::Dump(CDumpContext& dc) const

{

    CObject::Dump(dc);

    dc << "m_strName = " << m_strName << "\nm_nGrade = " <<m_nGrade;

}

#endif // _DEBUG

 

 

Listing 1: The CStudent class listing.

 

Continue on next module...part 2.

 

 

 

 

Further reading and digging:

  1. MSDN MFC 7.0 class library online documentation.

  2. MSDN MFC 9.0 class library online documentation - latest version.

  3. Porting & Migrating your older programs.

  4. MSDN Library

  5. DCOM at MSDN.

  6. COM+ at MSDN.

  7. COM at MSDN.

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 

 

 


 

| Tenouk C & C++ | MFC Home | Reusable Frame Window Base Class 2 | Separating the Document from Its View 2 | Download | Site Index |