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.
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.
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.)
Found it! Here is the proof:
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.