Modeless Dialog Boxes

[Previous] [Next]

Once you've mastered modal dialog boxes, you'll discover that modeless dialog boxes are just a variation on what you've already learned. Modal and modeless dialog boxes are more alike than they are different. The key differences between them include the following:

  • Whereas a modal dialog box is displayed by calling CDialog::DoModal, modeless dialog boxes are displayed with CDialog::Create. Unlike DoModal, which doesn't return until the dialog box is dismissed, Create returns as soon as the dialog box is created. Therefore, the dialog box is still displayed when Create returns.
  • Modeless dialog boxes are dismissed by calling DestroyWindow, not End-Dialog. You mustn't allow CDialog::OnOK or CDialog::OnCancel to be called on a modeless dialog box, because both call EndDialog.
  • Modal dialog classes are usually instantiated on the stack so that de-struction is automatic. Modeless dialog classes are instantiated with new so that the dialog object won't be destroyed prematurely. One way to ensure that the modeless dialog object is deleted when the dialog box is destroyed is to override CDialog::PostNcDestroy in the derived dialog class and execute a delete this statement.

There are other differences between modal and modeless dialog boxes that MFC handles for you. For example, the message loop of an SDK application that uses a modeless dialog box must be modified to call ::IsDialogMessage to forward messages to the dialog box. An MFC application requires no such modification because ::IsDialogMessage is called automatically.

In general, MFC makes dialog handling generic so that using modeless dialog boxes is little different than using modal dialog boxes. Let's prove it by converting DlgDemo1's dialog box into a modeless dialog box.

The DlgDemo2 Application

Figure 8-8's DlgDemo2 application is functionally identical to DlgDemo1 in all respects but one: the Options dialog box is modeless rather than modal. Following convention, the OK and Cancel buttons are now labeled Apply and Close. The Apply button applies the settings entered in the dialog box to the rectangle but doesn't dismiss the dialog box. The Close button removes the dialog box from the screen and discards any changes, just like the Cancel button in DlgDemo1. Despite the name changes, the button IDs are still IDOK and IDCANCEL. This means that we can still use OnOK and OnCancel to process button clicks and that Enter and Esc still serve as the buttons' keyboard equivalents.

Figure 8-8. The DlgDemo2 application.

MainFrm.h

// MainFrm.h : interface of the CMainFrame class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINFRM_H__7040DB88_9039_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__7040DB88_9039_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     afx_msg LRESULT OnApply (WPARAM wParam, LPARAM lParam);     afx_msg LRESULT OnDialogDestroyed (WPARAM wParam, LPARAM lParam);     DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif  // !defined(AFX_MAINFRM_H__7040DB88_9039_11D2_8E53_006008A82731__INCLUDED_) 

MainFrm.cpp

// MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "DlgDemo2.h" #include "OptionsDialog.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     ON_MESSAGE (WM_USER_APPLY, OnApply)     ON_MESSAGE (WM_USER_DIALOG_DESTROYED, OnDialogDestroyed) 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(NULL, NULL, AFX_WS_DEFAULT_VIEW,         CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL))         return -1;     return 0; } LRESULT CMainFrame::OnApply (WPARAM wParam, LPARAM lParam) {     m_wndView.SendMessage (WM_USER_APPLY, wParam, lParam);     return 0; } LRESULT CMainFrame::OnDialogDestroyed (WPARAM wParam, LPARAM lParam) {     m_wndView.SendMessage (WM_USER_DIALOG_DESTROYED, wParam, lParam);     return 0; } 

ChildView.h

// ChildView.h : interface of the CChildView class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_CHILDVIEW_H__7040DB8A_9039_11D2_8E53_006008A82731__INCLUDED_) #define AFX_CHILDVIEW_H__7040DB8A_9039_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /////////////////////////////////////////////////////////////////////////// // CChildView window class CChildView : public CWnd { // 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:     COptionsDialog* m_pDlg;     int m_nUnits;     int m_nHeight;     int m_nWidth;     //{{AFX_MSG(CChildView)     afx_msg void OnPaint();     afx_msg void OnFileOptions();     //}}AFX_MSG     afx_msg LRESULT OnApply (WPARAM wParam, LPARAM lParam);     afx_msg LRESULT OnDialogDestroyed (WPARAM wParam, LPARAM lParam);     DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif  // !defined( //    AFX_CHILDVIEW_H__7040DB8A_9039_11D2_8E53_006008A82731__INCLUDED_) 

