Стиль WS_EX_LAYERED для дочерних окон в Windows 8
В Windows Вы не можете просто так сделать полупрозрачный элемент управления, Вы должны либо рисовать все контролы сами (Qt, FMX) либо использовать DrawThemeParentBackground, что неминуемо приводит к тормозам.Регионы тут не помогут т.к. они не поддерживают частичную прозрачность.Было бы удобно использовать окна со стилем WS_EX_LAYERED («Слоистые» окна поддерживающие альфа прозрачность отдельных пикселей), однако Windows поддерживает этот стиль только для окон верхнего уровня. Так было до Windows 8 в которой, не прошло и полвека, наконец-то стало возможно назначать этот стиль дочерним окнам.Что это дает? Первое что приходит в голову, это то, что композицией окон будет заниматься видео карта, что даст прирост производительности.Под катом небольшое исследование этой возможности Windows 8.Все примеры кода будут представлены на Delphi, однако подобный подход можно использовать и в других языках.
Попробуем создать такую полупрозрачную кнопку: Для упрощения мы не будем рисовать ее средствами GDI+, а просто будем использовать готовый 32-х битный BitMap.
Создадим новое Vcl приложение, добавим на форму TImage и загрузим туда наш 32-х битный BitMap.Также добавим на форму кнопку, при нажатии которой мы будем создавать 100 «кнопок».
Наш Layered компонент мы сделаем наследником от TButton, в котором мы добавим конструктор, принимающий 32-х битный BitMap, с изображением нашей кнопки, и переопределим процедуру CreateWnd отвечающую за создание окна:
TWin8Control = class (TButton) private WinBitMap: TBitMap; public procedure CreateWnd; override; constructor Create (AOWner: TComponent; BitMap: TBitMap); end; // … constructor TWin8Control.Create (AOWner: TComponent; BitMap: TBitMap); begin inherited Create (AOwner); WinBitMap:= BitMap; end;
procedure TWin8Control.CreateWnd; var bf: TBlendFunction; BitmapSize: TSize; BitmapPos: Tpoint; begin inherited;
if Assigned (WinBitMap) then begin // убедимся в том что у нас Premultiplied битмап WinBitMap.AlphaFormat:= afPremultiplied;
bf.BlendOp:= AC_SRC_OVER; bf.BlendFlags:= 1; bf.AlphaFormat:= AC_SRC_ALPHA; bf.SourceConstantAlpha:= 255; // получаем размеры BitMap BitmapSize.cx:= WinBitMap.Width; BitmapSize.cy:= WinBitMap.Height; BitmapPos.X:= 0; BitmapPos.Y:= 0; // добавляем «слоистый» стиль окна SetWindowLong (Handle, GWL_EXSTYLE, GetWindowLong (Handle, GWL_EXSTYLE) or WS_EX_LAYERED); UpdateLayeredWindow ( Handle, 0, nil, @BitmapSize, WinBitMap.Canvas.Handle, @BitmapPos, 0, @bf, ULW_ALPHA ); end; end; Давайте теперь в обработчике нашей кнопки сделаем создание 100 дочерних Layered окон:
procedure TfrmMain.btnAdd100Click (Sender: TObject); var i: Integer; Win8Control: TWin8Control; begin for i:= 0 to 99 do begin Win8Control:= TWin8Control.Create (Self, Image.Picture.Bitmap); Win8Control.Parent:= Self; Win8Control.Top:= Random (400); Win8Control.Left:= Random (400); end; end; Запустим приложение и нажмем на кнопку: … И заметим, что к дочерним окнам почему-то не применился стиль WS_EX_LAYERED.
Как оказалось все дело в том, что эта фича не работает пока не указать в манифесте приложения поддержку Windows 8 (о чем не указано в явном виде на msdn):
Однако не все так радужно…Первое что бросается в глаза это то, что создаются такие окна очень медлительно, раз в 10 медленней обычных.Второе, это то, что стало тормозить даже элементарное перетаскивание окна, не говоря уже о ресайзе, при котором можно наблюдать замечательные артефакты (извиняюсь за фото с экрана, но из-за специфичной работы Windows с такими окнами, на скриншотах артефактов не видно): Это надо видеть, не поленитесь и поиграйтесь с примером.
Если несколько раз жать на кнопку, то подвиснет не только приложение, но и… вся система, что не происходит при создании обычных окон.Это приводит к выводу что, к сожалению, такая прекрасная возможность была реализована не качественно, и использование ее в реальных приложениях не возможно.
И еще один эксперимент, про то, как подвесить всю систему (только сначала сохраните все свои данные).Добавьте еще одну кнопку на форму и сделайте в обработчике такой бесконечный цикл:
procedure TfrmMain.LoopClick (Sender: TObject); var Win8Control: TWin8Control; begin while True do begin Win8Control:= TWin8Control.Create (Self, Image.Picture.Bitmap); Win8Control.Parent:= Self; Win8Control.Top:= Random (400); Win8Control.Left:= Random (400); end; end; После нажатия на кнопку Windows будет занята только созданием Layered окон и ничем больше, не будет реагировать даже на Ctrl+Alt+Del:)Проект на GitHub: https://github.com/errorcalc/LayeredTest