Attaching and Detaching Objects

Modeless dialogs

One of the common questions in the various forums is along the lines of "I tried to create my modeless dialog, and it failed. I never get the dialog. What's wrong?" and it is accompanied by a snippet of code that looks like this:

void CMyClass::OnLaunchDialog()
    {
     CMyToolbox dlg;
     dlg.Create(dlg.IDD);
    }

At this point you should now understand what has happened. The dialog is created, but as soon as the scope containing the variable is exited, the destructor comes in, and since this is a window, calls DestroyWindow on the associated object. Of course, this technique will always work for a modal dialog, as in

    {
     CMyDataEntryScreen dlg;
     dlg.DoModal();
    }

because after the modal dialog exits there is no need for the dlg variable. 

(It has never made sense to me why I have to provide the dialog ID for the Create method, since it is implicit in the class!)

But you can't use Detach here, because the dialog wants to process messages and it wants to have state. I've never tested this, but I suspect that if you actually did do a Detach that you would either start getting massive numbers of ASSERT failures, or you would experience an access fault of some sort. You really need that modeless dialog object around!

This is a case where the correct method is to create a CDialog reference, for example, add the following line to your CWinApp class (this assumes you only want one toolbox for all instances of windows):

// in your CWinApp class
     CMyToolbox * tools;
 
// in your CWinApp constructor:
    tools = NULL;
 
// in your activator:
void CWinApp::OnOpenToolbox()
    {
     if(tools != NULL)
         { /* use existing */
          if(tools->IsIconic())
              tools->ShowWindow(SW_RESTORE);
          tools->SetForegroundWindow();
          return;
         } /* use existing */
     tools = new CMyToolbox;
     tools->Create(CMyToolBox::IDD);
    }

This will create the window if it does not exist, and if it does, it simply brings it up to the top. This code assumes that the window can be minimized, so it will restore it if necessary. If you don't support minimization of the dialog, you may not need the SW_RESTORE operation. OTOH, it is sometimes convenient to not actually destroy the window when it is "closed", but simply to hide it, in which case you would use SW_SHOW as the operation.

Why not just create a CMyToolbox variable (not a reference)? Well, because you need to know when to delete the object, and it is easier if you are totally consistent and never allocate one on the stack or as a class member, but only use pointers to a heap-allocated version. You need to add a PostNcDestroy handler, which is a virtual method, to your class, of the form:

void CMyToolbox::PostNcDestroy()
    {
     CDialog::PostNcDestroy();
     // add this line
     delete this;
    }

This guarantees that when the window is closed, the instance of the window will be deleted. Note that this does not change your pointer, in our example, tools, so unless you explicitly set it to NULL you are going to be in deep trouble!

I handle this in a variety of ways. The most common method I use is to post a user-defined message back to the parent window that the modeless dialog has been destroyed. This tells the CWinApp class that it can zero out the variable. Note that a CWinApp, although a CCmdTarget, is not a CWnd, so you can't post a message to it using PostMessage. Instead, you have to do PostThreadMessage and do an ON_THREAD_MESSAGE or ON_REGISTERED_THREAD_MESSAGE to handle it. If you don't know what I'm talking about, read my essay on message management.

void CMyToolbox::PostNcDestroy()
    {
     CDialog::PostNcDestroy();
     // add these lines
     delete this;
     AfxGetApp()->PostThreadMessage(UWM_TOOLBOX_CLOSED, 0, 0);
    }

And in the class definition of your CWinApp class, add

afx_msg LRESULT OnToolboxClosed(WPARAM, LPARAM);

and add in your CWinApp message map:

ON_THREAD_MESSAGE(UWM_TOOLBOX_CLOSED, OnToolboxClosed)

or

ON_REGISTERED_THREAD_MESSAGE(UWM_TOOLBOX_CLOSED, OnToolboxClosed)

and the implementation method

LRESULT CMyApp::OnToolboxClosed(WPARAM, LPARAM)
    {
     tools = NULL;
     return 0;
    }

I explain the difference between ordinary messages and registered messages in my essay on message management. There are some hazards with this, because if your application happens to be in a message loop other than the main message pump (for example, has a modal dialog or MessageBox active) the PostThreadMessage won't be seen, and you have to handle a PostMessage in the MainFrame class.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Debuggers don't remove bugs. They only show them in slow motion.”