Background Information:
The problem is caused by VCL code that runs as a result of the AfterConstruction
method of TCustomForm. AfterConstruction contains an if statement
that sets the Visible property of the form to true.
void __fastcall TCustomForm::AfterConstruction()
{
...
...
if (FFormState.Contains(fsVisible))
Visible = true;
}
Visible is set based on the contents of the form's private FFormState set
variable. For MDI child forms, the VCL ensures that FFormState will always contain
fsVisible, irregardless of how you maniplulate the Visible property of the
form in code (the IDE will not let you set Visible to false for an MDI child).
The assignment to Visible eventually posts a user message to the form called
CM_VISIBLECHANGING. This message is handled by TWinControl, which
responds by calling the UpdateControlState function, and UpdateControlState
in turn calls UpdateShowing. TWinControl::UpdateShowing performs another user message called
CM_SHOWINGCHANGED. TCustomForm catches this user message, and at this
point, things begin to make sense.
void __fastcall TCustomForm::CMShowingChanged(TMessage &Message)
{
...
...
if (FormStyle == fsMDIChild)
{
// {Fake a size message to get MDI to behave }
if (FWindowState == wsMaximized)
{
SendMessage(Application.MainForm.ClientHandle,
WM_MDIRESTORE, Handle, 0);
ShowWindow(Handle, SW_SHOWMAXIMIZED);
}
...
}
The SendMessage statement turns out to be the culprit. Sending WM_MDIRESTORE to
the MDI client paints the MDI child in a non-maximized state on the screen. When the MDI
client receives the message, it sends other messages back to the MDI child form. These
messages include non-client painting messages and the WM_MDIACTIVATE message. The
painting messages combine to produce one annoying result: the MDI child is completely drawn
in the non-maximized state.
Solution:
Altering what the VCL does inside CMShowingChanged would be difficult, but we can
tell Windows not to repaint the MDI client area while TCustomForm is sending the
troublesome WM_MDIRESTORE message. We can force the MDI client window to suppress
all drawing until we give the OK. The API function LockWindowUpdate allows us to prevent the
MDI client from being painted until after the form's constructor has returned. Here is a
code example that suppresses painting of the MDI client until after the goofy
SendMessage's have taken their effect. As a result, the intermediate painting of
the MDI window in the non-maximized state never makes it to the screen.
// set WindowState to wsMaximized at design time.
LockWindowUpdate(Application->MainForm->ClientHandle);
TMDIChild *child = new TMDIChild(Application);
child->Caption = "Maximized MDI";
LockWindowUpdate(NULL);
Note: Only one window can be locked at a time. The HWND argument to
LockWindowUpdate determines which window we want to suppress. Passing NULL
to LockWindowUpdate deactivates the locking scheme.
Note: Notice that we lock the MDI client window and not the mainform of the program.
MDI child windows are children of the MDI client window, not the mainform. The
ClientHandle property of TForm returns the HWND of the MDI
client window. The ClientHandle property is only valid for forms that have
FormStyle set to fsMDIForm.
Note: If you create your MDI child windows from within the MDI parent, you can omit
the Application->Mainform prefix to ClientHandle. In other words, you
can call LockWindowUpdate like this:
LockWindowUpdate(ClientHandle);
Note: When the MDI form is initially drawn in the non-maximized state, the window is
not sized based on the Width and Height properties that you specified at
design time. The operating system sizes the window itself. If you step through
TCustomForm::CreateHandle, you will see that the size and position members of the
TMDICreateStruct structure are initialized to CW_USEDEFAULT. This tells
Windows to size the form however it sees fit. In fact, the Width, Height,
Left, and Top properties of the MDI child form are re-calculated
after the WM_MDICREATE message has been sent. The default values are leftovers from the
CreateParams function. You should be able to override CreateParams if you
need to alter the initial width, height, or position of the form.
Note: A couple of other functions play a role in why an MDI child is initially drawn
in the non-maximized state. The functions execute in response to the WM_MDIRESTORE
message that is sent from TCustomForm::CMShowingChanged. These functions include
WMMDIActivate, SetActive, and MergeMenu (all methods of
TCustomForm. The WM_MDIRESTORE message triggers a WM_MDIACTIVATE
message. The WMMDIActivate handler function calls SetActive.
SetActive calls MergeMenu, and MergeMenu does this:
// assume MergeState is true
int Size;
if(MergeState && (FormStyle == fsMDIChild) && (WindowState = wsMaximized)
{
// { Force MDI to put back the system menu of a maximized child }
Size = ClientWidth + (int(ClientHeight) << 16);
SendMessage(Handle, WM_SIZE, SIZE_RESTORED, Size);
SendMessage(Handle, WM_SIZE, SIZE_MAXIMIZED, Size);
}
When this code runs, the MDI child is still in the non-maximized state. The first
WM_SIZE message causes the MDI child to finish painting itself in the non-maximized
state. All of this code executes from within the very first SendMessage call in
CMShowingChanged.