Search code examples
delphidelphi-10.2-tokyo

MDI-Form ignores StyleElements seClient


i have a problem with the VCL-Styles and MDI-Form. I want to use the VCL Styles, but i also want to draw the background (image) of my MainForm (MDI) by myself. This worked fine without VCL Styles, but when a Style is active the background image of the MainForm isn't shown.

Without Style

I checked out the StyleElements for the MainForm, but exclude the seClient is ignoerd and the background image isn't shown.

With Style

When i exclude the seClient and seBoarder the image is shown again. Obviously the Form Boarder lost the Style, which is also not that what i want.

Exclude seClient/seBoarder

The image is drawn at the Canvas in the ClientWndProc by the messages WM_ERASEBKGND, WM_VSCROLL and WM_HSCROLL. With the Styles, it looks like this events didn't raise. Is there any way the get the image at the form background with VCL Styles active?


Solution

  • The main point to realize here is that form styled fsMDIForm is a very special TWinControl that manages two window handles instead of one - TWinControl.Handle and TForm.ClientHandle. While the first handle is the form window itself the second is MDI client window (container-like for MDI child windows inside MDI parent).

    TFormStyleHook hooks both window procedures and introduces new method TFormStyleHook.MDIClientWndProc, which processes messages sent to MDI client. This method luckily virtual. It does some pre-processing of messages and then calls the original hooked procedure. The sad part is that it prevents calling the old procedure for WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCPAINT and WM_ERASEBKGND. Even worse is that on WM_ERASEBKGND it paints the client area background directly using StyleServices.

    Thanks to the above the subclassing of TFormStyleHook for MDI forms a PITA. I see multiple design flaws here:

    1. Missing virtual TFormStyleHook.PaintMDIClientBackground similar to TFormStyleHook.PaintBackground.
    2. No way to control/access over the original MDI client proc without hacking (hidden in private field FMDIPrevClientProc).
    3. Disability to control styling of MDI client window via TForm.StyleElements (as noted by OP).

    So what is the workaround? The easiest I can see is creating a custom style hook:

    type
      TMainFormStyleHook = class(TFormStyleHook)
      public
        procedure MDIClientWndProc(var Message: TMessage); override;
      end;
    
    { TMainFormStyleHook }
    
    procedure TMainFormStyleHook.MDIClientWndProc(var Message: TMessage);
    begin
      if Message.Msg = WM_ERASEBKGND then
      begin
        { TODO: Paint background to TWMEraseBkgnd(Message).DC }
        Message.Result := 1;
      end
      else
        inherited;
    end;
    

    and applying it to your MDI parent:

    type
      TMainForm = class(TForm)
      private
        class constructor Create;
        class destructor Destroy;
        { ... }
      end;
    
    { TMainForm }
    
    class constructor TMainForm.Create;
    begin
      TCustomStyleEngine.RegisterStyleHook(TMainForm, TMainFormStyleHook);
    end;
    
    class destructor TMainForm.Destroy;
    begin
      TCustomStyleEngine.UnRegisterStyleHook(TMainForm, TMainFormStyleHook);
    end;
    

    Note that you still need to keep painting background in MDI parent form in case the VCL styles are disabled, so it's worth creating method TMainForm.PaintMDICLientBackground(DC: HDC) and call it from both places.

    I would argue that this is a bug in VCL. How about you guys?