Modal Dialogs
Modal dialogs: must be dismissed by user before any work can be done outside the dialog in the same application. (But you can work in another application).
System modal: can't even click to another application (example, the Control-Alt-Delete dialog).
Modeless: dialog can stay up while you work elsewhere (example, toolbars or tool palettes).
Purposes of modal dialogs:
- collect data from user
- present data to the user
- permit user to control application data
Modal dialogs are used with an ordinary SDI or MDI application. When you choose a “dialog-based” application, your main windows is a dialog box that is never dismissed. This is not a modal dialog. Of course, such an application can also make use of modal dialogs, but the main window is not a modal dialog.
Example
Let’s make the simplest possible dialog box, with just two controls, to let the user specify the height and width of a rectangle. Then in OnDraw we will draw a rectangle in the specified size. Make a new SDI application called RectangleDialog. Choose Project | Add Resource and scroll down to Dialog:
Select Dialog and click the New button, or double-click Dialog.
You will be thrown into the Dialog Editor, which we used in the previous lecture.
Design your dialog. In this case, put in two edit boxes, one above the other, and two static text boxes, to hold the labels Height and Width. The ID numbers of the static text boxes do not matter, as no code will involve them. Change the ID numbers of the edit boxes to IDC_HEIGHT and IDC_WIDTH. Note: the toolbar refers to an edit box as an “Edit Control”—but everybody calls them “edit boxes”. Similarly, the toolbar says “Static Text”, but everyone calls it a “static text box”.
Here’s a picture of the dialog box in the dialog editor, after dragging the controls to the desired position, and changing the captions of the static text boxes on their property sheets:
Right-click your dialog in the Dialog Editor (in a blank area that is not part of a control). Choose Add Class. That brings up Class Wizard:
Give your class a name (RectangleDialog) and note that the names of the .h and .cpp file are filled in for you. Change the base class
to CDialog—the picture shows the default, which is NOT CDialog. If you forget to change this, you’ll find out much later that you dialog doesn’t work properly; then the easiest way to correct the error will be to delete the wrong dialog and start again. You cannot change the Dialog ID on this screen—you can change it on the properties sheet of the dialog, but not here.
The new class is derived from CDialog, which has member functions OnOK, OnCancel, and DoModal.
OnOK calls UpdateData(TRUE) and then dismisses the dialog.
OnCancel just dismisses the dialog.
DoModal creates the dialog, lets it run, and only terminates when the dialog is dismissed.
Here is the manual entry:
CDialog::DoModal
virtualintDoModal();
Return Value
An int value that specifies the value of the nResult parameter that was passed to the CDialog::EndDialog member function, which is used to close the dialog box. The return value is –1 if the function could not create the dialog box, or IDABORT if some other error occurred.
Remarks
Call this member function to invoke the modal dialog box and return the dialog-box result when done. This member function handles all interaction with the user while the dialog box is active. This is what makes the dialog box modal; that is, the user cannot interact with other windows until the dialog box is closed.
If the user clicks one of the pushbuttons in the dialog box, such as OK or Cancel, a message-handler member function, such as OnOK or OnCancel, is called to attempt to close the dialog box. The default OnOK member function will validate and update the dialog-box data and close the dialog box with result IDOK, and the default OnCancel member function will close the dialog box with result IDCANCEL without validating or updating the dialog-box data. You can override these message-handler functions to alter their behavior.
Next we are going to add two member variables in the RectangleDialog class, one for each edit box. These variables will be integers that store the numbers the user has entered in the boxes.
Right click the dialog in the dialog editor and choose Add Variable. Fill in the resulting form as follows. Pay close attention to each field of the form as any mistake will cause trouble.
The check in “Control variable” along with the control ID says that you want this variable to be associated with the IDC_HEIGHT edit box. The choice of “value” for the “Category” says this variable will refer to the contents of the edit box, rather than to the edit box itself. The min and max values will enable MFC to do some error checking for you—it will enforce that the user cannot get away with pressing OK unless the value in the height box is between 10 and 400. The declaration that we want this variable to have type int will enable MFC to enforce the requirement that the user must enter an integer—if she enters giraffe and presses OK, she will get an error message. But she will be able to type in giraffe.
Similarly, add a variable m_width corresponding to IDC_WIDTH.
We still have not put any code into the application to make this dialog work. Before we do, we need to put in code to draw the rectangle whose height and width will be controlled by the dialog.
We’ve done this before: Just put a member variable m_theRect into the document class, and initialize it in the constructor of the document class, and in OnDraw put this code:
pDC->TextOut(5,5,”Right Click to change the size.”);
pDC->FillSolidRect(&pDoc->m_theRect, RGB(255,0,0));
Be sure you initialize the top of the rectangle to 30 or more so that it doesn’t obscure the text. The following code makes the dialog visible and the edit box work. It makes the dialog come up on a right-click, as we announced it would.
void CLab5View::OnRButtonDown(UINT nFlags, CPoint point)
{ RectangleDialog dialog;
CLab5Doc *pDoc = GetDocument();
dialog.m_width =
pDoc->m_theRect.right-pDoc->m_theRect.left;
dialog.m_height =
pDoc->m_theRect.bottom-pDoc->m_theRect.top;
int rval = dialog.DoModal();
if(rval == IDOK)
{ pDoc->m_theRect.right =
pDoc->m_theRect.left + dialog.m_width;
pDoc->m_theRect.bottom =
pDoc->m_theRect.top + dialog.m_height
Invalidate();
}
}
The program should now be working. Try the following tests:
1. You do get error messages from entering non-integers, or integers out of the specified range.
2. If you enter some numbers and press Cancel, the rectangle does not change size.
3. If you enter some numbers and press Enter, the numbers you entered are used correctly.
4. Every time the dialog comes up, the edit boxes contain the current height and width (including the first time).
How does this work??! We will see.
Data Exchange
Every Windows program with a dialog must have (at least) TWO copies of the data controlled by the dialog. There is the document data, which is actually used (in our example m_theRect) in the document’s work (in our case drawing the rectangle). Then there is the data that the user enters in the dialog box. This is tentative, because the user can always press Cancel, in which case the document data should not be changed. Therefore, the document data should be changed only when OK is pressed and the data has been validated (i.e. is acceptable). In the code above, that happens in the last few lines, when DoModal has returned ID_OK.
In an MFC application, there are actually THREE copies of the data while the dialog box is open. There is the document data, there is the data stored in m_height and m_width, and in addition there is the data stored in the edit controls themselves, which is updated with every key press. It is your job to connect your document data with the dialog member variables. You initialize them before calling DoModal (from the document data) and you set your document data from them when DoModal returns IDOK.