Some dialog boxes appear so frequently in application programs that they have rightfully taken their places as part of the operating system. Before Windows 3.1, programmers had to write their own Open and Save As dialog boxes to get a file name from the user before opening or saving a file. Because both the design and the implementation of these dialog boxes were left up to the programmer, every Open and Save As dialog box was different, and some were far inferior to others. Windows 3.1 fixed this long-standing problem by providing standard implementations of these and other commonly used dialog boxes in a DLL known as the common dialog library. Windows 95 enhanced the library with improved versions of the Windows 3.1 common dialogs and a new Page Setup dialog box for entering page layouts. Windows 98 and Windows 2000 further refine the common dialogs to make them more functional than ever.
MFC provides C++ interfaces to the common dialogs with the classes shown in the following table.
The Common Dialog Classes
Class | Dialog Type(s) |
---|---|
CFileDialog | Open and Save As dialog boxes |
CPrintDialog | Print and Print Setup dialog boxes |
CPageSetupDialog | Page Setup dialog boxes |
CFindReplaceDialog | Find and Replace dialog boxes |
CColorDialog | Color dialog boxes |
CFontDialog | Font dialog boxes |
In an SDK-style application, a common dialog is invoked by filling in the fields of a data structure and calling an API function such as ::GetOpenFileName. When the function returns, certain fields of the data structure contain values input by the user. MFC simplifies the interface by providing default input values for most fields and member functions for retrieving data entered into the dialog box. In an MFC application, getting a file name from the user before opening a file is normally no more complicated than this:
TCHAR szFilters[] = _T ("Text files (*.txt)¦*.txt¦All files (*.*)¦*.*¦¦"); CFileDialog dlg (TRUE, _T ("txt"), _T ("*.txt"), OFN_FILEMUSTEXIST ¦ OFN_HIDEREADONLY, szFilters); if (dlg.DoModal () == IDOK) { filename = dlg.GetPathName (); // Open the file and read it. } |
The TRUE parameter passed to CFileDialog's constructor tells MFC to display an Open dialog box rather than a Save As dialog box. The "txt" and "*.txt" parameters specify the default file name extension—the extension that is appended to the file name if the user doesn't enter an extension—and the text that initially appears in the dialog's File Name box. The OFN values are bit flags that specify the dialog's properties. OFN_FILEMUSTEXIST tells the dialog to test the file name the user enters and reject it if the file doesn't exist, and OFN_HIDEREADONLY hides the read-only check box that appears in the dialog box by default. szFilters points to a string specifying the file types the user can select from. When DoModal returns, the file name that the user entered, complete with path name, can be retrieved with CFileDialog::GetPathName. Other useful CFileDialog functions include GetFileName, which retrieves a file name without the path, and GetFileTitle, which retrieves a file name with neither path nor extension.
Generally, you'll find that MFC's common dialog classes are exceptionally easy to use, in part because you can often instantiate a common dialog class directly and avoid deriving classes of your own.
You can modify the behavior of CFileDialog and other common dialog classes in a number of ways. One method involves nothing more than changing the parameters passed to the dialog's constructor. For example, CFileDialog::CFileDialog's fourth parameter accepts about two dozen different bit flags affecting the dialog's appearance and behavior. One use for these flags is to create an Open dialog box that features a multiple-selection list box in which the user can select several files instead of just one. Rather than construct the dialog like this,
CFileDialog dlg (TRUE, _T ("txt"), _T ("*.txt"), OFN_FILEMUSTEXIST ¦ OFN_HIDEREADONLY, szFilters); |
you would do it like this:
CFileDialog dlg (TRUE, _T ("txt"), _T ("*.txt"), OFN_FILEMUSTEXIST ¦ OFN_HIDEREADONLY ¦ OFN_ALLOWMULTISELECT, szFilters); |
After DoModal returns, a list of file names is stored in the buffer referenced by the dialog object's m_ofn.lpstrFile data member. The file names are easily retrieved from the buffer with CFileDialog's GetStartPosition and GetNextPathName functions.
When you construct a dialog box from CFileDialog, the class constructor fills in the fields of an OPENFILENAME structure with values defining the title for the dialog window, the initial directory, and other parameters. The structure's address is subsequently passed to ::GetOpenFileName or ::GetSaveFileName. Some of the values used to initialize the structure are taken from CFileDialog's constructor parameter list, but other parameters are filled with default values appropriate for the majority of applications. Another way to customize an Open or a Save As dialog box is to modify the fields of the OPENFILENAME structure after constructing the dialog object but before calling DoModal. The OPENFILENAME structure is accessible through the public data member m_ofn.
Suppose you'd like to change the title of a multiple-selection file dialog to "Select File(s)" instead of "Open." In addition, you'd like the file name filter that was selected when the dialog box was closed to be selected again the next time the dialog box is displayed. Here's how you could make these changes:
CFileDialog dlg (TRUE, _T ("txt"), NULL, OFN_FILEMUSTEXIST ¦ OFN_ALLOWMULTISELECT, szFilters); dlg.m_ofn.nFilterIndex = m_nFilterIndex; static char szTitle[] = _T ("Select File(s)"); dlg.m_ofn.lpstrTitle = szTitle; if (dlg.DoModal () == IDOK) { m_nFilterIndex = dlg.m_ofn.nFilterIndex; } |
When the program is started, m_nFilterIndex should be set to 1. The first time the dialog box is created, the first file filter will be selected by default. When the user dismisses the dialog box with the OK button, the index of the currently selected filter is copied out of the OPENFILENAME structure and saved in m_nFilterIndex. The next time the dialog box is invoked, the same filter will be selected automatically. In other words, the dialog box will remember the user's filter selection. For a more thorough encapsulation, you could make m_nFilterIndex a part of the dialog box rather than a member of an external class by deriving your own dialog class from CFileDialog, declaring m_nFilterIndex to be a static member variable of that class, and initializing it to 1 before constructing a CMyFileDialog object for the first time.
You can implement more extensive changes by deriving your own dialog class from CFileDialog and overriding key virtual functions. In addition to OnOK and OnCancel, you can override the virtual functions OnFileNameOK, OnLBSelChangedNotify, and OnShareViolation to customize the way the dialog box validates file names, responds to changes in file name selections, and handles sharing violations. You can override OnInitDialog to perform all sorts of stunts, such as increasing the size of the dialog box and adding or deleting controls. (If you override CFileDialog::OnInitDialog, be sure to call the base class version from your own implementation.) You could, for example, stretch the dialog box horizontally and create a preview area that displays a thumbnail sketch of the contents of the currently selected file. By overriding OnLBSelChangedNotify, you could update the preview window when the selection changes.
This chapter's final application, Phones, brings together into one project many of the concepts discussed in this chapter and in Chapter 7. As you can see in Figure 8-13, Phones is a simple phone list program that stores names and phone numbers. Names and phone numbers are entered and edited in a modal dialog box that features a standard edit control for names, a numeric edit control for phone numbers, and icon push buttons. Data entered into the application can be saved to disk and read back using the File menu's Open, Save, and Save As commands. Phones uses CFileDialog to solicit file names from the user and CStdioFile to perform its file I/O. It also uses the derived list box class CPhonesListBox as the base class for CChildView, and it uses message reflection in that class to allow the list box to respond to its own double-click notifications. I hand-edited the AppWizard-generated CChildView class to change the base class from CWnd to CPhonesListBox. Pertinent portions of the application's source code are shown in Figure 8-14.
Figure 8-13. The Phones window and dialog box.
Figure 8-14. The Phones application.
MainFrm.h// MainFrm.h : interface of the CMainFrame class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINFRM_H__7BE4B248_90ED_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__7BE4B248_90ED_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "ChildView.h" class CMainFrame : public CFrameWnd { public: CMainFrame(); protected: DECLARE_DYNAMIC(CMainFrame) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMainFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo); //}}AFX_VIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif CChildView m_wndView; // Generated message map functions protected: //{{AFX_MSG(CMainFrame) afx_msg void OnSetFocus(CWnd *pOldWnd); afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_MAINFRM_H__7BE4B248_90ED_11D2_8E53_006008A82731__INCLUDED_) |
MainFrm.cpp// MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "Phones.h" #include "PhonesListBox.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_SETFOCUS() ON_WM_CREATE() //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { } CMainFrame::~CMainFrame() { } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; cs.dwExStyle &= ~WS_EX_CLIENTEDGE; cs.lpszClass = AfxRegisterWndClass(0); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef _DEBUG void CMainFrame::AssertValid() const { CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CFrameWnd::Dump(dc); } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers void CMainFrame::OnSetFocus(CWnd* pOldWnd) { // forward focus to the view window m_wndView.SetFocus(); } BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // let the view have first crack at the command if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise, do default handling return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndView.Create(WS_CHILD ¦ WS_VISIBLE ¦ LBS_USETABSTOPS ¦ LBS_SORT ¦ LBS_NOTIFY ¦ LBS_NOINTEGRALHEIGHT, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST)) return -1; return 0; } |
ChildView.h// ChildView.h : interface of the CChildView class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_CHILDVIEW_H__7BE4B24A_90ED_11D2_8E53_006008A82731__INCLUDED_) #define AFX_CHILDVIEW_H__7BE4B24A_90ED_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /////////////////////////////////////////////////////////////////////////// // CChildView window class CChildView : public CPhonesListBox { // Construction public: CChildView(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CChildView) protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFX_VIRTUAL // Implementation public: virtual ~CChildView(); // Generated message map functions protected: BOOL SaveFile (LPCTSTR pszFile); BOOL LoadFile (LPCTSTR pszFile); static const TCHAR m_szFilters[]; CString m_strPathName; //{{AFX_MSG(CChildView) afx_msg void OnNewEntry(); afx_msg void OnFileOpen(); afx_msg void OnFileSave(); afx_msg void OnFileSaveAs(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined( // AFX_CHILDVIEW_H__7BE4B24A_90ED_11D2_8E53_006008A82731__INCLUDED_) |
ChildView.cpp// ChildView.cpp : implementation of the CChildView class // #include "stdafx.h" #include "Phones.h" #include "PhonesListBox.h" #include "ChildView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CChildView const TCHAR CChildView::m_szFilters[] = _T ("Phone Files (*.phn)¦*.phn¦All Files (*.*)¦*.*¦¦"); CChildView::CChildView() { } CChildView::~CChildView() { } BEGIN_MESSAGE_MAP(CChildView, CPhonesListBox) //{{AFX_MSG_MAP(CChildView) ON_COMMAND(ID_FILE_NEW, OnNewEntry) ON_COMMAND(ID_FILE_OPEN, OnFileOpen) ON_COMMAND(ID_FILE_SAVE, OnFileSave) ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs) //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CChildView message handlers BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) { if (!CPhonesListBox::PreCreateWindow(cs)) return FALSE; cs.dwExStyle ¦= WS_EX_CLIENTEDGE; cs.style &= ~WS_BORDER; return TRUE; } void CChildView::OnNewEntry() { NewEntry (); } void CChildView::OnFileOpen() { CFileDialog dlg (TRUE, _T ("phn"), _T ("*.phn"), OFN_FILEMUSTEXIST ¦ OFN_HIDEREADONLY, m_szFilters); if (dlg.DoModal () == IDOK) { if (LoadFile (dlg.GetPathName ())) { m_strPathName = dlg.GetPathName (); SetCurSel (0); } } } void CChildView::OnFileSave() { if (!m_strPathName.IsEmpty ()) SaveFile (m_strPathName); else // Need a file name first. OnFileSaveAs (); } void CChildView::OnFileSaveAs() { CFileDialog dlg (FALSE, _T ("phn"), m_strPathName, OFN_OVERWRITEPROMPT ¦ OFN_PATHMUSTEXIST ¦ OFN_HIDEREADONLY, m_szFilters); if (dlg.DoModal () == IDOK) if (SaveFile (dlg.GetPathName ())) m_strPathName = dlg.GetPathName (); } BOOL CChildView::LoadFile(LPCTSTR pszFile) { BOOL bResult = FALSE; try { CStdioFile file (pszFile, CFile::modeRead); ResetContent (); DWORD dwCount; file.Read (&dwCount, sizeof (dwCount)); if (dwCount) { for (int i=0; i<(int) dwCount; i++) { CString string; file.ReadString (string); AddString (string); } } bResult = TRUE; } catch (CFileException* e) { e->ReportError (); e->Delete (); } return bResult; } BOOL CChildView::SaveFile(LPCTSTR pszFile) { BOOL bResult = FALSE; try { CStdioFile file (pszFile, CFile::modeWrite ¦ CFile::modeCreate); DWORD dwCount = GetCount (); file.Write (&dwCount, sizeof (dwCount)); if (dwCount) { for (int i=0; i<(int) dwCount; i++) { CString string; GetText (i, string); string += _T ("\n"); file.WriteString (string); } } bResult = TRUE; } catch (CFileException* e) { e->ReportError (); e->Delete (); } return bResult; } |
PhonesListBox.h#if !defined(AFX_PHONESLISTBOX_H__7BE4B250_90ED_11D2_8E53_006008A82731__INCLUDED_) #define AFX_PHONESLISTBOX_H__7BE4B250_90ED_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // PhonesListBox.h : header file // /////////////////////////////////////////////////////////////////////////// // CPhonesListBox window class CPhonesListBox : public CListBox { // Construction public: CPhonesListBox(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CPhonesListBox) //}}AFX_VIRTUAL // Implementation public: void NewEntry(); virtual ~CPhonesListBox(); // Generated message map functions protected: CFont m_font; //{{AFX_MSG(CPhonesListBox) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnEditItem(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined( // AFX_PHONESLISTBOX_H__7BE4B250_90ED_11D2_8E53_006008A82731__INCLUDED_) |
PhonesListBox.cpp// PhonesListBox.cpp : implementation file // #include "stdafx.h" #include "Phones.h" #include "PhonesListBox.h" #include "PhoneEdit.h" #include "EditDialog.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CPhonesListBox CPhonesListBox::CPhonesListBox() { } CPhonesListBox::~CPhonesListBox() { } BEGIN_MESSAGE_MAP(CPhonesListBox, CListBox) //{{AFX_MSG_MAP(CPhonesListBox) ON_WM_CREATE() ON_CONTROL_REFLECT(LBN_DBLCLK, OnEditItem) //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CPhonesListBox message handlers int CPhonesListBox::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CListBox::OnCreate(lpCreateStruct) == -1) return -1; m_font.CreatePointFont (80, _T ("MS Sans Serif")); SetFont (&m_font, FALSE); SetTabStops (128); return 0; } void CPhonesListBox::OnEditItem() { CEditDialog dlg; CString strItem; int nIndex = GetCurSel (); GetText (nIndex, strItem); int nPos = strItem.Find (_T (`\t')); dlg.m_strName = strItem.Left (nPos); dlg.m_strPhone = strItem.Right (strItem.GetLength () - nPos - 1); if (dlg.DoModal () == IDOK) { strItem = dlg.m_strName + _T ("\t") + dlg.m_strPhone; DeleteString (nIndex); AddString (strItem); } SetFocus (); } void CPhonesListBox::NewEntry() { CEditDialog dlg; if (dlg.DoModal () == IDOK) { CString strItem = dlg.m_strName + _T ("\t") + dlg.m_strPhone; AddString (strItem); } SetFocus (); } |
EditDialog.h#if !defined(AFX_EDITDIALOG_H__7BE4B252_90ED_11D2_8E53_006008A82731__INCLUDED_) #define AFX_EDITDIALOG_H__7BE4B252_90ED_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // EditDialog.h : header file // /////////////////////////////////////////////////////////////////////////// // CEditDialog dialog class CEditDialog : public CDialog { // Construction public: CEditDialog(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CEditDialog) enum { IDD = IDD_EDITDLG }; CButton m_wndOK; CButton m_wndCancel; CPhoneEdit m_wndPhoneEdit; CString m_strName; CString m_strPhone; //}}AFX_DATA // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CEditDialog) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(CEditDialog) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined( // AFX_EDITDIALOG_H__7BE4B252_90ED_11D2_8E53_006008A82731__INCLUDED_) |
EditDialog.cpp// EditDialog.cpp : implementation file // #include "stdafx.h" #include "Phones.h" #include "PhoneEdit.h" #include "EditDialog.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CEditDialog dialog CEditDialog::CEditDialog(CWnd* pParent /*=NULL*/) : CDialog(CEditDialog::IDD, pParent) { //{{AFX_DATA_INIT(CEditDialog) m_strName = _T(""); m_strPhone = _T(""); //}}AFX_DATA_INIT } void CEditDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CEditDialog) DDX_Control(pDX, IDOK, m_wndOK); DDX_Control(pDX, IDCANCEL, m_wndCancel); DDX_Control(pDX, IDC_PHONE, m_wndPhoneEdit); DDX_Text(pDX, IDC_NAME, m_strName); DDV_MaxChars(pDX, m_strName, 32); DDX_Text(pDX, IDC_PHONE, m_strPhone); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CEditDialog, CDialog) //{{AFX_MSG_MAP(CEditDialog) //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CEditDialog message handlers BOOL CEditDialog::OnInitDialog() { CDialog::OnInitDialog(); m_wndOK.SetIcon (AfxGetApp ()->LoadIcon (IDI_OK)); m_wndCancel.SetIcon (AfxGetApp ()->LoadIcon (IDI_CANCEL)); return TRUE; } |
PhoneEdit.h#if !defined(AFX_PHONEEDIT_H__7BE4B251_90ED_11D2_8E53_006008A82731__INCLUDED_) #define AFX_PHONEEDIT_H__7BE4B251_90ED_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // PhoneEdit.h : header file // /////////////////////////////////////////////////////////////////////////// // CPhoneEdit window class CPhoneEdit : public CEdit { // Construction public: CPhoneEdit(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CPhoneEdit) //}}AFX_VIRTUAL // Implementation public: virtual ~CPhoneEdit(); // Generated message map functions protected: //{{AFX_MSG(CPhoneEdit) afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif //!defined(AFX_PHONEEDIT_H__7BE4B251_90ED_11D2_8E53_006008A82731__INCLUDED_) |
PhoneEdit.cpp// PhoneEdit.cpp : implementation file // #include "stdafx.h" #include "Phones.h" #include "PhoneEdit.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CPhoneEdit CPhoneEdit::CPhoneEdit() { } CPhoneEdit::~CPhoneEdit() { } BEGIN_MESSAGE_MAP(CPhoneEdit, CEdit) //{{AFX_MSG_MAP(CPhoneEdit) ON_WM_CHAR() //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CPhoneEdit message handlers void CPhoneEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (((nChar >= _T (`0')) && (nChar <= _T (`9'))) ¦¦ (nChar == VK_BACK) ¦¦ (nChar == _T (`(`)) ¦¦ (nChar == _T (`)')) ¦¦ (nChar == _T (`-')) ¦¦ (nChar == _T (` `))) CEdit::OnChar(nChar, nRepCnt, nFlags); } |
One of the most subtle yet important elements of Phones' source code is the innocent-looking statement
DDX_Control (pDX, IDC_PHONE, m_wndPhoneEdit); |
in EditDialog.cpp. m_wndPhoneEdit is an instance of the CEdit-derived class CPhoneEdit, which represents an edit control that filters out nonnumeric characters. But m_wndPhoneEdit is linked to IDC_PHONE, which is an ordinary edit control created from the dialog template. The only reason IDC_PHONE acts like a CPhoneEdit instead of a CEdit is that DDX_Control subclasses the control and routes messages destined for the control through m-wndPhoneEdit's message map. The moral is both simple and profound. Whenever you want a control in a dialog box to behave as if it were an instance of a derived control class, map the control to a class instance with DDX_Control. Otherwise, any special behavior built into the derived class will go unused.
Phones does a reasonable job of demonstrating how MFC's CFileDialog class is used and how documents can be written to disk and read back. What it doesn't do very well is safeguard the user's data. If a list of names and phone numbers contains unsaved changes and another list is loaded or the application is shut down, Phones doesn't prompt you to save your changes. That's not how a real application should behave. Phones has other shortcomings, too, such as the fact that it doesn't register a file name extension with the operating system so that a saved file can be opened with a double-click. But don't despair: document handling is infinitely cleaner when performed in the context of the document/view architecture, and in the next chapter, we'll finally begin writing document/view applications. Once we do, MFC will handle many of the mundane chores expected of Windows applications, such as registering file name extensions and giving users the opportunity to save changes. If you've never written a document/view application before, you'll be pleasantly surprised at the level of support the framework provides.