Dialog Box Control Management

Page 5 of 5
  1. Introduction
  2. The problem
  3. The solution
  4. The code
  5. When controls aren't there yet

When controls aren't there yet

There is a problem during dialog startup. For example, if you have an OnChange or OnUpdate handler for an edit control, and you wish to manipulate another control. Even if you don't believe my methodology about always doing this in a single routine, you will still have serious problems. Consider the case where you want to enable a button when the text becomes nonempty. The test looks like

CString s;
c_Edit.GetWindowText(s);
s.TrimLeft();
c_DoSomething.EnableWindow(s.GetLength() > 0);

You find yourself in the middle of an ASSERT statement which, if you trace back, came from the EnableWindow call. You look at the m_hWnd member of the c_DoSomething button, you find that it is 0. If you are using GetDlgItem, it is worse, because you would have written something like

CButton * doSomething = (CButton *)GetDlgItem(IDC_DO_SOMETHING);

and your attempt to use it would take an access fault because the pointer was NULL. (Don't use GetDlgItem; see my essay on the right way to do this).

This happens because of a mismatch between MFC and the underlying Windows mechanisms. What has happened is that the DDX mechanism has created the edit control, which means that it can start generating messages that the MESSAGE_MAP will start dispatching, but has not yet assigned the IDC_DO_SOMETHING control to its corresponding CButton. Hence the ASSERT failure when you try to use it, even though the control already exists.

The most common cause of the GetDlgItem failure is that in the tab order the sequence is to create the edit control, create its spin-control buddy, which then generates a message to the dialog, which intercepts it, but the dialog creation code has not yet created the IDC_DO_SOMETHING button. So GetDlgItem necessarily returns a NULL pointer.

The way I get around this is to create a member variable of my dialog class, 

BOOL initialized;

In the class constructor, I simply set

initialized = FALSE;

At the end of OnInitDialog, I do the following:

initialized = TRUE;
updateControls();

and I modify updateControls() to have, as its first executable code, the statement

if(!initialized)
     return;

There are occasional other places I need to perform this test. Often you find these empirically.

You can sometimes replace the test with the implicit test,

if(!::IsWindow(c_Button.m_hWnd))
    return;

for example, which tests the control variable c_Button to see if the window has been created. Since I usually have to introduce the initialized variable, I find this variant less common in my code.

Summary

Of the variety of techniques for control management in dialogs, a decade of Windows programming has convinced me that the core philosophy of this method is the only philosophy that can ever make sense. Whether it is done by explicit call, as I do, or from OnIdle, or distributed to ON_UPDATE_COMMAND_UI handlers, there must be no more than one ShowWindow and one EnableWindow per control. To do otherwise is simply insane. It produces code that is difficult to write, difficult-to-impossible to debug, and utterly impossible to maintain. That is unacceptable.

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.

“Weeks of coding can save you hours of planning.”