Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Borderless TForm with drop shadow

I have made a TForm derivative that acts like the drop down part of a combo, or a hint window, or a popup menu - a temporary thing. It has no caption - its BorderStyle is set to bsNone. The form is displayed non-modally using Show, having set its position.

To make it stand out, it needs a drop shadow around its border. However, a consequence of setting its border to bsNone is that the drop shadow disappears.

Various Google sources suggest variations of this:

procedure TdlgEditServiceTask.CreateParams(var Params: TCreateParams);
const
  CS_DROPSHADOW = $00020000;
begin
  inherited;
  { Enable drop shadow effect on Windows XP and later }
  if (Win32Platform = VER_PLATFORM_WIN32_NT) and
     ((Win32MajorVersion > 5) or
      ((Win32MajorVersion = 5) and (Win32MinorVersion >= 1))) then
    Params.WindowClass.Style := Params.WindowClass.Style or
             CS_DROPSHADOW;
end;

but it doesn't work - the shadow is not displayed (unless I also set a resizable border with WS_THICKFRAME set, which looks terrible). This is a popup window, not a child window, so I don't see why it should fail.

Suggestions please?

NB: this is a similar question to this question, which remains unanswered.

NB2: There is an obscure VCL component called TShadowWindow that looks like it will do the right thing, but turns out to be too crudely written to be practical.

Update: Following Andreas' comments below, I have investigated this further, and found some niceties.

Under Windows 7, I discovered that the shadow does not appear when the popup window if it is over another window from the same application.

Here is a simple Delphi app, which uses CreateParams on a popup window to request a shadow as described above.

Windows 7 with shadow only over desktop

See how the drop shadow appears where it extends beyond the main window?

But I want to use the borderless window as a popup over the main window. The drop shadow distinguishes the popup from the window underneath. All my description up above refers to this circumstance. Obviously some Windows mechanism is interfering here.

I have also tried the same application under Windows XP. Here is how it looks.

Same application under XP

This works correctly with shadow everywhere*. Gah!

So it would seem to be a Vista/W7 thing, as Andreas suggests.

(*An earlier version of this text and screendump suggested that no shadow appeared. However, this turned out to be because I had the Windows XP display option 'Shadows under menus' turned off. Duh.)

like image 900
willw Avatar asked Aug 20 '10 10:08

willw


3 Answers

Found it! Here is the proof:

alt text

As you can see, the drop shadow now shows properly over the form.

The problem was one of Z-order. It turns out that the shadow is itself a separate window maintained by Windows itself. In Windows 7, it seems to show the shadow underneath the main window. In order to get it to display properly, one needs to move it up.

A genius called Łukasz Płomiński explained this in a thread in the Embarcadero newsgroup. Here is his code to sort it out:

procedure TForm1.FixSysShadowOrder;

  function FindSysShadowOrderProc(WindowHandle: HWND; // handle to window
    Form: TForm1 // application-defined value, 32-bit
    ): BOOL; stdcall;
  var
    Buffer: array [0 .. 255] of char;
    Rect: TRect;
  begin
    Result := True;
    if IsWindowVisible(WindowHandle) then
    begin
      // this code  search for SysShadow window created for this window.
      GetClassName(WindowHandle, Buffer, 255);
      if 0 <> AnsiStrComp(Buffer, PChar('SysShadow')) then
        Exit;

      GetWindowRect(WindowHandle, Rect);
      if (Rect.Left <> Form.Left) or (Rect.Top <> Form.Top) then
        Exit;

      Form.FSysShadowHandle := WindowHandle;
      // stop enumeration
      Result := False;
    end;
  end;

begin
  if not(csDesigning in ComponentState) and
    ((GetClassLong(Handle, GCL_STYLE) and CS_DROPSHADOW) = CS_DROPSHADOW)
    and IsWindowVisible(Handle) then
  begin
    // for speed, proper SysShadow handle is cached
    if FSysShadowHandle = 0 then
      EnumThreadWindows(GetCurrentThreadID(), @FindSysShadowOrderProc,
        lParam(Self));
    // if SysShadow exists, change its z-order, and place it directly below this window
    if FSysShadowHandle <> 0 then
      SetWindowPos(FSysShadowHandle, Handle, 0, 0, 0, 0,
        SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOOWNERZORDER or SWP_NOSIZE);
  end;
end;

You have to work out when to call FixSysShadowOrder(), because Z orders change, and it won't stay right. Łukasz suggested calling it in an idle routine (for example when updating an Action), and on receipt of WM_WINDOWPOSCHANGED message.

like image 76
willw Avatar answered Nov 01 '22 21:11

willw


For making drop shadow to work we have to invoke SystemParametersInfo win32 API with SPI_SETDROPSHADOW parameter, to turn on the entire system's drop shadow effect, for more information, please refer to:

SystemParametersInfo

like image 25
Ravi shankar Avatar answered Nov 01 '22 23:11

Ravi shankar


"It works on my computer."


(High-res)

But it is quite funny, for I have a faint memory of making the same conclusion as you make, that is, that CS_DROPSHADOW does not work without the thick, resizable, frame. Are you still running Windows Vista, perhaps?

like image 3
Andreas Rejbrand Avatar answered Nov 01 '22 23:11

Andreas Rejbrand