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

в 16:08, , рубрики: Delphi, WinAPI, Недоделали, разочарование, разработка под windows

В Windows Вы не можете просто так сделать полупрозрачный элемент управления, Вы должны либо рисовать все контролы сами(Qt, FMX) либо использовать DrawThemeParentBackground, что неминуемо приводит к тормозам.
Регионы тут не помогут т.к. они не поддерживают частичную прозрачность.
Было бы удобно использовать окна со стилем WS_EX_LAYERED («Слоистые» окна поддерживающие альфа прозрачность отдельных пикселей), однако Windows поддерживает этот стиль только для окон верхнего уровня. Так было до Windows 8 в которой, не прошло и полвека, наконец-то стало возможно назначать этот стиль дочерним окнам.
Стиль WS_EX_LAYERED для дочерних окон в Windows 8 - 1
Что это дает? Первое что приходит в голову, это то, что композицией окон будет заниматься видео карта, что даст прирост производительности.

Под катом небольшое исследование этой возможности Windows 8.

Все примеры кода будут представлены на Delphi, однако подобный подход можно использовать и в других языках.

Попробуем создать такую полупрозрачную кнопку:
Стиль WS_EX_LAYERED для дочерних окон в Windows 8 - 2
Для упрощения мы не будем рисовать ее средствами 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 - 3
… И заметим, что к дочерним окнам почему-то не применился стиль WS_EX_LAYERED.

Как оказалось все дело в том, что эта фича не работает пока не указать в манифесте приложения поддержку Windows 8 (о чем не указано в явном виде на msdn):

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
    <application> 
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
    </application> 
  </compatibility>

Добавляем эти строчки в манифест, подключаем его к проекту, снова запускаем и жмем на кнопку:
Стиль WS_EX_LAYERED для дочерних окон в Windows 8 - 4
Ура, работает!

Однако не все так радужно…
Первое что бросается в глаза это то, что создаются такие окна очень медлительно, раз в 10 медленней обычных.
Второе, это то, что стало тормозить даже элементарное перетаскивание окна, не говоря уже о ресайзе, при котором можно наблюдать замечательные артефакты (извиняюсь за фото с экрана, но из-за специфичной работы Windows с такими окнами, на скриншотах артефактов не видно):
Стиль WS_EX_LAYERED для дочерних окон в Windows 8 - 5Стиль WS_EX_LAYERED для дочерних окон в Windows 8 - 6
Это надо видеть, не поленитесь и поиграйтесь с примером.

Если несколько раз жать на кнопку, то подвиснет не только приложение, но и… вся система, что не происходит при создании обычных окон.
Это приводит к выводу что, к сожалению, такая прекрасная возможность была реализована не качественно, и использование ее в реальных приложениях не возможно.

И еще один эксперимент, про то, как подвесить всю систему (только сначала сохраните все свои данные).
Добавьте еще одну кнопку на форму и сделайте в обработчике такой бесконечный цикл:

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

Автор: Error1024

Источник

Поделиться

* - обязательные к заполнению поля