In this section you learn more about MFC and Document/View, expanding on the material covered in Section 9, "The Document/View Architecture." In this section, you will learn about
You usually add controls to a form view using a dialog resource, the same way you build dialog boxes. However, unlike dialog boxes, a form view is never modal; in fact, several form views can be open simultaneously, just like other views.
The most common reason to use a form view is because it's an easy-to-use view that looks like a dialog box. With practice, you can create a form view in a few minutes. You can add controls used in a form view to the form view's dialog resource, just as you add controls to a resource used by a normal dialog box. After you add the controls, you can associate them with MFC objects and class member variables, just as you associate controls with dialog boxes.
Using form views enables you to easily adapt the DDX and DDV routines used by dialog boxes to a view. Unlike a modal dialog box, several different form views can be open at once, making your program much more flexible. Like other views, a form view has access to all the Document/View interfaces, giving it direct access to the document class.
It's common for a form view to be one of several possible views for a document. In an MDI application, it's common to have more than one view for each document type. For example, a form view can be used as a data entry view, and another type of view can be used for display purposes. Figure 23.1 presents a class diagram showing some of the classes derived from CView.
The MFC view classes.
In a scroll view, the invisible part of the view can be made visible using scrollbars associated with the view. An easy way to visualize how scrolling works is to imagine a large virtual view, hidden except for a small window used as a viewport, as shown in Figure 23.2.
Only a portion of the entire view is visible in Figure 23.2. The view window scrolls to different parts of the document; the underlying large view retains its original position. Although you can implement scrolling yourself for any class derived from CView, much of the work is done for you if you use CScrollView.
Scrolling a view using CScrollView.
New Term: The edit view is a view that consists of an edit control. The edit view automatically supports printing, using the clipboard cut, paste, and copy functions, and Find and Replace. The edit view is supported by the CEditView class, so it can be associated with a document just like any other view.
Just a Minute: The edit view does not support true what-you-see-is-what-you-get (WYSIWYG) editing; only one font is supported for the entire view, and the display is not always 100 percent accurate with regard to a printed page.
Just a Minute: Using a form view requires only a few more steps than using a dialog box. All the hard work is handled by the framework and the MFC CFormView class. Using ClassWizard, you can add a form view to a project using 30 or 40 lines of your own code.
To illustrate how form views are used, add a form view to a project that was built using AppWizard. To reduce the amount of code that must be entered, you can reuse the DVTest project built in Section 9. To recap, the DVTest project stored a collection of names in its document class. In this section, you will associate a form view with the document and use it to display and input names into the program. For now, the form view is the only view associated with the document; you will learn about multiple views later this section.
| Property | Value |
| Style | Child |
| Border | None |
| Visible | Unchecked |
| Titlebar | Unchecked |
Other than the values listed in Table 23.1, there are no other limitations for dialog box properties or controls. Any controls you can add to a dialog box can be used in a form view.
Developer Studio enables you to add a dialog box resource to your project that has all of the properties needed for a form view. Follow these steps:
2. Select Insert from the context menu. A dialog box will be displayed containing a tree of resources that can be added to your project.
3. Expand the Dialog tree; a list of dialog box templates is displayed.
4. Select the IDD_FORMVIEW template, and click the New button.
The dialog resource used as a form view in the CDVTest sample project.
Set the dialog resource and control resource IDs as listed in Table 23.2. The list box should not be automatically sorted by Windows for this example, so clear the Sort attribute for the list box control. Use default properties for all other controls.
| Property | ID |
| Dialog | IDD_FORM_VIEW |
| Edit Control | IDC_EDIT |
| List Control | IDC_LIST |
| Close Button | IDC_CLOSE |
| Apply Button | IDC_APPLY |
2. Fill in the Add Class dialog box using the values from Table 23.3, then click OK.
| Control | Value |
| Name | CFormTest |
| Base Class | CFormView |
| File | FormTest.cpp |
| Dialog ID | IDD_FORM_VIEW |
| Automation | None |
Use ClassWizard to add two member variables to the CFormTest class, as shown in Table 23.4.
| Control ID | Control Type | Variable Type | Variable Name |
| IDC_LIST | Control | CListCtrl | m_lbNames |
| IDC_EDIT | Control | CEdit | m_edNames |
Listing 23.1 associates the CFormTest view class with the CDVTestDoc document class. Update the code that creates the document template in the CDVTestApp::InitInstance function, found in the CDVTestApp.cpp source file. You must change only the fourth parameter to the constructor, as shown by the comment.
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_DVTESTTYPE, RUNTIME_CLASS(CDVTestDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CFormTest)); // Change this lineAddDocTemplate(pDocTemplate); Because CFormTest is now used, the class declaration for CFormTest must be included into the DVTest.cpp source file. Add the following line after all other #include directives at the top of the DVTest.cpp source file:
#include "FormTest.h"
| Object ID | Message | Function Name |
| IDC_APPLY | BN_CLICKED | OnApply |
| IDC_CLOSE | BN_CLICKED | OnClose |
The code to handle control events is fairly straightforward. Edit the new functions added to the CFormTest class so that they look like the code in Listing 23.2.
void CFormTest::OnApply()
{
CDVTestDoc* pDoc;
pDoc = (CDVTestDoc*)GetDocument();
ASSERT_VALID( pDoc );
CString szName;
m_edNames.GetWindowText( szName );
m_edNames.SetWindowText( "" );
m_edNames.SetFocus();
if( szName.GetLength() > 0 )
{
int nIndex = pDoc->AddName( szName );
m_lbNames.InsertString( nIndex, szName );
m_lbNames.SetCurSel( nIndex );
}
}
void CFormTest::OnClose()
{
PostMessage( WM_COMMAND, ID_FILE_CLOSE );
} You must manually add an include statement for the document class. At
the top of the FormView.cpp file, add the following line just after all
the other #include directives:
#include "DVTestDoc.h"The OnApply function is split into three main parts:
void CFormTest::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
CDVTestDoc* pDoc = (CDVTestDoc*)GetDocument();
ASSERT_VALID(pDoc);
for( int nIndex = 0; nIndex < pDoc->GetCount(); nIndex++ )
{
CString szName = pDoc->GetName( nIndex );
m_lbNames.AddString( szName );
}
}
Time Saver: When a dialog box is displayed, the dialog resource is used to size the dialog box's window. A form view is not automatically sized this way, which leads to an unexpected display if you aren't aware of this behavior. However, you can resize the view to the exact dimensions of the dialog resource by using the ResizeParentToFit function. Add the following two lines of code to the CFormTest::OnInitialUpdate member function:
ResizeParentToFit( FALSE ); ResizeParentToFit();
Nope, it's not a typo; you must call ResizeParentToFit twice to make sure that the size is calculated correctly. The first call allows the view to expand and the second call shrinks the view to fit the dialog resource.
The form view class doesn't actually have any control over the minimize and maximize buttons--they belong to the frame, which also controls the capability to change the size of the view by dragging it with a mouse. The CChildFrame class is the frame used by default in MDI applications, although you can change the frame class by using a different class name when the document template is created.
To remove the sizable frame and minimize button from the frame class, add two lines of code to the frame class PreCreateWindow member function. The PreCreateWindow function is called just before the window is created. This enables you to change the style of the window, as shown in Listing 23.4.
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// Mask away the thickframe and maximize button style bits.
cs.style &= ~WS_THICKFRAME;
cs.style &= ~WS_MAXIMIZEBOX;
return CMDIChildWnd::PreCreateWindow(cs);
} The &= operator is the C++ bitwise AND operator, which is used to
clear or remove a bit that is set in a particular value. The tilde (~)
is the C++ inversion operator, used to "flip" the individual bits of a
particular value. These two operators are commonly used together to mask
off attributes that have been set using the bitwise OR operator. In Listing
23.4, the WS_THICKFRAME and WS_MAXIMIZEBOX attributes are cleared from
the cs.style variable.
Compile and run the DVTest project. Figure 23.4 shows DVTest after a few names have been added to the list box.
DVTest after adding a form view to the project.
Multiple views connected to a single document in an MDI application.
The most common reason to use multiple views is because there are different ways of looking at information contained in a document. For example, a form view often is used to give detailed information about a particular item in a database; another view might be used for data entry; still another type of view might be used to provide a summary of all items in the same database. Offering several views at the same time provides maximum flexibility for users of the program.
Because each of these views is connected to a single document, there must be some way to update the views when needed to keep them synchronized. When one of the views changes the document, all views must immediately be updated.
Just a Minute: Using multiple views allows each view to be specialized for a particular purpose. If only a single view were allowed, that view would have to be extremely flexible to suit the needs of every user of your program. Creating specialized views for particular purposes allows each of these views to do a single job for which they are well suited.
A new view is easily associated with an existing document. After a document class has been modified to work with multiple views, any number of view classes can be added to the program without further modifications to the document class. The following steps are required to modify an MDI program to use multiple views:
The new view class, CDisplayView, is derived directly from CView. Because CDisplayView only displays information, it must support only two new interfaces:
2. Press the button labeled Add Class and select the New option from the drop-down menu.
3. Use the values from Table 23.6 to fill in the Add Class dialog box.
4. Click OK and close ClassWizard.
| Control | Value |
| Name | CDisplayView |
| Base Class | CView |
| OLE Automation | None |
ClassWizard adds the new view to the project and creates some default initialization functions. However, the view class isn't useful until you do some additional work to associate it with the document class and define how it displays information.
When you create a new view using ClassWizard, you must add functions to handle the Document/View interfaces; they aren't automatically created as they are for views created by AppWizard when a new project is created. Using ClassWizard, add two message-handling functions to the CDisplayView class using the values from Table 23.7.
| Class Name | Object ID | Message |
| CDisplayView | CDisplayView | OnInitialUpdate |
| CDisplayView | CDisplayView | OnUpdate |
void CDisplayView::OnDraw(CDC* pDC)
{
CDVTestDoc* pDoc = (CDVTestDoc*)GetDocument();
ASSERT_VALID(pDoc);
// Calculate the space required for a single
// line of text, including the inter-line area.
TEXTMETRIC tm;
pDC->GetTextMetrics( &tm );
int nLineHeight = tm.tmHeight + tm.tmExternalLeading;
CPoint ptText( 0, 0 );
for( int nIndex = 0; nIndex < pDoc->GetCount(); nIndex++ )
{
CString szName = pDoc->GetName( nIndex );
pDC->TextOut( ptText.x, ptText.y, szName );
ptText.y += nLineHeight;
}
} Notice that the OnDraw function used in CDisplayView is the same as the
CDVTestView::OnDraw function in Listing 9.14. Although that view had exclusive
access to its document, the same source code works when the document is
shared by multiple views. You add the code for the OnInitialUpdate and
OnUpdate member functions later, in the section "Adding the OnInitialUpdate
and OnUpdate Member Functions."
Because the OnDraw function must access the CDVTestDoc class, add this #include directive for the CDVTestDoc class:
#include "DVTestDoc.h"Add this include statement after the other include statements near the beginning of the DisplayView.cpp source file.
public: CDocTemplate* GetDisplayTemplate() const; CDocTemplate* GetFormTemplate() const; private: CDocTemplate* m_pDisplayTemplate;CDocTemplate* m_pFormTemplate; The two document template pointers are set during the CDVTestApp::InitInstance member function. Instead of creating a CMultiDocTemplate object and passing it immediately to the AddDocTemplate function, CMultiDocTemplate objects are created, and their pointers are stored in the new member variables. Replace the current code used to create the document templates in CDVTestApp::InitInstance with the source code provided in Listing 23.7.
m_pFormTemplate = new CMultiDocTemplate( IDR_DVTESTTYPE, RUNTIME_CLASS(CDVTestDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CFormTest) ); m_pDisplayTemplate = new CMultiDocTemplate( IDR_DISPLAYTYPE, RUNTIME_CLASS(CDVTestDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CDisplayView) );AddDocTemplate( m_pFormTemplate ); Each of the document templates created in Listing 23.7 describes views associated with the CDVTestDoc class. One of the document templates uses the CFormTest class from earlier this section, whereas the other template uses the CDisplayView class. Because this class is new to the DVTest.cpp file, add an #include directive for the CDisplayView class:
#include "DisplayView.h"Listing 23.8 contains the source for the new CDVTestApp functions that return pointers to the CDocTemplate pointers created during CDVTest::OnInitInstance. The CMainFrame class uses these pointers when creating new views. Add the source code in Listing 23.8 to the DVTest.cpp file.
CDocTemplate* CDVTestApp::GetDisplayTemplate() const
{
return m_pDisplayTemplate;
}
CDocTemplate* CDVTestApp::GetFormTemplate() const
{
return m_pFormTemplate;
}
\nDVTest\n\n\n\nDVTest.Document\nDVTest DocumentThe contents of the resource string are split into seven sections, and each section is separated by \n. Each of the seven sections has a particular purpose, as shown in Table 23.8.
| Section | IDR_DVTEST | IDR_DISPLAYTYPE |
| Title | ||
| Document Name | DVTest | DVTest |
| New File Name | DVTest | |
| Filter Name | ||
| Filter Extension | ||
| Type ID | DVTest.Document | DVTest.Document |
| Type Name | DVTest Document | DVTest Document |
The new resource string is almost the same as the original string. The only difference is that there is no entry for the section marked New File Name. This is a clue to the MFC framework that this document template is not used to create new documents; instead, it is used only to open a new view on an existing document. You don't have to worry too much about the purpose of each segment. The MFC framework uses these segments when registering your application with Windows, and when opening new views and documents.
| Menu ID | Caption | Event | Function Name |
| ID_WINDOW_DISPLAY | &Display View | COMMAND | OnWindowDisplay |
| ID_WINDOW_FORM | &Form View | COMMAND | OnWindowForm |
CAUTION: You must add the new menu items to both the IDR_DISPLAYTYPE and IDR_DVTESTTYPE menus. If you don't, you won't be able to access the new menu items when either view is active.
Listing 23.9 provides the source code for the message-handling functions.
void CMainFrame::OnWindowForm()
{
CMDIChildWnd* pActiveChild = MDIGetActive();
if( pActiveChild != 0 )
{
CDocument* pDocument = pActiveChild->GetActiveDocument();
if( pDocument != 0 )
{
CDVTestApp* pApp = (CDVTestApp*)AfxGetApp();
CDocTemplate* pTemp;
CFrameWnd* pFrame;
pTemp = pApp->GetFormTemplate();
pFrame = pTemp->CreateNewFrame(pDocument,pActiveChild);
if( pFrame != 0 )
{
pTemp->InitialUpdateFrame(pFrame, pDocument);
}
}
}
}
void CMainFrame::OnWindowDisplay()
{
CMDIChildWnd* pActiveChild = MDIGetActive();
if( pActiveChild != 0 )
{
CDocument* pDocument = pActiveChild->GetActiveDocument();
if( pDocument != 0 )
{
CDVTestApp* pApp = (CDVTestApp*)AfxGetApp();
CDocTemplate* pTemp;
CFrameWnd* pFrame;
pTemp = pApp->GetDisplayTemplate();
pFrame = pTemp->CreateNewFrame(pDocument,pActiveChild);
if( pFrame != 0 )
{
pTemp->InitialUpdateFrame(pFrame, pDocument);
}
}
}
} These functions are nearly identical: the only difference between them
is the call to either GetDisplayTemplate or GetFormTemplate. The functions
provided in Listing 23.9 follow these steps when creating a new view:
2. Get a pointer to the active document.
3. Get a pointer to the application.
4. Using the application pointer, get the document template for the new view.
5. Using the document template, create a new frame associated with the active frame from step 1 and the document pointer from step 2.
6. Update the frame.
The document class controls the updating of all views.
Every document should provide updates to its associated views by calling the UpdateAllViews function when data contained by the document has been changed. To update all views associated with a document, you can use a line like this:
UpdateAllViews( NULL );The default implementation of UpdateAllViews notifies every view that the document has been changed by calling each view object's OnUpdate member function. The NULL parameter causes all views to be updated. If a view pointer is passed as a parameter, that view is not updated. Listing 23.10 provides the new source code for the CDVTestDoc::AddName function.
int CDVTestDoc::AddName( const CString& szName )
{
TRACE("CDVTestDoc::AddName, string = %s\n", (LPCSTR)szName);
int nPosition = m_arNames.Add( szName );
UpdateAllViews( NULL );
return nPosition;
}
void CDisplayView::OnInitialUpdate()
{
CView::OnInitialUpdate();
InvalidateRect( NULL );
}
void CDisplayView::OnUpdate(CView* pSender, LPARAM lHint,
CObject* pHint)
{
InvalidateRect( NULL );
}
All view classes should provide OnUpdate member functions that are called
by the MFC framework after the document class calls UpdateAllViews. Note
that the entire view is redrawn whenever the document has been updated.
The current view, CFormTest, must also support OnUpdate. Add the OnUpdate function to the CFormTest class using ClassWizard. Listing 23.12 provides the source code for CFormTest::OnUpdate.
void CFormTest::OnUpdate(CView* pSender, LPARAM lHint,
CObject* pHint)
{
CDVTestDoc* pDoc = (CDVTestDoc*)GetDocument();
ASSERT_VALID(pDoc);
m_lbNames.ResetContent();
for( int nIndex = 0; nIndex < pDoc->GetCount(); nIndex++ )
{
CString szName = pDoc->GetName( nIndex );
m_lbNames.AddString( szName );
}
}
Now that you have implemented OnUpdate, change the OnInitialUpdate member
function so that it performs only work that must be done when the view
is initially displayed. Remove source code from CFormTest::OnInitialUpdate
so it looks like the function provided in Listing 23.13.
void CFormTest::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
ResizeParentToFit( FALSE );
ResizeParentToFit();
}
Because OnUpdate handles the insertion of new items into the list box,
you should change the OnApply member function so that it does not add strings
to the list box. Edit the OnApply member function so it looks like the
code in Listing 23.14.
void CFormTest::OnApply()
{
CDVTestDoc* pDoc = (CDVTestDoc*)GetDocument();
ASSERT_VALID(pDoc);
CString szName;
m_edNames.GetWindowText( szName );
m_edNames.SetWindowText( "" );
m_edNames.SetFocus();
if( szName.GetLength() > 0 )
{
pDoc->AddName( szName );
}
}
Compile and run the DVTest project. Figure 23.7 shows DVTest with new names
added to the document, and multiple open views.
DVTest after adding the display view.
A There is no requirement that all view menus have the same items; each view menu can be customized to suit the needs of each view. You should give each menu item a unique identifier--two menu items that perform different tasks should have different identifiers, even if they have the same names.
Q Why is it useful to pass a CView pointer as a parameter in UpdateAllViews and prevent that view from receiving an OnUpdate notification?
A Often, a view that causes a document to be updated can efficiently update its own view. In this case, there is no need for that particular view to be updated.
2. What are the special requirements for dialog box resources used in a form view?
3. How do you size the frame of a form view so that it is the same size as its dialog box resource?
4. What is the difference between OnInitialUpdate and OnUpdate?
5. How do you prevent an MDI child window from being resized?
6. What function is called by a document class to notify views that the document has been changed?
7. What resources are identified through a shared resource identifier?
8. What view class enables you to use an edit control as a view?
9. What view class enables your view to have a large virtual area that is seen through a smaller scrolling viewport?
10. What class is used in an MDI application to associate a view class and a document class?
2. Modify the form view in DVTest so that it displays the number of items stored in the document.
Download Sample Programs - DVTest1.zip DVTest2.zip
|
|
|