The Ex15b Example: A Multi-View SDI Application
This second SDI example improves on Ex15a in the following ways:
Instead of a single embedded CStudent object, the document contains a list of CStudent objects. (Now you see the reason for using the CStudent class instead of making m_strName and m_nGrade data members of the document.)
Toolbar buttons allow the user to sequence through the list.
The application is structured to allow the addition of extra views. The Edit Clear All command is now routed to the document object, so the document's UpdateAllViews function and the view's OnUpdate function are brought into play.
The student-specific view code is isolated so that the CEx15bView class can later be transformed into a base class that contains only general-purpose code. Derived classes can override selected functions to accommodate lists of application-specific objects.
The Ex15b window, shown in Figure 15-1. The toolbar buttons are enabled only when appropriate. The Next (down arrow) button, for example, is disabled when we're positioned at the bottom of the list.

Figure 15-2: The Ex15b program in action.
The toolbar buttons function as follows.
Button | Function |
---|---|
![]() | Retrieves the first student record |
![]() | Retrieves the last student record |
![]() | Retrieves the previous student record |
![]() | Retrieves the next student record |
![]() | Inserts a new student record |
![]() | Deletes the current student record |
The Clear button in the view window clears the contents of the Name and Grade edit controls. The Clear All command on the Edit menu deletes all the student records in the list and clears the view's edit controls.
This example deviates from the step-by-step format in the previous examples. There's now more code, so we'll simply show selected code and the resource requirements. Boldface code indicates additional code or other changes that you enter in the output from the MFC Application Wizard and the code wizards available from Class View's Properties window. The frequent use of TRACE statements lets you follow the program's execution in the debugging window.
Resource Requirements
The file

Toolbar
The toolbar (visible in Figure 15-2) was created by erasing the Edit Cut, Copy, and Paste tiles (fourth, fifth, and sixth from the left) and replacing them with six new patterns. The Flip Vertical command (on the Image menu) was used to duplicate some of the tiles. The

Student Menu
It isn't absolutely necessary to have menu commands that correspond to the new toolbar buttons. (Class View's Properties window allows you to map toolbar button commands just as easily as menu commands.) However, most applications for Windows have corresponding menu commands, so users generally expect them.
Edit Menu
On the Edit menu, the clipboard commands are replaced by the Clear All command. See step 2 of the Ex15a example for an illustration of the Edit menu.
The IDD_EX15B_FORM Dialog Template
The IDD_EX15B_FORM dialog template is similar to the Ex15a dialog box shown in Figure 15-1 except that the Enter pushbutton has been replaced by the Clear pushbutton.The following IDs identify the controls:
Control | ID |
---|---|
Name edit control | IDC_NAME |
Grade edit control | IDC_GRADE |
Clear pushbutton | IDC_CLEAR |
The controls' styles are the same as for the Ex15a program.
Code Requirements
Here's a list of the files and classes in the Ex15b example.
Header File | Source Code File | Classes | Description |
---|---|---|---|
![]() | ![]() | CEx15bAppCAboutDlg | Application class (from the MFC Application Wizard) About dialog box |
![]() | ![]() | CMainFrame | SDI main frame |
![]() | ![]() | Ex15bDoc | Student document |
![]() | ![]() | Ex15bView | Student form view (derived from CFormView) |
![]() | ![]() | Cstudent | Student record (similar to Ex15a) |
![]() | ![]() | Includes the standard precompiled headers |
CEx15bApp
The files


CMainFrame
The code for the CMainFrame class in

CStudent
This is the code from Ex15a, except for the following line added at the end of

