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.