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.
Comments