Многие разработчики Delphi сталкиваются с интересной проблемой при работе с формами без границ (borderless forms), которые должны отображать тень. В частности, в Delphi 11 на Windows 10 наблюдается следующее поведение:
Если сначала создать форму без границ с тенью (Borderless Shadowed form), а затем обычную форму (Normal form), тень отображается корректно
Если же сначала создать обычную форму, а затем форму без границ, тень не появляется
Это неожиданное поведение, так как логично предположить, что форма без границ должна всегда отображать тень, независимо от порядка создания форм.
Анализ кода
Рассмотрим исходный код, который демонстрирует проблему:
type
TFormPopup = class(TForm)
Panel1: TPanel;
ButtonClose: TButton;
procedure ButtonCloseClick(Sender: TObject);
private
FIsBorderlessPopup: Boolean;
protected
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AIsBorderlessPopup: Boolean); reintroduce;
end;
procedure TFormPopup.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
If FIsBorderlessPopup then
begin
Params.Style := WS_POPUP;
Params.WindowClass.style := Params.WindowClass.style or CS_DROPSHADOW;
Params.ExStyle := WS_EX_TOPMOST;
end;
end;
Причины проблемы
Как выяснилось в обсуждении, проблема связана с механизмом регистрации классов окон в Windows:
Delphi регистрирует классы окон как через RTL, так и через Windows API
При создании формы сначала вызывается CreateParams, затем Windows создает окно с указанными параметрами
Проблема возникает из-за того, что VCL не всегда корректно обрабатывает UnregisterClass при уничтожении формы
В результате, при повторном создании формы, Windows использует кешированные параметры класса окна
Решения проблемы
1. Использование RecreateWnd (не всегда работает)
Первоначально было предложено вызывать RecreateWnd после установки параметров:
procedure TFormPopup.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
If FIsBorderlessPopup then
begin
Params.Style := WS_POPUP;
Params.WindowClass.style := Params.WindowClass.style or CS_DROPSHADOW;
Params.ExStyle := WS_EX_TOPMOST;
end;
RecreateWnd; // Попытка решения
end;
Однако, как показала практика, это решение не всегда работает надежно, особенно в разных версиях Delphi.
2. Разделение на два разных класса форм (рекомендуемое решение)
Наиболее надежное решение - создать два разных класса форм: один для обычной формы, другой для формы без границ с тенью.
Теперь в основном окне можно создавать формы следующим образом:
procedure TFormMain.ButtonCreateBorderlessClick(Sender: TObject);
begin
TBorderlessPopupForm.Create(nil).Show;
end;
procedure TFormMain.ButtonCreateNormalClick(Sender: TObject);
begin
TNormalPopupForm.Create(nil).Show;
end;
3. Принудительная отмена регистрации класса (не рекомендуется)
Некоторые разработчики предлагают явно вызывать UnregisterClass, но это решение не рекомендуется из-за потенциальных проблем:
procedure TFormPopup.CreateParams(var Params: TCreateParams);
var
ClassRegistered: Boolean;
TempClass: TWndClass;
begin
inherited CreateParams(Params);
ClassRegistered := GetClassInfo(Params.WindowClass.hInstance,
Params.WinClassName, TempClass);
if ClassRegistered then
begin
Winapi.Windows.UnregisterClass(Params.WinClassName,
Params.WindowClass.hInstance);
end;
If FIsBorderlessPopup then
begin
Params.Style := WS_POPUP;
Params.WindowClass.style := Params.WindowClass.style or CS_DROPSHADOW;
Params.ExStyle := WS_EX_TOPMOST;
end;
end;
Почему это плохое решение:
1. UnregisterClass может завершиться ошибкой, если существуют окна этого класса
2. Может вызвать проблемы с программным обеспечением, которое кэширует классы окон (например, программы для доступности)
3. Нарушает стандартный поток работы VCL
Дополнительные рекомендации
Не используйте FreeAndNil для Self: Вместо FreeAndNil(FormPopup) в обработчике кнопки закрытия лучше использовать Release, который обеспечит корректное освобождение формы.
Создание форм без глобальных переменных: Вместо хранения ссылки на форму в глобальной переменной, лучше создавать форму следующим образом: pascal with TFormPopup.Create(True) do Show;
Заключение
Проблема с отображением тени у borderless форм в Delphi после создания обычных форм связана с особенностями регистрации классов окон в Windows и VCL. Наиболее надежное решение - разделить функциональность на два разных класса форм. Это обеспечит стабильное поведение приложения и корректное отображение теней независимо от порядка создания форм.
Разделение на разные классы - это не только решение конкретной проблемы с тенями, но и хорошая практика проектирования, которая делает код более понятным и поддерживаемым.
Проблема с некорректным отображением тени у borderless-форм в Delphi 11 при определенном порядке создания форм, связанная с особенностями регистрации классов окон в Windows и VCL.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.