пятница, декабря 07, 2012

Блеск и нищета Firemonkey²

Блеск

В Delphi XE3 у Firemonkey появилась возможность стилизовать неклиентскую область окон приложения. Для этого используются новые имена стилей: для Windows это windowborderstyle и toolwindowstyle, для Mac OS используется единственный стиль macborderstyle. Информации, как создать такие стили нет, во всяком случае я не нашел, поэтому всё что будет написано дальше является результатом исследования кода класса TWindowBorder и его потомков под каждую платформу. В общем, можно сказать, что создать собственный стиль неклиентской области дело довольно простое. Стиль подразумевает наличие некоторых предопределенных элементов (например ‘client’ отвечает за клиентскую область окна, а ‘title’ за его текстовый заголовок) из которых формируется внешний вид окна. Так же имя элемента стиля отвечает за обозначение действия какой-либо области окна. Например элементы c именем стиля ‘left’ и ‘right’ отвечают за изменение горизонтального размера окна, а ‘caption’ за его перемещение (элементов с одинаковым именем стиля может быть несколько, что в результате даст несколько зон отвечающих за одно и то же действие) То есть при клике на этих элементах будет выполняться соответствующее им действие. В общем, кто имел опыт работы с WinAPI, в части обработки оконных сообщений, наверняка провели параллель с такой вещью, как HITTEST, и оказались правы – механизм работает один-в-один. Это был блеск. Теперь о нищете.

Нищета

Воодушевившись открывшимися новыми возможностями я решил сделать стиль имитирующий окна Mac OS X Leon. За основу был взят незамысловатый скриншот:

pic14

и еще несколько изображений окон, где присутствовали кнопки в заголовке окна. Я не ставил себе целью сделать сразу полнофункциональный стиль, для начала хотелось добиться схожести с оригиналом. Вооружившись дизайнером стилей и в очередной раз убедившись, что пользоваться оным невозможно, было решено дизайнить стиль прямо на форме (ведь любой стиль это просто набор компонентов). Затем с формы стиль был скопирован в файл macosx.style и загружен в компонент класса TStyleBook, который был назначен форме. В редакторе стилей созданный стиль выглядел вот так:

styledesigner

Сглаживания на кнопке нет т.к. делал я все под Windows XP, а у Firemonkey на этой ОC сглаживание не работает (работало до Delphi XE2 Update 4 hotfix 1, но хотфиксом его сломали да так и не починили). Для создания эффекта тени пришлось генерировать картинку в GIMP т.к. стандартный эффект TShadowEffect такую тень отрисовать не способен (при значении свойства Softness равном единице тень уже имеет видимую глазу структуру, а при значениях больше единицы и вовсе превращается в клетчатое пятно). Но с картинкой возникает проблема. При изменении размера окна тень должна растягиваться без артефактов поэтому просто масштабировать картинку нельзя, её нужно делить на области: четыре имеющих постоянный размер (расположены в углах), две растягивающиеся горизонтально и две растягивающие вертикально. Итого получается восемь частей. Каждую такую часть можно было представить компонентом TImage в который был бы загружен файл соответствующей части изображения. Однако, в Delphi XE3, для поддержки растровых стилей Firemonkey, появился класс TSubImage способный отрисовывать указанную область изображения на которое он ссылается. Это удобнее, чем делать множество файлов. Но, этот класс не доступен в дизайнере, поэтому мне пришлось установить в IDE пакет регистрирующий его. В общем, после некоторого непредвиденного траха с пакетом, тень была готова. Заголовок с кнопкой и клиенсткую область сделать труда не составило. Хотя нет, подобрать градиент для кнопки было сложно т.к. элементы выделения (белые кружочки для изменения размеров) полностью перекрывали маленькую кнопку и изменяющийся градиент было не видно :) В результате схожесть не попиксельная, но довольно близка к оригиналу. Итак, стиль готов, первый запуск:

styledwindowblackshadow

Легкое недоумение. Пробую максимизировать окно, оно максимизируется так, как будто тень является клиентской областью т.е. я вижу не распахнутую на весь экран клиентскую область, а клиентскую область в окружении черной рамки. Хорошо, с максимизацией разберемся как нибудь позже, но почему тень не прозрачная… Я же помню, что у меню были прозрачные тени. Ищу в файле стилей стиль для меню (menuviewstyle). Вижу, что тень сделана с помощью эффекта TShadowEffect, но место под неё зарезервировано с помощью свойств Margin и Padding у элементов стиля. Попробовал и я у своей тени выставить отрицательный Padding, вот что из этого вышло:

styledwindow

Тень совсем пропала, а кроме этого на каждое изменение размеров окна генерируется четыре исключительных ситуации EZeroDivide. В общем трюк не сработал. Начал смотреть, что же там происходит под капотом модуля FMX.Platform.Win отвечающем за взаимодействие с платформой Windows. А там все печально. Оказалось, что по изображению полученному отрисовкой стиля формируется регион (регион в терминологии Win32) и назначается окну. Всё, прощай прозрачная тень, прощайте сглаженные уголки окна и да здравствуют зазубрины :( Но, как же, тогда отрисовывается тень у меню? А для меню используются композитные окна (см. TFrom.Transparency). В общем, тоска зеленая :(

2 комментария:

Vladislav Javadov комментирует...

Ого, ого, кто-то еще тоже разрабатывает на XP.

Kazantsev Alexey комментирует...

Только XP, только classic theme! Правда под VirtualBox :)