A Rewrite of KB103788: INFO: Creating a Modeless Dialog Box with MFC Libraries

Home
Back To Tips Page

The MSDN article is confusing, misleading, and incomplete. This rewrite attempts to correct all these deficiencies.


To create a modeless dialog, first create a dialog template and populate it with controls. As you would for any dialog, create control variables, value variables, and event handlers. For purposes of this article, we will assume that the class name you have assigned to your class is CModeless. The parent class that launches the dialog will be called CMyClass.

1. In the parent class, add a pointer variable to the class

      CModeless * pdlg;

2. In the constructor of the parent class, add the line

      pdlg = NULL;

3. It is convenient to override the Create method to make it easier to create the modeless dialog; if you desire to do this, add the following method to the CModeless class:

      BOOL Create(CWnd * pWnd = NULL)
	    { return CDialog::Create(IDD, pWnd); }

4. The simplest form of creation is to skip step 3 entirely, and simply do

	pdlg = new CModeless;
	if(!pdlg->Create(CModeless::IDD, this))
             {
              ... deal with creation error here
              delete pdlg;
              ... avoid further processing
             }

but if you have overridden the Create member as described in step 3, you would only need to write

	if(!pdlg->Create(this))
             { 
              ... deal with creation error here
              delete pdlg;
              ...avoid further processing
             }

5. Neither OnOK nor OnCancel can be permitted to use the default handlers. They must be overridden. Note that removing the OK and Cancel buttons is not sufficient, because an <enter> key can generate a WM_COMMAND:IDOK message (which will invoke the OnOK virtual method) and an <esc> key can generate a WM_COMMAND:IDCANCEL message. Therefore, it is mandatory that these methods be overridden. It is common to delete both the OK and Cancel buttons from the dialog; alternatively, delete only one of the buttons, and label the remaining button Close. Generally, it does not matter which button you delete and which button you relabel.

5a. The OnCancel handler must call DestroyWindow, and must NOT call the superclass

        void CModeless::OnCancel()
            {
             // CDialog::OnCancel();  // MUST remove this line
             DestroyWindow();
            }

5b. The OnOK handler must call DestroyWindow, and must NOT call the superclass.

	void CModeless::OnOK()
            {
             // CDialog::OnOK(); // MUST remove this line
             DestroyWindow();
            }

Note that if you are depending on DDV to validate values, DDV will not be invoked because the superclass call has been bypassed. If the values are validated, there must be additional code added to make these values available, because the PostNcDestroy handler will delete them. If you want to use DDX/DDV, you must add code as shown:

	void CModeless::OnOK()
	    {
             // CDialog::OnOK(); // MUST remove this line
             if(!UpdateData(TRUE))
                { /* validation failed */
                 ...notify user that values are invalid
                 return;
                } /* validation failed */
             ...make values available to those who need them
             DestroyWindow();
            }

6. The PostNcDestroy handler must be overridden to delete the CModeless object

         void CModeless::PostNcDestroy()
	    {
             delete this;
            }

While this simple sketch shows you the bare bones of modeless dialog creation, it is not sufficient.

A more realistic model of modeless dialog usage

There is a problem with this simplistic model. The creator of the modeless dialog will not know that the dialog has been destroyed, and will not know that the pointer to it is now invalid. Also, if the modeless dialog was created by a menu item or other user action, the action should not create another instance. There are several ways to handle these situations.

Avoiding multiple instances/Allow minimize

void CMyClass::OnWantModelessDialog()
     {
      if(pdlg == NULL)
         { /* dialog does not exist */
          pdlg = new CModeless;
          if(!pdlg->Create(this)) // if step 3 override was done
              { /* failed */
               ... deal with notification
              delete pdlg;
              pdlg = NULL;
              return;
             } /* failed */
        else
            { /* already exists */
             if(pdlg->IsIconic())  // in case the minimize button is enabled
                 pdlg->ShowWindow(SW_RESTORE);
            } /* already exists */

To receive a notification of window termination, you must defined a user-defined message. This can be a WM_APP-based message or a Registered Window Message. Choose one of these two methods for specifying the message:

#define UWM_MODELESS_CLOSED (WM_APP + n) // for some value n, such as 200
static const UINT UWM_MODELESS_CLOSED =
                                     ::RegisterWindowMessage(_T("UWM_MODELESS_CLOSED-<guid here>"));

Add to your message map one of the following two lines, based on which type of message you are using

        ON_MESSAGE(UWM_MODELESS_CLOSED, OnModelessClosed)
	ON_REGISTERED_MESSAGE(UWM_MODELESS_CLOSED, OnModelessClosed)

add the following handler

        LRESULT CMyClass::OnModelessClosed(WPARAM, LPARAM)
            {
             pdlg = NULL;
             return 0;
            }

The techniques for message handling are discussed in my essay on Message Management.

Declare the handler in your parent class handler; for this example, the CMyClass class definition:

LRESULT OnModelessClosed(WPARAM, LPARAM);

In the dialog, add an OnDestroy handler:

        void CModeless::OnDestroy()
             {
              GetParent()->SendMessage(UWM_MODELESS_CLOSED);
              CDialog::OnDestroy();
             }

This will guarantee that the variable is NULL.

An alternative is to disable the control so another dialog instance is not created; this could be handled by adding an ON_UPDATE_COMMAND_UI handler. This would not allow the menu item/toolbar button to bring back a minimized dialog, but if the dialog cannot be minimized, this would be acceptable.

void CMyClass::OnUpdateWantModelessDialog(CCmdUI * pCmdUI)
    {
     pCmdUI->Enable(pdlg == NULL);
    }

Hiding and showing a modeless dialog

Another approach which is often convenient is to simply hide, rather than destroy, a modeless dialog. This way, there is no need to have to reload the control values each time it is created. To do this, the code should be modified as follows:

Modify the OnOK and OnCancel handlers to read

    void CModeless::OnCancel()
      {
       // CDialog::OnCancel();  // MUST remove this line
       ShowWindow(SW_HIDE);
      }
    void CModeless::OnOK()
      {
       // CDialog::OnOK(); // MUST remove this line
       ShowWindow(SW_HIDE);
      }

In addition add an OnClose handler

   void CModeless::OnClose()
      {
       // CDialog::OnClose(); // MUST remove this line
       ShowWindow(SW_HIDE);
      }

Modify the show-dialog handler to be

  void CMyClass::OnWantModelessDialog()
     {
      if(pdlg == NULL)
         { /* dialog does not exist */
          pdlg = new CModeless;
          if(!pdlg->Create(this)) // if step 3 override was done
              { /* failed */
               ... deal with notification
               delete pdlg;
               pdlg = NULL;
               return;
             } /* failed */
        else
            { /* already exists */  
          if(!pDlg->IsWindowVisible())         
                 pdlg->ShowWindow(SW_SHOW);
             if(pdlg->IsIconic())   // in case the minimize button is enabled
                  pdlg->ShowWindow(SW_RESTORE);
            } /* already exists */
     }

Some parent class has responsibility for managing this window; typically in its OnDestroy handler you will do

void CMyClass::OnDestroy()
   {
    if(pdlg != NULL)
        pdlg->DestroyWindow();
    CMyParent::OnDestroy();
   }

 

[Dividing Line Image]

The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright © 2008 Joseph M. Newcomer/FlounderCraft Ltd. All Rights Reserved.
Last modified: May 14, 2011