The dialog boxes we've worked with so far in this chapter have been ordinary modal dialog boxes. Now let's move on to the modeless dialog box and to the common dialog boxes for modern versions of the Windows base class CDialog. They both use a dialog resource that you can build with the dialog editor. If you're using a modeless dialog box with a view, you'll need to know some specialized programming techniques.
For modal dialog boxes, you've already learned that you construct a dialog object using a CDialog constructor that takes a dialog resource template ID as a parameter, and then you display the modal dialog box 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 box has been destroyed by the time the dialog object goes out of scope.
Modeless dialog boxes are more complicated. You start by invoking the CDialog default constructor to construct the dialog object, but to create the dialog box 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 box still on the screen. You must worry about exactly when to construct the dialog object, when to create the dialog box, when to destroy the dialog box, and when to process user-entered data.
Table 7-2 summarizes the differences between creating a modal dialog box and a modeless dialog box.
Modal Dialog Box |
Modeless Dialog Box |
|
---|---|---|
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 box to be destroyed when the user clicks the dialog box's OK button. This presents a problem. How does the view know that the user has clicked the OK button? The dialog box could call a view class member function directly, but that would "marry" the dialog box to a particular view class. A better solution is for the dialog box 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 box (but not the object so that it can maintain any user data specified in the dialog box). This sets the stage for the creation of a new dialog box.
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 box default pop-up style, which means that the dialog box isn't confined to the view's client area. As far as Windows is concerned, the dialog box's "owner" is the application's main frame window (which you'll see in Chapter 12 ), not the view. You need to know the dialog box'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 monster dialog box created earlier in the chapter to a modeless dialog box, but starting from scratch with a simpler dialog box is easier. Example Ex07c uses a dialog box with one edit control, an OK button, and a Cancel button. As in the Dialog Box That Ate Cincinnati example, pressing the left mouse button while the mouse cursor is inside the view window brings up the dialog box, but now we have the option of destroying it in response to another event—pressing the right mouse button when the mouse cursor is inside the view window. We'll allow only one open dialog box at a time, so we must be sure that a second left button press doesn't bring up a duplicate dialog box.
To summarize the upcoming steps, the Ex07c view class has a single associated dialog object that is constructed on the heap when the view is constructed. The dialog box 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 Ex07c example:
Create a new MFC Application project named Ex07c. In the MFC Application Wizard, accept all the defaults but two: On the Application Type page, select Single Document, and on the Advanced Features page, deselect Printing And Print Preview.
Create a new dialog resource. On the Project menu, select Add Resource and add a new dialog box. The dialog editor assigns the ID IDD_DIALOG1 to the new dialog box. Using the Properties window for the dialog box, change the Caption property to Modeless Dialog and set the Visible property to True. Leave the default OK and Cancel buttons with IDs IDOK and IDCANCEL
Add controls to the dialog box. Add a static text control and an edit control with the default ID IDC_EDIT1. Change the Caption property of the static text control to Edit 1. Here is the completed dialog box:
Use the MFC Class Wizard to create the CEx07cDialog class. In Class View, right-click on the Ex07c project, choose Add and then Add Class, select the MFC Class template, and click Open to start the MFC Class Wizard. Name the class CEx07cDialog, make sure it derives from CDialog, and set the Dialog ID to IDD_DIALOG1, as shown here. When you're finished, click the Finish button.
Add message handlers for IDCANCEL and IDOK. Select the CEx07cDialog class in Class View, click the Events button in the Properties window, and add the OnBnClickedCancel and OnBnClickedOk message handlers as shown in the following table.
Object ID |
Message |
Member Function |
---|---|---|
IDCANCEL |
BN_CLICKED |
OnBnClickedCancel |
IDOK |
BN_CLICKED |
OnBnClickedOk |
Add a variable to the CEx07cDialog class. Select the CEx07cDialog class in Class View. Choose Add Variable from the Project menu, and use the Add Member Variable Wizard to add member variables for the IDC_EDIT1 control. Make the variable of type CString and name it m_strEdit1, as shown here:
Edit
private: CView* m_pView;
Also, add the function prototypes as follows:
public: CEx07cDialog(CView* pView); BOOL Create();
Note |
Using the CView class rather than the CEx07cView class allows the dialog class to be used with any view class. |
Edit
#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.
Note |
Visual C++ maintains a list of symbol definitions in your project's |
Add the modeless constructor in the file
CEx07cDialog::CEx07cDialog(CView* pView) // modeless constructor : m_strEdit1(_T(")) { m_pView = pView; }
You should also add the following line to the modal constructor generated by the MFC Application Wizard:
IMPLEMENT_DYNAMIC(CEx07cDialog, CDialog) CEx07cDialog::CEx07cDialog(CWnd* pParent /*=NULL*/) : CDialog(CEx07cDialog::IDD, pParent) , m_strEdit1(_T(")) { m_pView = NULL; }
The C++ compiler is clever enough to distinguish between the modeless constructor CEx07cDialog(CView*) and the modal constructor CEx07cDialog(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.
Add the Create function in
BOOL CEx07cDialog::Create() { return CDialog::Create(CEx07cDialog::IDD); }
Note |
Create is not a virtual function. You can choose a different name if you want to. |
Edit the OnBnClickedCancel and OnBnClickedOk functions in
void CEx07cDialog::OnBnClickedCancel() { if (m_pView != NULL) { // modeless case -- do not call base class OnCancel m_pView->PostMessage(WM_GOODBYE, IDCANCEL); } else { CDialog::OnCancel(); // modal case } } void CEx07cDialog::OnBnClickedOk() { 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 box is being used as a modeless dialog box, it sends the user-defined message WM_GOODBYE to the view. We'll worry about handling the message later.
Important |
For a modeless dialog box, be sure to 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 will result in a call to the base class functions, which call the Windows EndDialog function. EndDialog is appropriate only for modal dialog boxes. In a modeless dialog box, you must call DestroyWindow instead and, if necessary, you must call UpdateData to transfer data from the dialog controls to the class data members. |
Edit the
private: CEx07cDialog* m_pDlg;
You also need to add the forward declaration at the beginning of
class CEx07cDialog;
You won't have to include
Modify the CEx07cView constructor and destructor in the file
CEx07cView::CEx07cView() { m_pDlg = new CEx07cDialog(this); } CEx07cView::~CEx07cView() { delete m_pDlg; // destroys window if not already destroyed }
Add code to the virtual OnDraw function in the
void CEx07cView::OnDraw(CDC* pDC) { CEx07cDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDC->TextOut(0, 0, "Press the left mouse button here."); }
Add message handlers in CEx07cView for WM_LBUTTONDOWN and WM_RBUTTONDOWN. Select the CEx07cView class in Class View, click the Messages button in the Properties window, and add the OnLButtonDown and OnRButtonDown functions. Then edit the code in file
void CEx07cView::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 CEx07cView::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.
Add the dialog box header include statement to the file
#include "Ex07cView.h" #include "Ex07cDialog.h"
Add your own message code for the WM_GOODBYE message. Because Class View does not support user-defined messages, you must write the code yourself. This task makes you appreciate the work Visual Studio does for the other messages.
In
ON_MESSAGE(WM_GOODBYE, OnGoodbye)
Also in
LRESULT CEx07cView::OnGoodbye(WPARAM wParam, LPARAM lParam) { // message received in response to modeless dialog OK // and Cancel buttons TRACE("CEx07cView::OnGoodbye %x, %lx\n", wParam, lParam); TRACE("Dialog edit1 contents = %s\n", (const char*) m_pDlg->m_strEdit1); m_pDlg->DestroyWindow(); return 0L; }
In
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.
Build and test the application.Build and run Ex07c. Press the left mouse button and then the right mouse button. (Be sure the mouse cursor is outside the dialog box when you press the right mouse button.) Press the left mouse button again and enter some data in the Edit 1 edit control, and then click the dialog box's OK button. Does the view's TRACE statement correctly list the edit control's contents?
Note |
If you use the Ex07c view and dialog classes in an MDI application, each MDI child window can have one modeless dialog box. When the user closes an MDI child window, the child's modeless dialog box will be destroyed because the view's destructor calls the dialog box destructor, which in turn destroys the dialog box. |