This second SDI example improves on EX16A in the following ways:
The EX16B window, shown in Figure 16-3, looks a little different from the EX16A window shown in Figure 16-1. The toolbar buttons are enabled only when appropriate. The Next (arrow-down graphic) button, for example, is disabled when we're positioned at the bottom of the list.
Figure 16-3. The EX16B 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 | |
Deletes the current student record | |
Inserts a new 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. Because there's now more code, we'll simply list selected code and the resource requirements. In the listing figures, boldface code indicates additional code or other changes that you enter in the output from AppWizard and ClassWizard. The frequent use of TRACE statements lets you follow the program's execution in the debugging window.
The file ex16b.rc defines the application's resources as follows.
The toolbar (visible in Figure 16-3) 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 ex16b.rc file defines the linkage between the command IDs and the toolbar buttons.
Having menu options that correspond to the new toolbar buttons isn't absolutely necessary. (ClassWizard allows you to map toolbar button commands just as easily as menu commands.) However, most applications for Microsoft Windows have menu options for all commands, so users generally expect them.
On the Edit menu, the clipboard menu items are replaced by the Clear All menu item. See step 2 for an illustration of the Edit menu.
The IDD_STUDENT dialog template, shown here, is similar to the EX16A dialog shown in Figure 16-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 EX16A program.
Here's a list of the files and classes in the EX16B example.
Header File | Source Code File | Classes | Description |
ex16b.h | ex16b.cpp | CEx16bApp | Application class (from AppWizard) |
CAboutDlg | About dialog | ||
MainFrm.h | MainFrm.cpp | CMainFrame | SDI main frame |
StuDoc.h | StuDoc.cpp | CStudentDoc | Student document |
StuView.h | StuView.cpp | CStudentView | Student form view (derived from CFormView) |
Student.h | Student.cpp | CStudent | Student record (similar to EX16A) |
StdAfx.h | StdAfx.cpp | Includes the standard precompiled headers |
The files ex16b.cpp and ex16b.h are standard AppWizard output.
The code for the CMainFrame class in MainFrm.cpp is standard AppWizard output.
This is the code from EX16A, except for the following line added at the end of Student.h:
typedef CTypedPtrList<CObList, CStudent*> CStudentList;
Use of the MFC template collection classes requires the following statement in StdAfx.h:
#include <afxtempl.h>
AppWizard originally generated the CStudentDoc class. Figure 16-4 shows the code used in the EX16B example.
STUDOC.H
// StuDoc.h : interface of the CStudentDoc class // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_STUDOC_H__4D011047_7E1C_11D0_8FE0_00C04FC2A0C2__INCLUDED_) #define AFX_STUDOC_H__4D011047_7E1C_11D0_8FE0_00C04FC2A0C2__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "student.h" class CStudentDoc : public CDocument { protected: // create from serialization only CStudentDoc(); DECLARE_DYNCREATE(CStudentDoc) // Attributes public: CStudentList* GetList() { return &m_studentList; } // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CStudentDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); virtual void DeleteContents(); //}}AFX_VIRTUAL // Implementation public: virtual ~CStudentDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX_MSG(CStudentDoc) afx_msg void OnEditClearAll(); afx_msg void OnUpdateEditClearAll(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: CStudentList m_studentList; }; ////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined(AFX_STUDOC_H__4D011047_7E1C_11D0_8FE0_00C04FC2A0C2__INCLUDED_) STUDOC.CPP
// StuDoc.cpp : implementation of the CStudentDoc class // #include "stdafx.h" #include "ex16b.h" #include "StuDoc.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ////////////////////////////////////////////////////////////////////// // CStudentDoc IMPLEMENT_DYNCREATE(CStudentDoc, CDocument) BEGIN_MESSAGE_MAP(CStudentDoc, CDocument) //{{AFX_MSG_MAP(CStudentDoc) ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll) ON_UPDATE_COMMAND_UI(ID_EDIT_CLEAR_ALL, OnUpdateEditClearAll) //}}AFX_MSG_MAP END_MESSAGE_MAP() ////////////////////////////////////////////////////////////////////// // CStudentDoc construction/destruction CStudentDoc::CStudentDoc() { TRACE("Entering CStudentDoc constructor\n"); #ifdef _DEBUG afxDump.SetDepth(1); // Ensure dump of list elements #endif // _DEBUG } CStudentDoc::~CStudentDoc() { } BOOL CStudentDoc::OnNewDocument() { TRACE("Entering CStudentDoc::OnNewDocument\n"); if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here // (SDI documents will reuse this document) return TRUE; } ////////////////////////////////////////////////////////////////////// // CStudentDoc serialization void CStudentDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } } ////////////////////////////////////////////////////////////////////// // CStudentDoc diagnostics #ifdef _DEBUG void CStudentDoc::AssertValid() const { CDocument::AssertValid(); } void CStudentDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); dc << "\n" << m_studentList << "\n"; } #endif //_DEBUG ////////////////////////////////////////////////////////////////////// // CStudentDoc commands void CStudentDoc::DeleteContents() { #ifdef _DEBUG Dump(afxDump); #endif while (m_studentList.GetHeadPosition()) { delete m_studentList.RemoveHead(); } } void CStudentDoc::OnEditClearAll() { DeleteContents(); UpdateAllViews(NULL); } void CStudentDoc::OnUpdateEditClearAll(CCmdUI* pCmdUI) { pCmdUI->Enable(!m_studentList.IsEmpty()); } |
The Edit Clear All command is handled in the document class. The following message handlers were added through ClassWizard.
Object ID | Message | Member Function |
ID_EDIT_CLEAR_ALL | COMMAND | OnEditClearAll |
ID_EDIT_CLEAR_ALL | ON_UPDATE_COMMAND_UI | OnUpdateEditClearAll |
The document class provides for an embedded CStudentList object, the m_studentList data member, which holds pointers to CStudent objects. The list object is constructed when the CStudentDoc object is constructed, and it is destroyed at program exit. CStudentList is a typedef for a CTypedPtrList for CStudent pointers.
The document constructor sets the depth of the dump context so that a dump of the list causes dumps of the individual list elements.
The inline GetList function helps isolate the view from the document. The document class must be specific to the type of object in the listin 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.
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 are closed. DeleteContents also dumps the student list.
AppWizard 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.
Figure 16-5 shows the code for the CStudentView class. This code will be carried over into the next two chapters.
STUVIEW.H
// StuView.h : interface of the CStudentView class // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_STUVIEW_H__4D011049_7E1C_11D0_8FE0_00C04FC2A0C2__INCLUDED_) #define AFX_STUVIEW_H__4D011049_7E1C_11D0_8FE0_00C04FC2A0C2__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CStudentView : public CFormView { protected: POSITION m_position; // current position in document list CStudentList* m_pList; // copied from document protected: // create from serialization only CStudentView(); DECLARE_DYNCREATE(CStudentView) public: //{{AFX_DATA(CStudentView) enum { IDD = IDD_STUDENT }; int m_nGrade; CString m_strName; //}}AFX_DATA // Attributes public: CStudentDoc* GetDocument(); // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CStudentView) public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support virtual void OnInitialUpdate(); // called first time after construct virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint); //}}AFX_VIRTUAL // Implementation public: virtual ~CStudentView(); #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: //{{AFX_MSG(CStudentView) afx_msg void OnClear(); afx_msg void OnStudentHome(); afx_msg void OnStudentEnd(); afx_msg void OnStudentPrev(); afx_msg void OnStudentNext(); afx_msg void OnStudentIns(); afx_msg void OnStudentDel(); afx_msg void OnUpdateStudentHome(CCmdUI* pCmdUI); afx_msg void OnUpdateStudentEnd(CCmdUI* pCmdUI); afx_msg void OnUpdateStudentDel(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; #ifndef _DEBUG // debug version in StuView.cpp inline CStudentDoc* CStudentView::GetDocument() { return (CStudentDoc*)m_pDocument; } #endif ////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined(AFX_STUVIEW_H__4D011049_7E1C_11D0_8FE0_00C04FC2A0C2__INCLUDED_) STUVIEW.CPP
// StuView.cpp : implementation of the CStudentView class // #include "stdafx.h" #include "ex16b.h" #include "StuDoc.h" #include "StuView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__ ; #endif ////////////////////////////////////////////////////////////////////// // CStudentView IMPLEMENT_DYNCREATE(CStudentView, CFormView) BEGIN_MESSAGE_MAP(CStudentView, CFormView) //{{AFX_MSG_MAP(CStudentView) ON_BN_CLICKED(IDC_CLEAR, OnClear) ON_COMMAND(ID_STUDENT_HOME, OnStudentHome) ON_COMMAND(ID_STUDENT_END, OnStudentEnd) ON_COMMAND(ID_STUDENT_PREV, OnStudentPrev) ON_COMMAND(ID_STUDENT_NEXT, OnStudentNext) ON_COMMAND(ID_STUDENT_INS, OnStudentIns) ON_COMMAND(ID_STUDENT_DEL, OnStudentDel) ON_UPDATE_COMMAND_UI(ID_STUDENT_HOME, OnUpdateStudentHome) ON_UPDATE_COMMAND_UI(ID_STUDENT_END, OnUpdateStudentEnd) ON_UPDATE_COMMAND_UI(ID_STUDENT_PREV, OnUpdateStudentHome) ON_UPDATE_COMMAND_UI(ID_STUDENT_NEXT, OnUpdateStudentEnd) ON_UPDATE_COMMAND_UI(ID_STUDENT_DEL, OnUpdateStudentDel) //}}AFX_MSG_MAP END_MESSAGE_MAP() ////////////////////////////////////////////////////////////////////// // CStudentView construction/destruction CStudentView::CStudentView() : CFormView(CStudentView::IDD) { TRACE("Entering CStudentView constructor\n"); //{{AFX_DATA_INIT(CStudentView) m_nGrade = 0; m_strName = _T(""); //}}AFX_DATA_INIT m_position = NULL; } CStudentView::~CStudentView() { } void CStudentView::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); //{{AFX_DATA_MAP(CStudentView) DDX_Text(pDX, IDC_GRADE, m_nGrade); DDV_MinMaxInt(pDX, m_nGrade, 0, 100); DDX_Text(pDX, IDC_NAME, m_strName); DDV_MaxChars(pDX, m_strName, 20); //}}AFX_DATA_MAP } BOOL CStudentView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return CFormView::PreCreateWindow(cs); } void CStudentView::OnInitialUpdate() { TRACE("Entering CStudentView::OnInitialUpdate\n"); m_pList = GetDocument()->GetList(); CFormView::OnInitialUpdate(); } ////////////////////////////////////////////////////////////////////// // CStudentView diagnostics #ifdef _DEBUG void CStudentView::AssertValid() const { CFormView::AssertValid(); } void CStudentView::Dump(CDumpContext& dc) const { CFormView::Dump(dc); } CStudentDoc* CStudentView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CStudentDoc))); return (CStudentDoc*)m_pDocument; } #endif //_DEBUG ////////////////////////////////////////////////////////////////////// // CStudentView message handlers void CStudentView::OnClear() { TRACE("Entering CStudentView::OnClear\n"); ClearEntry(); } void CStudentView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { // called by OnInitialUpdate and by UpdateAllViews TRACE("Entering CStudentView::OnUpdate\n"); m_position = m_pList->GetHeadPosition(); GetEntry(m_position); // initial data for view } void CStudentView::OnStudentHome() { TRACE("Entering CStudentView::OnStudentHome\n"); // need to deal with list empty condition if (!m_pList->IsEmpty()) { m_position = m_pList->GetHeadPosition(); GetEntry(m_position); } } void CStudentView::OnStudentEnd() { TRACE("Entering CStudentView::OnStudentEnd\n"); if (!m_pList->IsEmpty()) { m_position = m_pList->GetTailPosition(); GetEntry(m_position); } } void CStudentView::OnStudentPrev() { POSITION pos; TRACE("Entering CStudentView::OnStudentPrev\n"); if ((pos = m_position) != NULL) { m_pList->GetPrev(pos); if (pos) { GetEntry(pos); m_position = pos; } } } void CStudentView::OnStudentNext() { POSITION pos; TRACE("Entering CStudentView::OnStudentNext\n"); if ((pos = m_position) != NULL) { m_pList->GetNext(pos); if (pos) { GetEntry(pos); m_position = pos; } } } void CStudentView::OnStudentIns() { TRACE("Entering CStudentView::OnStudentIns\n"); InsertEntry(m_position); GetDocument()->SetModifiedFlag(); GetDocument()->UpdateAllViews(this); } void CStudentView::OnStudentDel() { // deletes current entry and positions to next one or head POSITION pos; TRACE("Entering CStudentView::OnStudentDel\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 CStudentView::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 CStudentView::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 CStudentView::OnUpdateStudentDel(CCmdUI* pCmdUI) { // called during idle processing and when Student menu drops down pCmdUI->Enable(m_position != NULL); } void CStudentView::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 CStudentView::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 CStudentView::ClearEntry() { m_strName = ""; m_nGrade = 0; UpdateData(FALSE); ((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME)); } |
Figure 16-5. The CStudentView class listing.
ClassWizard was used to map the CStudentView Clear pushbutton notification message as follows.
Object ID | Message | Member Function |
IDC_CLEAR | BN_CLICKED | OnClear |
Because CStudentView is derived from CFormView, ClassWizard supports the definition of dialog data members. The variables shown here were added with the Add Variables button.
Control ID | Member Variable | Category | Variable Type |
IDC_GRADE | m_nGrade | Value | int |
IDC_NAME | m_strName | Value | CString |
The minimum value of the m_nGrade data member was set to 0, and its maximum value was set to 100. The maximum length of the m_strName data member was set to 20 characters.
ClassWizard maps 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_PREV | COMMAND | OnStudentPrev |
ID_STUDENT_NEXT | COMMAND | OnStudentNext |
ID_STUDENT_INS | COMMAND | OnStudentIns |
ID_STUDENT_DEL | COMMAND | OnStudentDel |
Each command handler has built-in error checking.
The following update command UI 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 items.
Object ID | Message | Member Function |
ID_STUDENT_HOME | UPDATE_COMMAND_UI | OnUpdateStudentHome |
ID_STUDENT_END | UPDATE_COMMAND_UI | OnUpdateStudentEnd |
ID_STUDENT_PREV | UPDATE_COMMAND_UI | OnUpdateStudentHome |
ID_STUDENT_NEXT | UPDATE_COMMAND_UI | OnUpdateStudentEnd |
ID_STUDENT_DEL | UPDATE_COMMAND_UI | OnUpdateCommandDel |
For example, this 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. The Previous button is disabled under the same circumstances, so it uses the same update command UI handler. The End and the Next buttons share a handler for similar reasons. Because a delay sometimes occurs in calling the update command UI functions, the command message handlers must look for error conditions.
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.
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.
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 multiview application, you might need a different strategy for setting the CStudentView m_position variable in response to document updates from another view.
The following three functions are protected virtual functions that deal specifically with CStudent objects:
GetEntryInsertEntry
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.
Fill in the student name and grade fields, and then click this button
to insert the entry into the list. Repeat this action several times, using the Clear pushbutton to erase the data from the previous entry. When you exit the application, the debug output should look similar to this:
a CStudentDoc 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