Serialization
and Multiple View support
MDI applications are designed using the document-view architecture in which there could be many views associated with a document object. Further, the application can open multiple document objects at the same time. This logical breakdown allows the data related functions to be assigned to the document class object including serialization (saving/retrieving data to/from the hard disk) and the view related functions to be assigned to the view class object. The view object is contained inside a childframe object which in turn is contained inside a mainframe object.
Child frames
Serialization Main frame
As discussed previously, MFC framework provides several support functions (referred to as the Document-View interfaces). These are:
OnNewDocument – called when application starts or from the menu File->New is selected. We often write document object (data) initialization code here.
OnInitialUpdate - called one time for a view object when a new view is created. This could be when the application starts or if the user creates a new view by selecting Window->New from the menu.
UpdateAllViews – triggers a call to OnUpdate in each view class object connected to the current document. UpdateAllViews needs to be called when a user updates the data through one of the views. The code in the OnUpdate will usually then read the latest data from the document object and call the OnDraw to display it.
The view class provides a member function called GetDocument which returns a pointer to the associated document object.
Note that the different views connected to the document object can be of different types. For example, one of the views could be of CView type (default view type in an SDI or MDI application) while the other view could be of CFormView type with dialog controls. We will see how to associate multiple views with the same document object through the following example.
Create an MFC Appwizard(exe) type of project. Name the project mdimview. Select all the defaults.
In the last wizard screen, you will notice that there are 5 classes created for the project as shown below. MDI applications contain a childframe inside a mainframe and thus make it possible to associate multiple views with a document (or to open multiple documents at the same time – each with possibly multiple views)
Add a new header file called Student.h to the project. Type in the following code.
//--------Student.h------------
#ifndef
STUDENT_H
#define
STUDENT_H
class
Student : public CObject {
DECLARE_SERIAL(Student);
CString m_szName;
int m_nID;
public:
Student();
Student(const CString & nm,
int id);
void set(const CString & nm,
int id);
CString GetName() const;
int GetID() const;
virtual void Serialize(CArchive
& ar); // override serialize
};
#endif
Add another cpp file called Student.cpp to the project with the following code:
//-----Student.cpp-----------
#include
"stdafx.h"
#include
"Student.h"
IMPLEMENT_SERIAL
(Student,CObject,1);
Student::Student()
{ }
Student::Student(const
CString & nm, int id) {
m_szName = nm;
m_nID = id;
}
void
Student::set(const CString & nm, int id) {
m_szName = nm;
m_nID = id;
}
CString
Student::GetName() const { return m_szName;}
int
Student::GetID() const { return m_nID;}
void
Student::Serialize(CArchive & ar) {
if (ar.IsLoading())
ar >>
m_szName >> m_nID ;
else
ar <<
m_szName << m_nID ;
}
Modify the mdimviewDoc.h file to as shown below. The additions are shown in bold.
//
mdimviewDoc.h : interface of the CMdimviewDoc class
//
/////////////////////////////////////////////////////////////////////////////
#if
!defined(AFX_MDIMVIEWDOC_H__A9BFF795_B1C1_4DC7_8594_FFC9C05777C1__INCLUDED_)
#define
AFX_MDIMVIEWDOC_H__A9BFF795_B1C1_4DC7_8594_FFC9C05777C1__INCLUDED_
#if
_MSC_VER > 1000
#pragma
once
#endif
// _MSC_VER > 1000
#include "Student.h"
#include <afxtempl.h>
void AFXAPI SerializeElements(CArchive & ar, Student
**pSt, int cnt);
class
CMdimviewDoc : public CDocument
{
protected:
// create from serialization only
CMdimviewDoc();
DECLARE_DYNCREATE(CMdimviewDoc)
//
Attributes
public:
//
Operations
public:
int
GetStudentCount() const;
Student
* GetStudent(int num) const;
protected:
CArray <Student
*, Student * &> m_setofStudents;
Modify the mdimviewDoc.cpp file to add the SerializeElements function. Also add the code for GetStudentCount and GetStudent member functions.
void
AFXAPI SerializeElements(CArchive & ar, Student **pSt, int cnt) {
for (int i = 0; i < cnt; i++,
pSt++)
{
if
(ar.IsStoring())
(*pSt)->Serialize(ar);
else
{
Student
* pNewSt = new Student;
pNewSt
->Serialize(ar);
*pSt
= pNewSt;
}
}
}
int
CMdimviewDoc::GetStudentCount() const
{
return m_setofStudents.GetSize();
}
Student
* CMdimviewDoc::GetStudent(int num) const {
Student * pst = 0;
if (num <
m_setofStudents.GetSize())
pst =
m_setofStudents.GetAt(num);
return pst;
}
Modify the menu resource (IDR_MDIMVITYPE) to add a top level menu item called Student. Add a menu underneath it called Add Student with an ID of ID_STUDENT_ADD.
Insert a new dialog type resource in the project. The dialog design looks like:
The two edit boxes have IDs of IDC_NAME and IDC_ID respectively.
When you choose view->Class wizard, it will prompt you to assign a new class to the dialog resource. Give it a class name of CAddStudent.
Through the class wizard, associate two variables to the two edit boxes. For the name edit box, associate it with a CString variable called m_szName. Similarly, associate the ID edit box with an int variable called m_nID.
Add an event handler to the document class for the Add Student menu item. The code for the handler is:
void
CMdimviewDoc::OnStudentAdd()
{
// TODO: Add your command handler
code here
// TODO: Add your
command handler code here
CAddStudent d1;
if (d1.DoModal() == IDOK)
{
Student * pst =
new Student(d1.m_szName,d1.m_nID);
m_setofStudents.Add(pst);
UpdateAllViews(NULL);
SetModifiedFlag();
}
}
Modify the document cpp file to as shown below.
//
mdimviewDoc.cpp : implementation of the CMdimviewDoc class
//
#include
"stdafx.h"
#include
"mdimview.h"
#include
"mdimviewDoc.h"
#include "AddStudent.h"
#include "Student.h"
#ifdef
_DEBUG
….
….
void
CMdimviewDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add
storing code here
m_setofStudents.Serialize(ar);
}
else
{
// TODO: add
loading code here
m_setofStudents.Serialize(ar);
}
}
Through the class wizard add the code for OnDraw function to as shown below.
void
CMdimviewView::OnDraw(CDC* pDC)
{
CMdimviewDoc* pDoc =
GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native
data here
TEXTMETRIC tm;
pDC->GetTextMetrics(&tm);
int nLineHeight = tm.tmHeight +
tm.tmExternalLeading;
CPoint ptText(0,0);
for (int i = 0; i <
pDoc->GetStudentCount(); i++) {
CString s1;
Student * pst =
pDoc->GetStudent(i);
s1.Format("Student
Name = %s, ID = %d\n",pst->GetName(),
pst->GetID());
pDC->TextOut(ptText.x,
ptText.y,s1);
ptText.y +=
nLineHeight;
}
}
Also add the code for OnUpdate function (through class wizard):
void
CMdimviewView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized
code here and/or call the base class
InvalidateRect(NULL);
}
Build and execute the program. Test the serialization capabilities also.
You can add the OnNewDocument function to the document class to initialize the document data.
BOOL
CMdimviewDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization
code here
// (SDI documents will reuse this
document)
m_setofStudents.RemoveAll();
Student * pst =
new Student("First Student
",100);
m_setofStudents.Add(pst);
return True;
}
Using
different kind of View:
The default base class for the view is CView for the SDI and MDI applications. One problem with CView is that it does not allow you to add controls to the client area. If you need controls in the client area, then the base class for the view should be CFormView. Here is how the view class hierarchy is designed in MFC.
CView
CScrollView CEditView
CFormView
To change the view class to another type, you could choose a different base class when going through the application wizard steps. In the last dialog of the wizard, when it displays the different classes that will be created in the project, you can select a particular class and change its base class. Once an application is created, then the only way to change a view’s behavior is by modifying the “document template” which holds the relationship between a document and its associated view. We will explain this by modifying the current example to display the students in a CFormView type of object (instead of the CView).
Insert another dialog resource to the project. Design the dialog as shown below. Since this dialog will be used as a view, the entire dialog should have the properties of:
Style Child
Border None
Visible UnChecked
Titlebar Unchecked
The two edit boxes in the dialog have IDs of IDC_NAME and IDC_ID respectively. The List Control has an ID of IDC_LIST and the two buttons have IDs of IDC_CLOSE and IDC_APPLY.
Also after designing the dialog when the class wizard asks you to create a new class for the dialog, its base class should be CFormView, as shown below.
Through the class wizard associate a variable called m_lbDisplay of category control to the List control and m_szName (CString variable) to the first edit box, and m_nID (int variable) to the second edit box.
Write the following event handlers for the Apply button, close button, OnInitialUpdate and OnUpdate handlers in the CFormTest class.
void
CFormTest::OnApply()
{
// TODO: Add your control
notification handler code here
CMdimviewDoc * pDoc;
pDoc = (CMdimviewDoc *)
GetDocument();
ASSERT_VALID(pDoc);
UpdateData(TRUE);
if (m_szName.GetLength() >
0) // edit box is not empty
{
Student * pst =
new Student(m_szName,m_nID);
int index =
pDoc->m_setofStudents.Add(pst);
CString s1;
s1.Format("%s
%d",m_szName,m_nID);
m_lbDisplay.InsertItem(index,s1,0);
pDoc->SetModifiedFlag(TRUE);
pDoc->UpdateAllViews(this);
}
}
void
CFormTest::OnClose()
{
// TODO: Add your control
notification handler code here
PostMessage(WM_COMMAND,
ID_FILE_CLOSE);
}
void
CFormTest::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
// TODO: Add your specialized
code here and/or call the base class
CMdimviewDoc
* pDoc = (CMdimviewDoc *) GetDocument();
ASSERT_VALID(pDoc);
m_lbDisplay.DeleteAllItems();
for (int i = 0; i <
pDoc->GetStudentCount(); i++)
{
CString nm =
(pDoc->GetStudent(i))->GetName();
int id =
(pDoc->GetStudent(i))->GetID();
CString s1;
s1.Format("%s
%d",nm,id);
m_lbDisplay.InsertItem(i,s1,0);
}
}
void
CFormTest::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized
code here and/or call the base class
// TODO: Add your
specialized code here and/or call the base class
CMdimviewDoc * pDoc = (CMdimviewDoc *)
GetDocument();
ASSERT_VALID(pDoc);
m_lbDisplay.DeleteAllItems();
for (int i = 0; i <
pDoc->GetStudentCount(); i++)
{
CString nm =
(pDoc->GetStudent(i))->GetName();
int id =
(pDoc->GetStudent(i))->GetID();
CString s1;
s1.Format("%s
%d",nm,id);
m_lbDisplay.InsertItem(i,s1,0);
}
}
Add the following include files to the top of FormTest.cpp file.
#include
"Student.h"
#include
"mdimviewDoc.h"
You need to modify the document template in the mdimview.cpp file (InitInstance function) to as shown below.
pDocTemplate = new CMultiDocTemplate(
IDR_MDIMVITYPE,
RUNTIME_CLASS(CMdimviewDoc),
RUNTIME_CLASS(CChildFrame),
// custom MDI child frame
// RUNTIME_CLASS(CMdimviewView));
RUNTIME_CLASS(CFormTest));
AddDocTemplate(pDocTemplate);
Providing
Different View Types to a Document:
Note that the application class stores pointer(s) to the CMultiDocTemplate object and passes it to the CMainFrame class when a new view needs to be generated. Thus to provide different kinds of views to a document, the first thing we need to do is to create different CMultiDocTemplate objects in the InitInstance of the application class.
Modify the application class (mdimview.h file) to add two CDocTemplate pointers and the corresponding get functions as shown below.
CDocTemplate * GetCViewTemplate() const;
CDocTemplate * GetCFormViewTemplate() const;
private:
CDocTemplate * m_pCViewTemplate;
CDocTemplate
* m_pCFormViewTemplate;
Then modify the InitInstance function to create two CMultiDocTemplate objects as shown below.
Write the code for GetCViewTemplate( ) and GetCFormViewTemplate( ) functions in the mdimview.cpp file, as shown below.
// CMultiDocTemplate* pDocTemplate; before two multidoc objects
m_pCFormViewTemplate = new CMultiDocTemplate(
IDR_MDIMVITYPE,
RUNTIME_CLASS(CMdimviewDoc),
RUNTIME_CLASS(CChildFrame), // custom
MDI child frame
// RUNTIME_CLASS(CMdimviewView));
RUNTIME_CLASS(CFormTest));
AddDocTemplate(m_pCFormViewTemplate);
m_pCViewTemplate = new CMultiDocTemplate(
IDR_MDIMVITYPE,
RUNTIME_CLASS(CMdimviewDoc),
RUNTIME_CLASS(CChildFrame), // custom
MDI child frame
RUNTIME_CLASS(CMdimviewView));
AddDocTemplate(m_pCViewTemplate);
Note that, if you had started with a CFormView type of view and wanted to add a CView type of view, you could have added a new class to the application through the class wizard. This new class would be given a name of CMdimviewView and its base class would be CView. However, in our current example, if you recall, we had started with a standard MDI application (which will have a CView type of view), then we had added another dialog class to it (derived from CFormView), so we have already both the view classes, and all we need to do is add the document templates and associated code.
Write the Get functions for the two document template objects in the mdimview.cpp file, as shown below.
CDocTemplate
* CMdimviewApp::GetCFormViewTemplate() const {
return m_pCFormViewTemplate ;
}
CDocTemplate
* CMdimviewApp::GetCViewTemplate() const {
return m_pCViewTemplate ;
}
If you build the program so far and try to run it, you will get a dialog in the beginning as shown below.
If you choose the top choice, a form view MDI GUI will be displayed, as shown below (because the first document template is a form view).
If you stop and execute the program again, and choose the second Mdimvi choice in the dialog, the program will run with a CView type of view as shown below.
Now we will add menu options to choose the type of view we want. Note that in an MDI application, each child window can have its own set of menus (possibly different from the main frame). In our application, we already have two menu resources so far, called:
IDR_MAINFRAME and IDR_MVITYPE,
Select the IDR_MVITYPE resource and from the edit menu, choose copy, then paste it to create another copy of the resource. Change the name of the resource to IDR_MDIFORMTYPE. Add two menu items underneath the window menu in both the IDR_MVITYPE and IDR_MDIFORMTYPE menus. The menus have IDs of ID_WINDOW_FORMDISPLAY (caption “Form View Display”) and ID_WINDOW_CVIEWDISPLAY (caption “CView Display”). Add the following handler to the two menu items in the mainframe class:
void
CMainFrame::OnWindowFormdisplay()
{
// TODO: Add your command handler code here
CMDIChildWnd * pActiveChild = MDIGetActive();
if (pActiveChild != 0) {
CDocument * pDocument =
pActiveChild->GetActiveDocument();
if(pDocument != 0)
{
CMdimviewApp * pApp =
(CMdimviewApp *) AfxGetApp();
CDocTemplate * pTemp;
CFrameWnd * pFrame;
pTemp =
pApp->GetCFormViewTemplate();
pFrame=pTemp->CreateNewFrame(pDocument,pActiveChild);
if (pFrame != 0)
pTemp->InitialUpdateFrame(pFrame,pDocument);
}
}
}
void
CMainFrame::OnWindowCviewdisplay()
{
// TODO: Add your command handler code here
CMDIChildWnd * pActiveChild = MDIGetActive();
if (pActiveChild != 0) {
CDocument * pDocument =
pActiveChild->GetActiveDocument();
if(pDocument != 0)
{
CMdimviewApp * pApp =
(CMdimviewApp *) AfxGetApp();
CDocTemplate * pTemp;
CFrameWnd * pFrame;
pTemp = pApp->GetCViewTemplate();
pFrame=pTemp->CreateNewFrame(pDocument,pActiveChild);
if (pFrame != 0)
pTemp->InitialUpdateFrame(pFrame,pDocument);
}
}
}
The code in the mdimview.cpp file, InitInstance function can now be modified to point to the two new resources in the document templates.
// CMultiDocTemplate* pDocTemplate; before two multidoc objects
m_pCFormViewTemplate = new CMultiDocTemplate(
IDR_MDIFORMTYPE,
RUNTIME_CLASS(CMdimviewDoc),
RUNTIME_CLASS(CChildFrame), // custom
MDI child frame
// RUNTIME_CLASS(CMdimviewView));
RUNTIME_CLASS(CFormTest));
AddDocTemplate(m_pCFormViewTemplate);
m_pCViewTemplate = new CMultiDocTemplate(
IDR_MDIMVITYPE,
RUNTIME_CLASS(CMdimviewDoc),
RUNTIME_CLASS(CChildFrame), // custom
MDI child frame
RUNTIME_CLASS(CMdimviewView));
// AddDocTemplate(m_pCViewTemplate);
Note that the above code follows the general guidelines of:
1. Get a pointer to the active child window.
2. Get a pointer to the active document window.
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.
6. Update the frame
Through the class wizard, add the OnInitialUpdate and OnUpdate handlers for the view class, as shown below.
void CMdimviewView::OnUpdate(CView*
pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized code here and/or call
the base class
InvalidateRect(NULL);
}
void
CMdimviewView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call
the base class
InvalidateRect(NULL);
}
We can assign a different icon and a string resource to the new document template corresponding to the form view. You can copy the icon resource to the clip board from the edit menu and paste it back. Change the resource name to IDR_MDIFORMTYPE. Double click on it to design a different icon for it. You should choose the number of pixels to be 16x16 for the icon when you are designing or changing it in the bit map editor.
When we have a dialog or form type of view for an MDI application, it is better if the dialog occupies the proper child frame area, i.e., the size of frame is equal to the size of the dialog.. To do this, add the following code to the OnInitialUpdate function in the CFormTest class (FormTest.cpp file).
void
CFormTest::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call
the base class
ResizeParentToFit(FALSE);
ResizeParentToFit(FALSE);
…
…
To prevent the child frame from being resized, you can add the following code to the CChildFrame class.
BOOL
CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by
modifying
// the
CREATESTRUCT cs
cs.style
&= ~WS_THICKFRAME;
cs.style &= ~WS_MAXIMIZEBOX;
if( !CMDIChildWnd::PreCreateWindow(cs) )
return FALSE;
return TRUE;
}
Build and execute the program.