Home Articles Books Downloads FAQs Tips

Q: Create an MDI child in the maximized state


Answer:

If you try to set the WindowState of an MDI child form to wsMaximized, you may notice that the form doesn't initially draw itself in the maximized state. The form first appears in a non-maximized state before resizing to the maximized state. To the user, this behavior may look odd.


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.



Copyright © 1997-2000 by Harold Howe.
All rights reserved.