Giving an MDI Application a Background Image


 

An MDI application actually consists of at least three types of windows:  the frame window, the client window, and the MDI child windows.  The frame window is the main form of the application which holds the main menu, the visible non-client areas, and any toolbars or other controls which you parent to it.  In addition, the frame form is the parent of the client window.  That is, the client window is a child of the frame window.  The client window, as the name suggests, occupies much of the client area of the frame form.  Think of it as a TPanel placed on a regular form, with the Align property set to alClient.  The MDI child windows are then parented to the client window.  That is, they are children of the client window.

You'll notice that if you drop any windowed controls on the frame form at design time, they will always appear in front of the MDI children at run time.  This is specifically due to the fact that the controls you place on the frame form are children of the frame window, while the MDI child windows are children of the client window. 

Why then won't a TImage appear when placed on the frame window?  The answer is that since a TImage control is not a windowed control (i.e., it descends from TGraphicControl instead of TWinControl), it uses the WM_PAINT messages of its Parent to draw itself to the device context of its Parent.  Now, since the the frame window has this client window occupying the entire (available) client area, the client window will cover up anything drawn to the client area of the frame window.  If this is confusing, think back to the TPanel example.  Imagine starting a new project with a regular form (fsNormal).  Next, drop a TImage on this form then, drop a TPanel on the form as well.  If you set the Align property of this TPanel to alClient, it will occupy the entire client area, thus covering up the Image.  This is exactly the same thing that's happening in an MDI application.

How then do you give a background image to an MDI application?  The solution is to draw directly to the client window.  You can get a handle to this client window by using the TForm::ClientHandle property.  The question now is when to draw to the client window.  Since a window uses the WM_ERASEBKGND message to fill its background with the brush specified when the class was originally registered, a good place to draw the image would be in response to this message.  In order to get at this message, you'll need to subclass the client window, then handle the message in the subclass procedure.

The following example tiles a TImage (Image1, placed on the frame form at design-time) to occupy the entire area of the client window (background). 
 

KEYWORDS: MDI, frame window, client window, ClientHandle.
SEE ALSO: http://msdn.microsoft.com/library/psdk/winui/mdocint_1zn7.htm


 

//---------------------------------------------------------------------------

// in header file...

    FARPROC NewClientWP;
    FARPROC OldClientWP;
    void __fastcall MDIClientWndProc(TMessage &Msg);
 

//---------------------------------------------------------------------------
 


 
 

//---------------------------------------------------------------------------

// in source file...

// subclass the client window
__fastcall TMainForm::TMainForm(TComponent *Owner)
    : TForm(Owner)
{
    NewClientWP = (FARPROC)MakeObjectInstance(MDIClientWndProc);
    OldClientWP = (FARPROC)SetWindowLong(ClientHandle, GWL_WNDPROC,
                                         (LONG)NewClientWP);
}
//---------------------------------------------------------------------------

// implement a helper funtion to tile the image
void TileBlt(HDC HDestDC, int DestWidth, int DestHeight, HDC HSourceDC,
    int SourceWidth, int SourceHeight)
{
    for (int y = 0; y < DestHeight; y = y + SourceHeight)
    {
        for (int x = 0; x < DestWidth; x = x + SourceWidth)
        {
            ::BitBlt(HDestDC, x, y,
                     SourceWidth, SourceHeight,
                     HSourceDC, 0, 0,
                     SRCCOPY);
        }
    }
}
//---------------------------------------------------------------------------

// in the subclass procedure, draw the image
void __fastcall TMainForm::MDIClientWndProc(TMessage &Msg)
{
    switch (Msg.Msg)
    {
       // draw the image to the device context of the
        // client window
         case WM_ERASEBKGND:
        {
            HDC Hdc = (HDC)Msg.WParam;
            SelectPalette(Hdc, Image1->Picture->Bitmap->Palette, true); 
            RealizePalette(Hdc);
            TileBlt(Hdc, Width, Height,
                    Image1->Canvas->Handle,
                    Image1->Picture->Bitmap->Width,
                    Image1->Picture->Bitmap->Height);

            Msg.Result = 0;
            return;
        }

        // handle the palette changes
        case WM_QUERYNEWPALETTE:
        {
            HDC Hdc = GetDC(ClientHandle);
            SelectPalette(Hdc, Image1->Picture->Bitmap->Palette, true);
            RealizePalette(Hdc);
            InvalidateRect(ClientHandle, NULL, true);
            ReleaseDC(ClientHandle, Hdc);

            Msg.Result = 0;
           return;
        }
        case WM_PALETTECHANGED:
        {
            if ((HWND)Msg.WParam != ClientHandle)
            {
                HDC Hdc = GetDC(ClientHandle);
                SelectPalette(Hdc, Image1->Picture->Bitmap->Palette, true);
                RealizePalette(Hdc);
                UpdateColors(Hdc);
                ReleaseDC(ClientHandle, Hdc);
            }

            Msg.Result = 0;
            return;
        }

        // refresh the image upon scrolling
        case WM_HSCROLL:
        case WM_VSCROLL:
        {
            InvalidateRect(ClientHandle, NULL, true);
            break;
        } 

        // un-subclass the client window
        case WM_DESTROY:
        {
            SetWindowLong(ClientHandle, GWL_WNDPROC, (LONG)OldClientWP);
            FreeObjectInstance(NewClientWP);
        }
    }

    // call the default window procedure
    Msg.Result = CallWindowProc(OldClientWP, ClientHandle, Msg.Msg,
                                Msg.WParam, Msg.LParam);
}
//---------------------------------------------------------------------------