typedef CTypedPtrList<CObList, CStudent*> CStudentList;
Note | Use of the MFC template collection classes requires the following statement in ![]()
|
CEx15bDoc
The MFC Application Wizard originally generated the CEx15bDoc class. The code used in the Ex15b example is shown here:
Ex15bDoc.h
// Ex15bDoc.h : interface of the CEx15bDoc class
//
#pragma once
#include "student.h"
class CEx15bDoc : public CDocument
{
protected: // create from serialization only
CEx15bDoc();
DECLARE_DYNCREATE(CEx15bDoc)
// Attributes
public:
CStudentList* GetList() {
return &m_studentList;
}
// Operations
public:
// Overrides
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
// Implementation
public:
virtual ~CEx15bDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
private:
CStudentList m_studentList;
};
Ex15bDoc.cpp
// Ex15bDoc.cpp : implementation of the CEx15bDoc class
//
#include "stdafx.h"
#include "Ex15b.h"
#include "Ex15bDoc.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CEx15bDoc
IMPLEMENT_DYNCREATE(CEx15bDoc, CDocument)
BEGIN_MESSAGE_MAP(CEx15bDoc, CDocument)
ON_COMMAND(ID_EDIT_CLEARALL, OnEditClearall)
ON_UPDATE_COMMAND_UI(ID_EDIT_CLEARALL, OnUpdateEditClearall)
END_MESSAGE_MAP()
// CEx15bDoc construction/destruction
CEx15bDoc::CEx15bDoc()
{
TRACE("Entering CEx15bDoc constructor\n");
#ifdef _DEBUG
afxDump.SetDepth(1); // Ensure dump of list elements
#endif // _DEBUG
}
CEx15bDoc::~CEx15bDoc()
{
}
BOOL CEx15bDoc::OnNewDocument()
{
TRACE("Entering CEx15bDoc::OnNewDocument\n");
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
return TRUE;
}
// CEx15bDoc serialization
void CEx15bDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
// CEx15bDoc diagnostics
#ifdef _DEBUG
void CEx15bDoc::AssertValid() const
{
CDocument::AssertValid();
}
void CEx15bDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
dc << "\n" << m_studentList << "\n";
}
#endif //_DEBUG
// CEx15bDoc commands
void CEx15bDoc::DeleteContents()
{
#ifdef _DEBUG
Dump(afxDump);
#endif
while (m_studentList.GetHeadPosition()) {
delete m_studentList.RemoveHead();
}
}
void CEx15bDoc::OnEditClearall()
{
DeleteContents();
UpdateAllViews(NULL);
}
void CEx15bDoc::OnUpdateEditClearall(CCmdUI *pCmdUI)
{
pCmdUI->Enable(!m_studentList.IsEmpty());
}
Message Handlers for CEx15bDoc
The Edit Clear All command is handled in the document class. The following message handlers were added through Class View's Properties window.
Object ID | Message | Member Function |
---|---|---|
ID_EDIT_CLEARALL | COMMAND | OnEditClearall |
ID_EDIT_CLEARALL | ON_UPDATE_COMMAND_UI | OnUpdateEditClearall |
Data Members
The document class provides for an embedded CStudentList object, the m_stu-dentList data member, which holds pointers to CStudent objects. The list object is constructed when the CEx15bDoc object is constructed, and it is destroyed at program exit. CStudentList is a typedef for a CTypedPtrList for CStudent pointers.
Constructor
The document constructor sets the depth of the dump context so that a dump of the list causes dumps of the individual list elements.
GetList
The inline GetList function helps isolate the view from the document. The document class must be specific to the type of object in the list—in this case, objects of the class CStudent. A generic list view base class, however, can use a member function to get a pointer to the list without knowing the name of the list object.
DeleteContents
The DeleteContents function is a virtual override function that is called by other document functions and by the application framework. Its job is to remove all student object pointers from the document's list and to delete those student objects. An important point to remember here is that SDI document objects are reused after they're closed. DeleteContents also dumps the student list.
Dump
The MFC Application Wizard generates the Dump function skeleton between the lines #ifdef _DEBUG and #endif. Because the afxDump depth was set to 1 in the document constructor, all the CStudent objects contained in the list are dumped.
CEx15bView
The code for the CEx15bView class is shown in the following code listing.
Ex15bView.h
// Ex15bView.h : interface of the CEx15bView class
//
#pragma once
class CEx15bView : public CFormView
{
protected:
POSITION m_position; // current position in document list
CStudentList* m_pList; // copied from document
protected: // create from serialization only
CEx15bView();
DECLARE_DYNCREATE(CEx15bView)
public:
enum{ IDD = IDD_EX15B_FORM };
// Attributes
public:
CEx15bDoc* GetDocument() const;
// Operations
public:
// Overrides
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual void OnInitialUpdate(); // called first time after construct
// Implementation
public:
virtual ~CEx15bView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
virtual void ClearEntry();
virtual void InsertEntry(POSITION position);
virtual void GetEntry(POSITION position);
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnStudentHome();
afx_msg void OnStudentDelete();
afx_msg void OnStudentEnd();
afx_msg void OnStudentInsert();
afx_msg void OnStudentNext();
afx_msg void OnStudentPrevious();
afx_msg void OnUpdateStudentHome(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentDelete(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentEnd(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentNext(CCmdUI *pCmdUI);
afx_msg void OnUpdateStudentPrevious(CCmdUI *pCmdUI);
int m_nGrade;
CString m_strName;
protected:
virtual void OnUpdate(Cview* /*pSender/,
LPARAM /*lHint*/, CObject* /*pHint*/)
public:
afx_msg void OnBnClickedClear();
};
#ifndef _DEBUG // debug version in Ex15bView.cpp
inline CEx15bDoc* CEx15bView::GetDocument() const
{ return reinterpret_cast<CEx15bDoc*>(m_pDocument); }
#endif
Ex15bView.cpp
// Ex15bView.cpp : implementation of the CEx15bView class
//
#include "stdafx.h"
#include "Ex15b.h"
#include "Ex15bDoc.h"
#include "Ex15bView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CEx15bView
IMPLEMENT_DYNCREATE(CEx15bView, CFormView)
BEGIN_MESSAGE_MAP(CEx15bView, CFormView)
ON_COMMAND(ID_STUDENT_HOME, OnStudentHome)
ON_COMMAND(ID_STUDENT_DELETE, OnStudentDelete)
ON_COMMAND(ID_STUDENT_END, OnStudentEnd)
ON_COMMAND(ID_STUDENT_INSERT, OnStudentInsert)
ON_COMMAND(ID_STUDENT_NEXT, OnStudentNext)
ON_COMMAND(ID_STUDENT_PREVIOUS, OnStudentPrevious)
ON_UPDATE_COMMAND_UI(ID_STUDENT_HOME, OnUpdateStudentHome)
ON_UPDATE_COMMAND_UI(ID_STUDENT_DELETE, OnUpdateStudentDelete)
ON_UPDATE_COMMAND_UI(ID_STUDENT_END, OnUpdateStudentEnd)
ON_UPDATE_COMMAND_UI(ID_STUDENT_NEXT, OnUpdateStudentNext)
ON_UPDATE_COMMAND_UI(ID_STUDENT_PREVIOUS, OnUpdateStudentPrevious)
ON_BN_CLICKED(IDC_CLEAR, OnBnClickedClear)
END_MESSAGE_MAP()
// CEx15bView construction/destruction
CEx15bView::CEx15bView()
: CFormView(CEx15bView::IDD)
, m_nGrade(0)
, m_strName(_T("))
, m_position(NULL)
{
TRACE("Entering CEx15bView constructor\n");
}
CEx15bView::~CEx15bView()
{
}
void CEx15bView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
DDX_Text(pDX, IDC_GRADE, m_nGrade);
DDX_Text(pDX, IDC_NAME, m_strName);
}
BOOL CEx15bView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CFormView::PreCreateWindow(cs);
}
void CEx15bView::OnInitialUpdate()
{
TRACE("Entering CEx15bView::OnInitialUpdate\n");
m_pList = GetDocument()->GetList();
CFormView::OnInitialUpdate();
}
// CEx15bView diagnostics
#ifdef _DEBUG
void CEx15bView::AssertValid() const
{
CFormView::AssertValid();
}
void CEx15bView::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
CEx15bDoc* CEx15bView::GetDocument() const // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx15bDoc)));
return (CEx15bDoc*)m_pDocument;
}
#endif //_DEBUG
// CEx15bView message handlers
void CEx15bView::OnStudentHome()
{
TRACE("Entering CEx15bView::OnStudentHome\n");
// need to deal with list empty condition
if (!m_pList->IsEmpty()) {
m_position = m_pList->GetHeadPosition();
GetEntry(m_position);
}
}
void CEx15bView::OnUpdateStudentHome(CCmdUI *pCmdUI)
{
// called during idle processing and when Student menu drops down
POSITION pos;
// enables button if list not empty and not at home already
pos = m_pList->GetHeadPosition();
pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}
void CEx15bView::OnStudentDelete()
{
// deletes current entry and positions to next one or head
POSITION pos;
TRACE("Entering CEx15bView::OnStudentDelete\n");
if ((pos = m_position) != NULL) {
m_pList->GetNext(pos);
if (pos == NULL) {
pos = m_pList->GetHeadPosition();
TRACE("GetHeadPos = %ld\n", pos);
if (pos == m_position) {
pos = NULL;
}
}
GetEntry(pos);
CStudent* ps = m_pList->GetAt(m_position);
m_pList->RemoveAt(m_position);
delete ps;
m_position = pos;
GetDocument()->SetModifiedFlag();
GetDocument()->UpdateAllViews(this);
}
}
void CEx15bView::OnUpdateStudentDelete(CCmdUI *pCmdUI)
{
// called during idle processing and when Student menu drops down
pCmdUI->Enable(m_position != NULL);
}
void CEx15bView::OnStudentEnd()
{
TRACE("Entering CEx15bView::OnStudentEnd\n");
if (!m_pList->IsEmpty()) {
m_position = m_pList->GetTailPosition();
GetEntry(m_position);
}
}
void CEx15bView::OnUpdateStudentEnd(CCmdUI *pCmdUI)
{
// called during idle processing and when Student menu drops down
POSITION pos;
// enables button if list not empty and not at end already
pos = m_pList->GetTailPosition();
pCmdUI->Enable((m_position != NULL) && (pos != m_position));
}
void CEx15bView::OnStudentInsert()
{
TRACE("Entering CEx15bView::OnStudentInsert\n");
InsertEntry(m_position);
GetDocument()->SetModifiedFlag();
GetDocument()->UpdateAllViews(this);
}
void CEx15bView::OnStudentNext()
{
POSITION pos;
TRACE("Entering CEx15bView::OnStudentNext\n");
if ((pos = m_position) != NULL) {
m_pList->GetNext(pos);
if (pos) {
GetEntry(pos);
m_position = pos;
}
}
}
void CEx15bView::OnUpdateStudentNext(CCmdUI *pCmdUI)
{
OnUpdateStudentEnd(pCmdUI);
}
void CEx15bView::OnStudentPrevious()
{
POSITION pos;
TRACE("Entering CEx15bView::OnStudentPrevious\n");
if ((pos = m_position) != NULL) {
m_pList->GetPrev(pos);
if (pos) {
GetEntry(pos);
m_position = pos;
}
}
}
void CEx15bView::OnUpdateStudentPrevious(CCmdUI *pCmdUI)
{
OnUpdateStudentHome(pCmdUI);
}
void CEx15bView::OnUpdate(CView* /*pSender*/,
LPARAM /*lHint*/, CObject* /*pHint*/)
{
// called by OnInitialUpdate and by UpdateAllViews
TRACE("Entering CEx15bView::OnUpdate\n");
m_position = m_pList->GetHeadPosition();
GetEntry(m_position); // initial data for view
}
void CEx15bView::ClearEntry()
{
m_strName = ";
m_nGrade = 0;
UpdateData(FALSE);
((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));
}
void CEx15bView::GetEntry(POSITION position)
{
if (position) {
CStudent* pStudent = m_pList->GetAt(position);
m_strName = pStudent->m_strName;
m_nGrade = pStudent->m_nGrade;
}
else {
ClearEntry();
}
UpdateData(FALSE);
}
void CEx15bView::InsertEntry(POSITION position)
{
if (UpdateData(TRUE)) {
// UpdateData returns FALSE if it detects a user error
CStudent* pStudent = new CStudent;
pStudent->m_strName = m_strName;
pStudent->m_nGrade = m_nGrade;
m_position = m_pList->InsertAfter(m_position, pStudent);
}
}
void CEx15bView::OnBnClickedClear()
{
TRACE("Entering CEx15bView::OnBnClickedClear\n");
ClearEntry();
}
Message Handlers for CEx15bView
Class View's Properties window was used to map the CEx15bView Clear pushbutton notification message as follows:
Object ID | Message | Member Function |
---|---|---|
IDC_CLEAR | BN_CLICKED | OnBnClickedClear |
Because CEx15bView is derived from CFormView, Class View supports the definition of dialog data members. The variables shown here were added using the Add Member Variable Wizard:
Control ID | Member Variable | Category | Variable Type |
---|---|---|---|
IDC_GRADE | m_nGrade | Value | int |
IDC_NAME | m_strName | Value | CString |
You can use Class View's Properties window to map toolbar button commands to their handlers. Here are the commands and the handler functions to which they were mapped:
Object ID | Message | Member Function |
---|---|---|
ID_STUDENT_HOME | COMMAND | OnStudentHome |
ID_STUDENT_END | COMMAND | OnStudentEnd |
ID_STUDENT_PREVIOUS | COMMAND | OnStudentPrevious |
ID_STUDENT_NEXT | COMMAND | OnStudentNext |
ID_STUDENT_INSERT | COMMAND | OnStudentInsert |
ID_STUDENT_DELETE | COMMAND | OnStudentDelete |
Each command handler has built-in error checking.The following update command user interface message handlers are called during idle processing to update the state of the toolbar buttons and, when the Student menu is painted, to update the menu commands.
Object ID | Message | Member Function |
---|---|---|
ID_STUDENT_HOME | UPDATE_COMMAND_UI | OnUpdateStudentHome |
ID_STUDENT_END | UPDATE_COMMAND_UI | OnUpdateStudentEnd |
ID_STUDENT_PREVIOUS | UPDATE_COMMAND_UI | OnUpdateStudentPrevious |
ID_STUDENT_NEXT | UPDATE_COMMAND_UI | OnUpdateStudentNext |
ID_STUDENT_DELETE | UPDATE_COMMAND_UI | OnUpdateCommandDelete |
For example, the Following button, which retrieves the first student record, is disabled when the list is empty and when the m_position variable is already set to the head of the list.

Data Members
The m_position data member is a kind of cursor for the document's collection. It contains the position of the CStudent object that is currently displayed. The m_pList variable provides a quick way to get at the student list in the document.
OnInitialUpdate
The virtual OnInitialUpdate function is called when you start the application. It sets the view's m_pList data member for subsequent access to the document's list object.
OnUpdate
The virtual OnUpdate function is called both by the OnInitialUpdate function and by the CDocument::UpdateAllViews function. It resets the list position to the head of the list, and it displays the head entry. In this example, the UpdateAllViews function is called only in response to the Edit Clear All command. In a multi-view application, you might need a different strategy for setting the CEx15bView m_position variable in response to document updates from another view.
Protected Virtual Functions
The following three functions are protected virtual functions that deal specifically with CStudent objects: GetEntry, InsertEntry, and ClearEntry. You can transfer these functions to a derived class if you want to isolate the general-purpose list-handling features in a base class.
Testing the Ex15b Application
Fill in the student name and grade fields, and then click this button to insert the entry into the list:

a CEx15bDoc at $4116D0
m_strTitle = Untitled
m_strPathName =
m_bModified = 1
m_pDocTemplate = $4113F1
a CObList at $411624
with 4 elements
a CStudent at $412770
m_strName = Fisher, Lon
m_nGrade = 67
a CStudent at $412E80
m_strName = Meyers, Lisa
m_nGrade = 80
a CStudent at $412880
m_strName = Seghers, John
m_nGrade = 92
a CStudent at $4128F0
m_strName = Anderson, Bob
m_nGrade = 87