Modeless Dialog Boxes

To make a modeless dialog box, create the dialog box using

the Create function, instead of calling DoModal. This is what makes the box modeless. When the box is destroyed, EndDialog is not used; instead, DestroyWindow is called. Since the normal OnOk and OnCancel member functions of a CDialog object would call EndDialog, you need to make sure your dialog doesn't call those functions and instead overrides them to call DestroyWindow. In the lab example, we simply delete the OK and Cancel buttons from the dialog. MFC seems to be smart enough not to call OnOK when the Enter key is presssed and OnCancel when the Esc key is pressed, after we have deleted those buttons.

Usually, when you create a modal dialog box, you destroy it manually after DoModal returns. Since Create returns immediately, you'll need to have some other mechanism for destroying the C++ object associated with the window. In the lab example, we create both the C++ object (by declaring a variable) and the Win32 object (by calling Create) in the view constructor, and then call DestroyWindow in the view destructor.

ShowWindow

When we want to make the dialog box visible or invisible, we do not create or destroy it. Instead, we call ShowWindow(SW_SHOW) to make a window visible, and ShowWindow(SW_HIDE) to make it invisible. ShowWindow is a member function of the CWnd class in MFC. Hiding a window in this way does not affect its existence as a data structure in memory--it only stops Windows from displaying it on the screen.

A Demo Program

The plan is to create a modeless dialog box that allows the user to change the document data. The dialog box should come up on depressing the right mouse button and should have a Close button that dismisses it. To make the program as simple as possible, and focus the effort on the dialog box, the program just draws a colored square. The document data is just the length of one side of the square, in pixels.

1. First we write the same old program to draw a colored square. Add a member variable m_side of type int in the document class. Initialize it to 30 in the constructor of the document class. Add code in the OnDraw function of the view class to draw a square of side pDoc-> m_side.

2. Create a new dialog box using the dialog editor, containing an edit box (for typing in the length of the side) and a Close button. Give the button the id IDC_CLOSE. Delete the OK and Cancel buttons from this dialog (select them and press the Delete key).

3. Use Class Wizard to create a new class CSquareDialog for your dialog.

4. Use Class Wizard to add a member variable m_edit of type int associated to your edit box.

Specify a minimum value of 0 and maximum value of 400 for its validation.

5. Add a member variable m_dialog of type CSquareDialog to your view class. (Right click your view class in the class view in the workspace window.) Of course you will have to add #include “SquareDialog.h” at the top of the file to get this to compile. It will have to come before the include command for the view header file, so that when the view header file is read (containing the definition of m_dialog) the compiler will have already seen the definition of CSquareDialog.

6. The main difficulty in this program is that we want to change the document data from inside the dialog class, when the edit box text changes. How can we get at the document data from the dialog class? GetDocument is a member of the view class, so it would be enough to get at the view class, but that isn’t so easy. Add a member variable m_view of type (CModelessDemoView *) (assuming the name of the program is ModelessDemo) to your dialog class. You cannot build after that because the declaration of m_view generates an error. That’s because the view class is not defined when your file SquareDialog.cpp is compiled.

How can this be cured? We cannot add # include ModelessDemo.h to SquareDialog.cpp because, as we saw in step 7, we need the compiler to process the definition of CSquareDialog before it processes the view header file.

This is what we call in C++ a “bidirectional dependency”. One class has a member variable

whose type is a second class, and the second class has a member variable whose type is the first class. This is a C++ problem that has nothing specific to do with Windows programming. The cure is a forward declaration:

class CModelessDemoView;

Insert this line exactly as shown near the top of SquareDialog.h. It has to come before the first reference to CModelessDemoView. It tells the compiler that CModelessDemoView is a class, so the compiler will not complain when a variable of that type is declared. The compiler will wait to see the actual definition of that class later. Note, this is not the same as a #include command—do not try to make it into a # include command. It is part of the C++ language.

When you have added this forward declaration, you should be able to compile and build. If not, perhaps you will find a command #include CModelessDemo.h in your SquareDialog.h file, with the comment inserted by Class View. Delete that line if it’s there-Class View is too “smart”.

7. In the constructor of CModelessDemoView, call m_dialog.Create(IDD_DIALOG1). (This creates the Win32 object which m_dialog is supposed to wrap. This creates the dialog window, but does not make it visible.) Also put in: m_dialog.m_view = this; That way, the view will be accessible from within the dialog class. Put in m_dialog.m_edit = 30; also. This must come before the Create call or the initialization won’t be visible.

8. In the destructor of CModelessDemoView, call DestroyWindow(); this will destroy the window created by m_dialog.Create.

9. Create a handler OnRButtonDown by mapping the WM_RBUTTONDOWN message in your view class. This is where we want to make the dialog come up. Since it's a modeless dialog, we don't do that with DoModal. Instead, since we have already called m_dialog.Create(IDD_DIALOG1), all we have to do is make the dialog visible by m_dialog.ShowWindow(SW_SHOW).

10. Add a handler for the IDC_CLOSE button in the dialog class. The code should be ShowWindow(SW_HIDE); This makes the dialog invisible, without destroying it.

11. Since this is a modeless dialog, there is no OK button and we cannot wait until the user presses OK to change the data. We want the square to change appearance every time the user changes the number in the edit box. (Alternately, we could provide an “Update Appearance” button—but we don’t do that.) Every time the edit box text changes, the edit box sends a message to its parent. That message is EN_CHANGE. In your dialog class, add a handler for the EN_CHANGE message. Here we call UpdateData(TRUE) to get the value from the edit box into m_edit. Then, we want to set the document member variable m_side. We can do this with

m_view->GetDocument()->m_radius = m_edit;

After this, of course, we have to call m_view->Invalidate(); if we hope to see any results. Or,

GetDocument()->UpdateAllViews();

should also work, since that will invalidate the view. This code will not yet compile, because although we put a forward declaration about the view class, now we’re trying to compile its member functions, and the compiler hasn’t seen those member functions yet. The next step will cure this.

12. Manually add #include “ModelessDemoDoc.h” and #include “ModelessDemoView.h” at the top of the file SquareDialog.cpp where you put the handler for the Close button. Whenever you include ModelessDemoView.h you will also need ModelesDemosDoc.h first, so that the GetDocument declaration in ModelessDemoView.h will compile. Now it should build.

Test the program. Start the program--you should see a square. Right-click--you should see the dialog. Check that you can resize the view window--yes, the dialog is modeless. Use the edit box--the square should be redrawn in the new side. Close the dialog--the dialog should disappear. Right-click again--the dialog comes back. Close it again, it disappears.