Pull to refresh

Стиль WS_EX_LAYERED для дочерних окон в Windows 8

Reading time 3 min
Views 17K
В 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):
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
    <application> 
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
    </application> 
  </compatibility>


Добавляем эти строчки в манифест, подключаем его к проекту, снова запускаем и жмем на кнопку:

Ура, работает!

Однако не все так радужно…
Первое что бросается в глаза это то, что создаются такие окна очень медлительно, раз в 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
Tags:
Hubs:
+14
Comments 52
Comments Comments 52

Articles