ChildView.cpp

// ChildView.cpp : implementation of the CChildView class // #include "stdafx.h" #include "DlgDemo2.h" #include "OptionsDialog.h" #include "ChildView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CChildView CChildView::CChildView() {     m_nWidth = 4;     m_nHeight = 2;     m_nUnits = 0;     m_pDlg = NULL; } CChildView::~CChildView() { } BEGIN_MESSAGE_MAP(CChildView,CWnd )     //{{AFX_MSG_MAP(CChildView)     ON_WM_PAINT()     ON_COMMAND(ID_FILE_OPTIONS, OnFileOptions)     //}}AFX_MSG_MAP     ON_MESSAGE (WM_USER_APPLY, OnApply)     ON_MESSAGE (WM_USER_DIALOG_DESTROYED, OnDialogDestroyed) END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CChildView message handlers BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)  {     if (!CWnd::PreCreateWindow(cs))         return FALSE;     cs.dwExStyle ¦= WS_EX_CLIENTEDGE;     cs.style &= ~WS_BORDER;     cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW¦CS_VREDRAW¦CS_DBLCLKS,          ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW+1), NULL);     return TRUE; } void CChildView::OnPaint()  {     CPaintDC dc(this); // Device context for painting.          CBrush brush (RGB (255, 0, 255));     CBrush* pOldBrush = dc.SelectObject (&brush);     switch (m_nUnits) {     case 0: // Inches.         dc.SetMapMode (MM_LOENGLISH);         dc.Rectangle (0, 0, m_nWidth * 100, -m_nHeight * 100);         break;     case 1: // Centimeters.         dc.SetMapMode (MM_LOMETRIC);         dc.Rectangle (0, 0, m_nWidth * 100, -m_nHeight * 100);         break;     case 2: // Pixels.         dc.SetMapMode (MM_TEXT);         dc.Rectangle (0, 0, m_nWidth, m_nHeight);         break;     }     dc.SelectObject (pOldBrush); } void CChildView::OnFileOptions()  {     //     // If the dialog box already exists, display it.     //     if (m_pDlg != NULL)         m_pDlg->SetFocus ();     //     // If the dialog box doesn't already exist, create it.     //     else {         m_pDlg = new COptionsDialog;         m_pDlg->m_nWidth = m_nWidth;         m_pDlg->m_nHeight = m_nHeight;         m_pDlg->m_nUnits = m_nUnits;         m_pDlg->Create (IDD_OPTIONS);         m_pDlg->ShowWindow (SW_SHOW);     } } LRESULT CChildView::OnApply (WPARAM wParam, LPARAM lParam) {     RECTPROP* prp = (RECTPROP*) lParam;     m_nWidth = prp->nWidth;     m_nHeight = prp->nHeight;     m_nUnits = prp->nUnits;     Invalidate ();     return 0; } LRESULT CChildView::OnDialogDestroyed (WPARAM wParam, LPARAM lParam) {     m_pDlg = NULL;     return 0; } 

OptionsDialog.h

#if !defined(AFX_OPTIONSDIALOG_H__7040DB90_9039_11D2_8E53_006008A82731__INCLUDED_) #define  AFX_OPTIONSDIALOG_H__7040DB90_9039_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // OptionsDialog.h : header file // /////////////////////////////////////////////////////////////////////////// // COptionsDialog dialog class COptionsDialog : public CDialog { // Construction public:     COptionsDialog(CWnd* pParent = NULL);   // standard constructor // Dialog Data     //{{AFX_DATA(COptionsDialog)     enum { IDD = IDD_OPTIONS };     int        m_nWidth;     int        m_nHeight;     int        m_nUnits;     //}}AFX_DATA // Overrides     // ClassWizard generated virtual function overrides     //{{AFX_VIRTUAL(COptionsDialog)     protected:     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support     virtual void PostNcDestroy();     //}}AFX_VIRTUAL     virtual void OnOK ();     virtual void OnCancel (); // Implementation protected:     // Generated message map functions     //{{AFX_MSG(COptionsDialog)     afx_msg void OnReset();     //}}AFX_MSG     DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately  // before the previous line. #endif  // !defined( //    AFX_OPTIONSDIALOG_H__7040DB90_9039_11D2_8E53_006008A82731__INCLUDED_) 

