In the Microsoft Foundation Class (MFC) Library version 6.0, modal and modeless dialogs share the same base class, CDialog, and they both use a dialog resource that you can build with the dialog editor. If you're using a modeless dialog with a view, you'll need to know some specialized programming techniques.
For modal dialogs, you've already learned that you construct a dialog object using a CDialog constructor that takes a resource template ID as a parameter, and then you display the modal dialog window by calling the DoModal member function. The window ceases to exist as soon as DoModal returns. Thus, you can construct a modal dialog object on the stack, knowing that the dialog window has been destroyed by the time the C++ dialog object goes out of scope.
Modeless dialogs are more complicated. You start by invoking the CDialog default constructor to construct the dialog object, but then to create the dialog window you need to call the CDialog::Create member function instead of DoModal. Create takes the resource ID as a parameter and returns immediately with the dialog window still on the screen. You must worry about exactly when to construct the dialog object, when to create the dialog window, when to destroy the dialog, and when to process user-entered data.
Here's a summary of the differences between creating a modal dialog and a modeless dialog.
Modal Dialog | Modeless Dialog | |
Constructor used | Constructor with resource ID param | Default constructor (no params) |
Function used to create window | DoModal | Create with resource ID param |
Suppose you want the modeless dialog window to be destroyed when the user clicks the dialog's OK button. This presents a problem. How does the view know that the user has clicked the OK button? The dialog could call a view class member function directly, but that would "marry" the dialog to a particular view class. A better solution is for the dialog to send the view a user-defined message as the result of a call to the OK button message-handling function. When the view gets the message, it can destroy the dialog window (but not the object). This sets the stage for the creation of a new dialog.
You have two options for sending Windows messages: the CWnd::SendMessage function or the PostMessage function. The former causes an immediate call to the message-handling function, and the latter posts a message in the Windows message queue. Because there's a slight delay with the PostMessage option, it's reasonable to expect that the handler function has returned by the time the view gets the message.
Now suppose you've accepted the dialog default pop-up style, which means that the dialog isn't confined to the view's client area. As far as Windows is concerned, the dialog's "owner" is the application's main frame window (introduced in Chapter 13), not the view. You need to know the dialog's view to send the view a message. Therefore, your dialog class must track its own view through a data member that the constructor sets. The CDialog constructor's pParent parameter doesn't have any effect here, so don't bother using it.
We could convert the Chapter 6 monster dialog to a modeless dialog, but starting from scratch with a simpler dialog is easier. Example EX07A uses a dialog with one edit control, an OK button, and a Cancel button. As in the Chapter 6 example, pressing the left mouse button while the mouse cursor is inside the view window brings up the dialog, but now we have the option of destroying it in response to another eventpressing the right mouse button when the mouse cursor is inside the view window. We'll allow only one open dialog at a time, so we must be sure that a second left button press doesn't bring up a duplicate dialog.
To summarize the upcoming steps, the EX07A view class has a single associated dialog object that is constructed on the heap when the view is constructed. The dialog window is created and destroyed in response to user actions, but the dialog object is not destroyed until the application terminates.
Here are the steps to create the EX07A example:
Be sure to select the dialog's Visible property.
Add the message-handling functions shown next. To add a message-handling function, click on an object ID, click on a message, and then click the Add Function button. The Add Member Function dialog box appears. Edit the function name if necessary, and click the OK button.
Object ID | Message | Member Function |
IDCANCEL | BN_CLICKED | OnCancel |
IDOK | BN_CLICKED | OnOK |
private: CView* m_pView;
Also, add the function prototypes as follows:
public: CEx07aDialog(CView* pView); BOOL Create();
Using the CView class rather than the CEx07aView class allows the dialog class to be used with any view class.
#define WM_GOODBYE WM_USER + 5
The Windows constant WM_USER is the first message ID available for user-defined messages. The application framework uses a few of these messages, so we'll skip over the first five messages.
Visual C++ maintains a list of symbol definitions in your project's resource.h file, but the resource editor does not understand constants based on other constants. Don't manually add WM_GOODBYE to resource.h because Visual C++ might delete it.
CEx07aDialog::CEx07aDialog(CView* pView) // modeless constructor { m_pView = pView; }
You should also add the following line to the AppWizard-generated modal constructor:
m_pView = NULL;
The C++ compiler is clever enough to distinguish between the modeless constructor CEx07aDialog(CView*) and the modal constructor CEx07aDialog(CWnd*). If the compiler sees an argument of class CView or a derived CView class, it generates a call to the modeless constructor. If it sees an argument of class CWnd or another derived CWnd class, it generates a call to the modal constructor.
BOOL CEx07aDialog::Create() { return CDialog::Create(CEx07aDialog::IDD); }
Create is not a virtual function. You could have chosen a different name if you had wanted to.
void CEx07aDialog::OnCancel() // not really a message handler { if (m_pView != NULL) { // modeless case -- do not call base class OnCancel m_pView->PostMessage(WM_GOODBYE, IDCANCEL); } else { CDialog::OnCancel(); // modal case } } void CEx07aDialog::OnOK() // not really a message handler { if (m_pView != NULL) { // modeless case -- do not call base class OnOK UpdateData(TRUE); m_pView->PostMessage(WM_GOODBYE, IDOK); } else { CDialog::OnOK(); // modal case } }
If the dialog is being used as a modeless dialog, it sends the user-defined message WM_GOODBYE to the view. We'll worry about handling the message later.
For a modeless dialog, be sure you do not call the CDialog::OnOK or CDialog::OnCancel function. This means you must override these virtual functions in your derived class; otherwise, using the Esc key, the Enter key, or a button click would result in a call to the base class functions, which call the Windows EndDialog function. EndDialog is appropriate only for modal dialogs. In a modeless dialog, you must call DestroyWindow instead, and if necessary, you must call UpdateData to transfer data from the dialog controls to the class data members.
private: CEx07aDialog* m_pDlg;
If you add the forward declaration
class CEx07aDialog;
at the beginning of ex07aView.h, you won't have to include ex07aDialog.h in every module that includes ex07aView.h.
CEx07aView::CEx07aView() { m_pDlg = new CEx07aDialog(this); } CEx07aView::~CEx07aView() { delete m_pDlg; // destroys window if not already destroyed }
void CEx07aView::OnDraw(CDC* pDC) { pDC->TextOut(0, 0, "Press the left mouse button here."); }
void CEx07aView::OnLButtonDown(UINT nFlags, CPoint point) { // creates the dialog if not created already if (m_pDlg->GetSafeHwnd() == 0) { m_pDlg->Create(); // displays the dialog window } } void CEx07aView::OnRButtonDown(UINT nFlags, CPoint point) { m_pDlg->DestroyWindow(); // no problem if window was already destroyed }
For most window types except main frame windows, the DestroyWindow function does not destroy the C++ object. We want this behavior because we'll take care of the dialog object's destruction in the view destructor.
#include "ex07aView.h" #include "ex07aDialog.h"
ON_MESSAGE(WM_GOODBYE, OnGoodbye)
LRESULT CEx07aView::OnGoodbye(WPARAM wParam, LPARAM lParam) { // message received in response to modeless dialog OK // and Cancel buttons TRACE("CEx07aView::OnGoodbye %x, %lx\n", wParam, lParam); TRACE("Dialog edit1 contents = %s\n", (const char*) m_pDlg->m_strEdit1); m_pDlg->DestroyWindow(); return 0L; }
afx_msg LRESULT OnGoodbye(WPARAM wParam, LPARAM lParam);
With Win32, the wParam and lParam parameters are the usual means of passing message data. In a mouse button down message, for example, the mouse x and y coordinates are packed into the lParam value. With the MFC library, message data is passed in more meaningful parameters. The mouse position is passed as a CPoint object. User-defined messages must use wParam and lParam, so you can use these two variables however you want. In this example, we've put the button ID in wParam.
If you use the EX07A view and dialog classes in an MDI application, each MDI child window can have one modeless dialog. When the user closes an MDI child window, the child's modeless dialog is destroyed because the view's destructor calls the dialog destructor, which, in turn, destroys the dialog window.