MDI Applications in MFC
Visual C++ and MFC provide excellent support for creating Multiple Document Interface (MDI) applications. An MDI application allows multiple documents to be open at the same time, with each document possibly having multiple views. MFC application wizard creates five different classes for an MDI application.
1. Application class
2. Document class
3. Mainframe class
4. Childframe class
5. View class
Multiple views that are connected to a particular document object are displayed inside a child frame. In addition to the above classes, MFC framework supports important functions and events that help implement an MDI application properly.
These are:
OnNewDocument – member function of document class, invoked when the program starts, or when File->New menu is used to create a new document. We can write any code for document data initialization here.
OnInitialUpdate – member function of the view class, invoked when a new view is created through Window->New menu. When a program is run, it is given one view by default, so it is called one time for the default view before it is displayed. This is a good place to do any initialization for the view class.
OnUpdate – member function of the view class, called when UpdateAllViews is invoked as a result of user changing the data through one of the views.
UpdateAllViews – Whenever the user changes the document data through one of the views, we need to call UpdateAllViews (document class member function) so that other views can be informed of the changing data. It triggers a call to OnUpdate in each view object.
MFC application wizard also provides the support for serialization of document data.
Serializaton: Serialization refers to being able to save the state of an object on the hard disk (in a file) when the program is quitting (or from the File->Save menu), and also be able to restore the saved state of an object from a file, when an existing document is opened from the File->open menu.
You can write the serialization code yourself by creating an archive object and tying it to a file object. Then sending the class data to the archive object to save it, or reading the class data from the archive object and putting it into class variables when trying to load data from the File->Open menu.
Example of Serialization: Create a dialog based application that has a multi-line edit box where some one can type-in comments, a simple edit box where some one could enter their integer ID, and two buttons, one with the caption “Save Data”, other with the caption “Load Data”.
When some one clicks on the “Save Data” button, your program should be able to save the data typed in the comments edit box and the id edit box into a file called “t1.dat”. Similarly when some one clicks the “Load Data” button, your program should be able to read the data from the “t1.dat” file and initialize the two edit boxes according to this data.
Create a MFC app. Wizard dialog based application. Name the project SerializeDlg.
Using the classwizard, assign a CString variable called m_strComments to the Comments edit box, and an integer variable called m_nID to the ID edit box.
Write the following code for the two button handlers:
void
CSerializeDlgDlg::OnSavetofile()
{
// TODO: Add your control
notification handler code here
UpdateData(TRUE);
CFile f1;
f1.Open("C:\\temp\\t1.dat",
CFile::modeCreate | CFile::modeWrite);
//create an archive object and tie it to
the file object
CArchive ar(&f1,CArchive::store);
//serialize the data for the two edit boxes
ar << m_strComments << m_nID;
ar.Close();
f1.Close();
}
void
CSerializeDlgDlg::OnLoadfromfile()
{
// TODO: Add your control
notification handler code here
CFile f1;
if (f1.Open("C:\\temp\\t1.dat",
CFile::modeRead) == FALSE)
{
MessageBox("Could not open file");
return;
}
//create an archive object and tie it to
the file object
CArchive ar(&f1,CArchive::load);
//serialize the data for the two edit boxes
ar >> m_strComments >> m_nID;
ar.Close();
UpdateData(FALSE);
f1.Close();
}
Example: Let us create an MDI application that will allow the user to draw a single circle of a fixed size at the point where the user clicks a mouse. Create an MFC AppWizard(exe) called mdidraw. Choose the project type to be be Multiple Documents from the next dialog.
You will notice that the wizard will create five classes in your application.
Add two data members to the document class header file as shown below:
class
CMdidrawDoc : public CDocument
{
protected:
// create from serialization only
CMdidrawDoc();
DECLARE_DYNCREATE(CMdidrawDoc)
//
Attributes
public:
int m_posx; // for the center position of the circle
int m_posy;
Add two similar data members to the view class header file also.
class
CMdidrawView : public CView
{
protected:
// create from serialization only
CMdidrawView();
DECLARE_DYNCREATE(CMdidrawView)
//
Attributes
public:
CMdidrawDoc* GetDocument();
int m_posx;
int m_posy;
Using the class wizard, add two functions to the document class cpp file as shown below.
BOOL
CMdidrawDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization
code here
// (SDI documents will reuse this
document)
m_posx = 100;
m_posy = 100;
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
//
CMdidrawDoc serialization
void
CMdidrawDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add
storing code here
ar << m_posx
<< m_posy;
}
else
{
// TODO: add
loading code here
ar >> m_posx
>> m_posy;
}
}
Similarly using the class wizard, add the following functions to the view class cpp file.
void CMdidrawView::OnDraw(CDC* pDC)
{
CMdidrawDoc* pDoc =
GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native
data here
RECT br;
br.left = m_posx - 40;
br.top = m_posy + 40;
br.bottom = m_posy - 40;
br.right = m_posx + 40;
pDC->Ellipse(&br);
}
void
CMdidrawView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized
code here and/or call the base class
CMdidrawDoc* pDoc;
pDoc = GetDocument();
m_posx = pDoc->m_posx;
m_posy = pDoc->m_posy;
}
void
CMdidrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler
code here and/or call default
m_posx = point.x;
m_posy = point.y;
CMdidrawDoc* pDoc;
pDoc = GetDocument();
pDoc->m_posx = m_posx;
pDoc->m_posy = m_posy;
InvalidateRect(NULL);
pDoc->UpdateAllViews(this);
pDoc->SetModifiedFlag(TRUE);
CView::OnLButtonDown(nFlags,
point);
}
void
CMdidrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized
code here and/or call the base class
CMdidrawDoc* pDoc;
pDoc = GetDocument();
m_posx = pDoc->m_posx;
m_posy = pDoc->m_posy;
InvalidateRect(NULL);
}
Build and run the program. Test different features of the MDI application such as creating multiple views, being able to save the drawing and open it later, being able to open multiple documents etc..
Enhancing the drawing application by adding Tool bar feature:
Create an MDI type application called mdidraw2. We will provide a toolbar feature and also to be able to draw one of two different shapes.
Toolbar is a resource in VC++, and there is a visual editor for creating/modifying this resource. From the resource view, double click on the Toolbar “IDR_MAINFRAME” resource. Create two toolbar buttons, one with a picture of a rectangle (assign it an ID of ID_DRAW_RECTANGLE), and other with a picture of circle (assign it an ID of ID_DRAW_CIRCLE), as shown below.
You will notice that there are two menu resources, one is for the main frame, and the other for a child frame. Modify the child frame menu resource (IDR_MDIDRATYPE) to include a top level menu called “Draw”. Put two menu items under it called “Draw Rectangle” (with ID of ID_DRAW_RECTANGLE) and “Draw Circle” (with ID of ID_DRAW_CIRCLE). Note that we are assigning the same IDs to the menu items as we did for the tool bar buttons, because tool bar buttons simply act as short cuts to the menu items.
Add the following data members to the document class header file.
class
CMdidraw2Doc : public CDocument
{
protected:
// create from serialization only
CMdidraw2Doc();
DECLARE_DYNCREATE(CMdidraw2Doc)
//
Attributes
public:
CString m_shape; // type of
shape, circle or rectangle
int m_Rx1pos; // rectangle top left (bounding rectangle
for the circle or rectangle)
int m_Ry1pos;
int m_Rx2pos; // rectangle bottom right pos
int m_Ry2pos;
//
Operations
Add the following data members to the view class header file. Note that we are adding an extra data member called m_SelectedShapeonToolbar to keep track of what is the current drawing mode.
class
CMdidraw2View : public CView
{
protected:
// create from serialization only
CMdidraw2View();
DECLARE_DYNCREATE(CMdidraw2View)
//
Attributes
public:
CMdidraw2Doc* GetDocument();
CString m_shape; // type of
shape, circle or rectangle
int m_Rx1pos; // rectangle top left
int m_Ry1pos;
int m_Rx2pos; // rectangle bottom right pos
int m_Ry2pos;
CString m_SelectedShapeOnToolbar;
int m_track;
Write the following code in the document class cpp file for OnNewDocument() function.
BOOL
CMdidraw2Doc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization
code here
// (SDI documents will reuse this
document)
m_shape
= "RECTANGLE";
m_Rx1pos = 100; // rectangle top left
m_Ry1pos = 100;
m_Rx2pos = 200; // rectangle bottom right pos
m_Ry2pos = 200;
return TRUE;
}
Through the classwizard, add the OnInitialUpdate function to the view class and type the following code (shown in bold).
void
CMdidraw2View::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized
code here and/or call the base class
// copy data member values from
the document object
Cmdidraw2Doc * pDoc =
GetDocument();
m_shape = pDoc->m_shape;
m_Rx1pos =
pDoc->m_Rx1pos; // rectangle top
left
m_Ry1pos = pDoc->m_Ry1pos;
m_Rx2pos =
pDoc->m_Rx2pos; // rectangle bottom
right pos
m_Ry2pos = pDoc->m_Ry2pos;
}
Since our program can draw only either a circle or a rectangle, we will indicate the current active shape by putting a check mark on the menu item for the draw circle or the draw rectangle. There is a special event that called UPDATE_COMMAND_UI that is triggered for each menu item before it is displayed. We can place checkmark code in this event handler. Through the class wizard, add the code for two UPDATE_COMMAND_UI event handlers in the view class (one for ID_DRAW_CIRCLE, one for ID_DRAW_RECTANGLE).
void
CMdidraw2View::OnUpdateDrawCircle(CCmdUI* pCmdUI)
{
// TODO: Add your command update
UI handler code here
if (m_SelectedShapeOnToolbar ==
"CIRCLE")
pCmdUI->SetCheck(1);
else
pCmdUI->SetCheck(0);
}
void
CMdidraw2View::OnUpdateDrawRectangle(CCmdUI* pCmdUI)
{
// TODO: Add your command update
UI handler code here
if (m_SelectedShapeOnToolbar ==
"RECTANGLE")
pCmdUI->SetCheck(1);
else
pCmdUI->SetCheck(0);
}
Write the following event handlers (through the class wizard) for the “Draw Circle” and “Draw Rectangle” menu items.
void
CMdidraw2View::OnDrawCircle()
{
// TODO: Add your
command handler code here
m_SelectedShapeOnToolbar =
"CIRCLE";
}
void
CMdidraw2View::OnDrawRectangle()
{
// TODO: Add your command handler
code here
m_SelectedShapeOnToolbar =
"RECTANGLE" ;
}
Also add the following code to the constructor of the view class (in the view cpp file);
CMdidraw2View::CMdidraw2View()
{
// TODO: add construction code
here
m_SelectedShapeOnToolbar =
"RECTANGLE" ;
m_track = 0;
}
Add the following code to the OnDraw function in the view class.
void
CMdidraw2View::OnDraw(CDC* pDC)
{
CMdidraw2Doc* pDoc =
GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native
data here
CRect r1(m_Rx1pos,
m_Ry1pos,m_Rx2pos,m_Ry2pos);
if (m_shape ==
"CIRCLE")
pDC->Ellipse(&r1);
else
pDC->Rectangle(&r1);
}
Add the event handlers for the WM_LBUTTONDOWN, WM_LBUTTONUP, and WM_MOUSEMOVE events in the view class.
void
CMdidraw2View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler
code here and/or call default
m_Rx1pos = point.x;
m_Ry1pos = point.y;
m_track = 1;
CView::OnLButtonDown(nFlags,
point);
}
void
CMdidraw2View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler
code here and/or call default
m_Rx2pos = point.x;
m_Ry2pos = point.y;
m_track = 0;
m_shape =
m_SelectedShapeOnToolbar;
InvalidateRect(NULL);
CMdidraw2Doc * pDoc =
GetDocument();
pDoc->m_Rx1pos = m_Rx1pos;
pDoc->m_Ry1pos = m_Ry1pos;
pDoc->m_Rx2pos = m_Rx2pos;
pDoc->m_Ry2pos = m_Ry2pos;
pDoc->m_shape = m_shape;
pDoc->SetModifiedFlag(TRUE);
pDoc->UpdateAllViews(this);
CView::OnLButtonUp(nFlags,
point);
}
void
CMdidraw2View::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler
code here and/or call default
if (m_track == 1) {
CRect
pRectold(m_Rx1pos,m_Ry1pos, m_Rx2pos,m_Ry2pos);
m_Rx2pos =
point.x;
m_Ry2pos =
point.y;
CRect
pRectnew(m_Rx1pos,m_Ry1pos, m_Rx2pos,m_Ry2pos);
m_shape =
m_SelectedShapeOnToolbar;
InvalidateRect(pRectold,TRUE);
InvalidateRect(pRectnew,TRUE);
}
CView::OnMouseMove(nFlags,
point);
}
Write the following OnUpdate function through the class wizard in the view class.
void
CMdidraw2View::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
// TODO: Add your specialized
code here and/or call the base class
CMdidraw2Doc * pDoc =
GetDocument();
m_Rx1pos = pDoc->m_Rx1pos;
m_Ry1pos = pDoc->m_Ry1pos;
m_Rx2pos = pDoc->m_Rx2pos;
m_Ry2pos = pDoc->m_Ry2pos;
m_shape = pDoc->m_shape;
InvalidateRect(NULL);
}
Write the following serialization code in the document class.
void
CMdidraw2Doc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add
storing code here
ar<< m_shape
<< m_Rx1pos << m_Ry1pos << m_Rx2pos << m_Ry2pos;
}
else
{
// TODO: add
loading code here
ar>> m_shape
>> m_Rx1pos >> m_Ry1pos >> m_Rx2pos >> m_Ry2pos;
}
}
Build and test the application.