OptionsDialog.cpp

// OptionsDialog.cpp : implementation file // #include "stdafx.h" #include "DlgDemo2.h" #include "OptionsDialog.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // COptionsDialog dialog COptionsDialog::COptionsDialog(CWnd* pParent /*=NULL*/)     : CDialog(COptionsDialog::IDD, pParent) {     //{{AFX_DATA_INIT(COptionsDialog)     m_nWidth = 0;     m_nHeight = 0;     m_nUnits = -1;     //}}AFX_DATA_INIT } void COptionsDialog::DoDataExchange(CDataExchange* pDX) {     CDialog::DoDataExchange(pDX);     //{{AFX_DATA_MAP(COptionsDialog)     DDX_Text(pDX, IDC_WIDTH, m_nWidth);     DDX_Text(pDX, IDC_HEIGHT, m_nHeight);     DDX_Radio(pDX, IDC_INCHES, m_nUnits);     //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(COptionsDialog, CDialog)     //{{AFX_MSG_MAP(COptionsDialog)     ON_BN_CLICKED(IDC_RESET, OnReset)     //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // COptionsDialog message handlers void COptionsDialog::OnReset()  {     m_nWidth = 4;     m_nHeight = 2;     m_nUnits = 0;     UpdateData (FALSE);     } void COptionsDialog::OnOK () {     UpdateData (TRUE);     RECTPROP rp;     rp.nWidth = m_nWidth;     rp.nHeight = m_nHeight;     rp.nUnits = m_nUnits;       AfxGetMainWnd ()->SendMessage (WM_USER_APPLY, 0, (LPARAM) &rp); } void COptionsDialog::OnCancel () {     DestroyWindow (); } void COptionsDialog::PostNcDestroy ()  {     CDialog::PostNcDestroy ();     AfxGetMainWnd ()->SendMessage (WM_USER_DIALOG_DESTROYED, 0, 0);     delete this; } 

As before, the Options dialog box is invoked by selecting Options from the File menu. Here's the code in OnFileOptions that constructs the dialog object, initializes the dialog's data members, and creates the dialog box:

 m_pDlg = new COptionsDialog; m_pDlg->m_nWidth = m_nWidth; m_pDlg->m_nHeight = m_nHeight; m_pDlg->m_nUnits = m_nUnits; m_pDlg->Create (IDD_OPTIONS); m_pDlg->ShowWindow (SW_SHOW); 

To avoid automatic destruction, the dialog object is created on the heap rather than on the stack. The dialog pointer is saved in CChildView::m_pDlg, which is initialized to NULL by CChildView's constructor and reset to NULL when the dialog box is destroyed. Any member function of CChildView can determine whether the dialog box is currently displayed by checking m_pDlg for a non-NULL value. This turns out to be quite useful because before creating the Options dialog box, OnFileOptions checks m_pDlg to see whether the dialog box is already displayed. If the answer is yes, OnFileOptions uses the m_pDlg pointer to set the focus to the existing dialog box rather than create a new one:

 if (m_pDlg != NULL)     m_pDlg->SetFocus (); 

Without this precaution, every invocation of File-Options would create a new instance of the dialog, even if other instances already existed. There's normally no reason to have two or more copies of the same dialog box on the screen at the same time, so you shouldn't allow the user to open multiple instances of a modeless dialog box unless circumstances warrant it.

Processing the Apply and Close Buttons

One of the fundamental differences in implementing modal and modeless dialog boxes with MFC is how the dialog classes handle OnOK and OnCancel. A modal dialog class rarely overrides OnCancel because the default implementation in CDialog calls EndDialog to close the dialog box and return IDCANCEL. OnOK rarely needs to be overridden because the CDialog implementation of OnOK calls UpdateData to update the dialog's data members before dismissing the dialog box. If the dialog box's controls and data members are linked via DDX or DDV, the default action provided by CDialog::OnOK is usually sufficient.

