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.

Dynamic Data Exchange

The exchange of data between the dialog member variables and the controls themselves is called Dynamic Data Exchange. MFC does this for you, although there are functions that you can use to force it to happen at a time you desire. For simple dialogs, you do not need to do this. When DoModal is first called, it initializes the controls from the member variables. That is why you see the correct text in the edit boxes when the dialog comes up. When OK or Cancel is clicked, DoModal gets the values out of the controls, puts them in the member variables, and (in the case of OK) tries to validate them. If they don’t pass the validation, it puts up an error message and DoModal does not terminate. If they do pass, DoModal terminates, and your member variables have the values that the user entered. Then it’s up to you to get them where you want them—usually into the document data.

Review: the steps for using a modal dialog are

1. Design your dialog.

2. Add a class corresponding to your dialog.

3. Add member variables corresponding to your controls.

4. Put in a call to DoModal. Before the call, initialize your member variables, and if the return value is IDOK, exchange the data out of the member variables into the document data.

You need to practice these steps on your own several times so that you can do them reliably, accurately, quickly, and without having to look up how to do them. Aim for fifteen minutes in putting up a simple dialog that controls a simple drawing. Try it again and again. You will get one exercise in the lab and another in homework, but you should do this five or six times at least so you will never forget. Every semester there are some students who cannot do this on the final exam. Don’t be one of them.