Безграничный TForm с тенью

Я создал производную TForm, которая действует как выпадающая часть комбо, или окно подсказки, или всплывающее меню - временная вещь. У него нет заголовка - его BorderStyle имеет значение bsNone. Форма отображается немодально, используя Show, установив ее положение.

Чтобы выделить его, для его границы требуется тень. Тем не менее, следствием установки границы BsNone является то, что тень исчезает.

Различные источники Google предлагают варианты этого:

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;

но это не сработает - тень не отображается (если я также не устанавливаю изменяемую границу с набором WS_THICKFRAME, который выглядит ужасно). Это всплывающее окно, а не дочернее окно, поэтому я не понимаю, почему он должен потерпеть неудачу.

Предложения пожалуйста?

NB: это аналогичный вопрос в вопросе this, который остается без ответа.

NB2: Существует неясный компонент VCL, называемый TShadowWindow, который выглядит так, как будто он будет поступать правильно, но оказывается слишком грубо написано, чтобы быть практичным.

Обновление: Следуя комментариям Андреаса ниже, я исследовал это дальше и нашел некоторые тонкости.

В Windows 7 я обнаружил, что тень не появляется, когда всплывающее окно, если оно находится над другим окном из того же приложения.

Вот простое приложение Delphi, которое использует CreateParams во всплывающем окне для запроса тени, как описано выше.

Windows 7 with shadow only over desktop

Посмотрите, как появляется тень, где она выходит за пределы главного окна?

Но я хочу использовать окно без полей как всплывающее окно в главном окне. Тень отбрасывает всплывающее окно из окна под ним. Все мое описание выше относится к этому обстоятельству. Очевидно, что некоторые механизмы Windows вмешиваются здесь.

Я также попробовал одно и то же приложение под Windows XP. Вот как это выглядит.

Same application under XP

Это верно работает с тенью везде *. Г!

Таким образом, это похоже на Vista/W7, как предполагает Андреас.

(* Более ранняя версия этого текста и screendump показала, что тени не появилось. Однако это оказалось потому, что у меня была опция отображения Windows XP "Shadows under menus" выключена.)

Ответы

Ответ 1

Нашел! Вот доказательство:

alt text

Как вы можете видеть, тень теперь корректно отображается над формой.

Проблема была одна из Z-порядка. Оказывается, тень сама по себе является отдельным окном, поддерживаемым самой Windows. В Windows 7 кажется, что тень находится под основным окном. Чтобы отобразить его правильно, нужно его переместить.

Гений по имени Лукаш Пломиньский объяснил это в потоке в новостной группе Embarcadero. Вот его код, чтобы разобраться:
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;

Вам нужно работать, когда вызывать FixSysShadowOrder(), потому что Z-заказы меняются, и это не будет оставаться верным. Łukasz предложил вызвать его в режиме простоя (например, при обновлении действия) и при получении сообщения WM_WINDOWPOSCHANGED.

Ответ 2

"Это работает на моем компьютере."


(с высоким разрешением)

Но это довольно забавно, потому что у меня слабая память сделать тот же вывод, что и вы, то есть, что CS_DROPSHADOW не работает без толстой, изменяемой размера рамки. Возможно, вы все еще используете Windows Vista?

Ответ 3

Чтобы сделать тень для работы, мы должны вызывать API-интерфейс Win32 SystemParametersInfo с параметром SPI_SETDROPSHADOW, чтобы включить весь эффект отбрасывания всей системы, для получения дополнительной информации см.:

SystemParametersInfo