A modeless dialog box, by contrast, almost always overrides OnOK and OnCancel. As mentioned earlier, it's important to prevent CDialog::OnOK and CDialog::OnCancel from being called in a modeless dialog box because modeless dialog boxes are dismissed with DestroyWindow, not EndDialog. You should override OnOK if any button in the dialog box has the ID IDOK. You should always override OnCancel because an IDCANCEL notification is sent when the user presses the Esc key or clicks the dialog box's close button, regardless of whether the dialog box contains a Cancel button.

Because clicking DlgDemo2's Apply and Close buttons generates calls to On-OK and OnCancel, both functions are overridden in COptionsDialog. COptionsDialog::OnOK contains the following statements:

 UpdateData (TRUE); RECTPROP rp; rp.nWidth = m_nWidth; rp.nHeight = m_nHeight; rp.nUnits = m_nUnits;   AfxGetMainWnd ()->SendMessage (WM_USER_APPLY, 0, (LPARAM) &rp); 

The first statement updates the dialog's member variables to match the current state of the controls. A modeless dialog box that uses DDX or DDV must call UpdateData itself because calling CDialog::OnOK and letting it call UpdateData is out of the question. The next block of statements instantiates the RECTPROP structure declared in Stdafx.h and copies the new settings from the dialog's data members to the data structure. The final statement sends a message to the application's main window telling it to apply the settings contained in the RECTPROP structure to the dialog box. WM_USER_APPLY is a user-defined message that's defined this way in Stdafx.h:

 #define WM_USER_APPLY WM_USER+0x100 

WM_USER, which is defined as 0x400 in the header file Winuser.h, specifies the low end of a range of message IDs an application can use without conflicting with the message IDs of standard Windows messages such as WM_CREATE and WM_PAINT. An application is free to use message IDs from WM_USER's 0x400 through 0x7FFF for its own purposes. Messages in this range are referred to as user-defined messages. Because dialog boxes use some message IDs in this range themselves, DlgDemo2 arbitrarily adds 0x100 to WM_USER to avoid conflicts.

A message transmitted with SendMessage includes two parameters the sender can use to pass data to the receiver: a 32-bit value of type WPARAM and another 32-bit value whose type is LPARAM. When COptionsDialog::OnOK sends a message to the main window, it sends along a pointer to a RECTPROP structure containing the settings retrieved from the dialog box. The main window processes the message with CMainFrame::OnApply, which is referenced in the message map with the following statement:

 ON_MESSAGE (WM_USER_APPLY, OnApply); 

When activated, OnApply forwards the message to the view:

 LRESULT CMainFrame::OnApply (WPARAM wParam, LPARAM lParam) {     m_wndView.SendMessage (WM_USER_APPLY, wParam, lParam);     return 0; } 

CChildView::OnApply, in turn, copies the values out of the data structure and into its own data members. It then invalidates the view to force a repaint incorporating the new settings:

 LRESULT CChildView::OnApply (WPARAM wParam, LPARAM lParam) {     RECTPROP* prp = (RECTPROP*) lParam;     m_nWidth = prp->nWidth;     m_nHeight = prp->nHeight;     m_nUnits = prp->nUnits;     Invalidate ();     return 0; } 

The value returned by a handler for a user-defined message is returned to the caller through SendMessage. DlgDemo2 attaches no meaning to the return value, so both CMainFrame::OnApply and CChildView::OnApply return 0.

COptionsDialog::OnCancel contains just one statement: a call to DestroyWindow to destroy the dialog box. Ultimately, this action activates COptionsDialog::PostNcDestroy, which is implemented as follows:

 void COptionsDialog::PostNcDestroy () {     CDialog::PostNcDestroy ();     AfxGetMainWnd ()->SendMessage (WM_USER_DIALOG_DESTROYED, 0, 0);     delete this; } 

This SendMessage sends a different user-defined message to the main window. The main window's WM_USER_DIALOG_DESTROYED handler, CMainFrame::OnDialogDestroyed, forwards the message to the view, whose WM_USER_DIALOG_DESTROYED handler responds by setting m_pDlg to NULL. Its work almost done, PostNcDestroy finishes up by executing a delete this statement to delete the dialog object created by CChildView::OnFileOptions.



Programming Windows with MFC
Programming Windows with MFC, Second Edition
ISBN: 1572316950
EAN: 2147483647
Year: 1999
Pages: 101
Authors: Jeff Prosise

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net