TStyleHook, GDI+ и шрифт из ресурса

StyleHook GDI+

Наследники TStyleHook служат для перехвата событий и пользовательской отрисовки компонента. Что позволяет кардинально улучшить интерфейс без написания своих компонент и хакерских уловок. Планировал к стилям зайти издалека, но уж так как-то само собой сложилось, что буду описывать сейчас.

Небольшое вступление

Буквально несколько слов. Я против стремления писать для конкретного проекта универсальный код. Это отнимает много времени, неоправданно затягивает проект, и ни к чему хорошему не приводит. Невозможно учесть все, тем более нельзя предсказать, что появится в будущем. Более того, вернувшись к проекту через полгода-год, хочется все переписать. То, что казалось мега-крутым, на поверку оказалось ненужным.

Я также против неуемного желания использовать сторонние супер-компоненты. Есть конкретный проект, в нем есть конкретные проблемы. Вот их и надо решать. Если их можно решить без сторонних компонент, зачем они нужны?

В Delphi есть возможность подключить тему из предложенного списка. Тема хороша тем, что ее внешний вид не зависит от ОС и можно использовать хуки стилей. Плоха тем, что включаются небольшие, но тормоза. Однако, если не задаваться целью менять темы на ходу, а выбрать какую-то одну и творить в ее рамках, с помощью TStyleHook можно очень легко и очень быстро менять вид и поведение стандартных компонент.

Жаль, что при стандартной теме Windows такой фокус со TStyleHook не проходит.

Выбрал тему Light. Потому что минималистична, имеет малый размер и белая. Последнее очень субъективно, согласен.

Мерцание области отрисовки

После выбора темы, снова начались проблемы с «морганием» отрисовки при изменении размеров окна и масштабировании. Это как раз те самые тормоза, о которых шла речь выше, плюс начинают работать уже зарегистрированные стили. Ранее, проблема была решена так. Сейчас сделаем это через хук.

Для ScrollBox’а уже есть свой хук — TScrollBoxStyleHook. Сделаем наследника от него.

Иными словами, все устраивает, кроме отрисовки фона. Реализация переопределенного метода удивляет лаконичностью:

То есть не делать вообще ничего в том случае, если во множестве ControlState компонента присутствует csCustomPaint.

Так как это хук конкретно для ScrollBox’а, у которого по умолчанию csCustomPaint отсутствует, добавим его в пару интересующих нас ScrollBox’ов. Делаем это как обычно в событии OnCreate формы.

В предыдущей статье описано, что PaintBox’ы имеют размер либо больше, либо равный клиентской части родителя. Поэтому перерисовывать фон не требуется. Мерцание возникает именно из-за того, что вначале рисуется фон, потом поверх него битмап. DoubleBuffered тут не поможет.

Регистрируем стиль:

И наслаждаемся плавностью отрисовки. Хук конечно использован не по назначению, но между тем одной проблемой меньше.

Мы решаем конкретные проблемы конкретного проекта.

Шрифт из ресурса

В предыдущей статье описана проблема с кракозяблами под Windows 7. Связана проблема с бедностью юникода и шрифта на тот момент.

Вместо пиктограмм на BitBtn использую символы юникода и шрифт Segoe UI Symbol. Удобно менять цвет при наведении, сделать тень под символом и прочие эффекты. Шрифт был создан специально для Windows 7. На момент создания юникод еще не был столь красочным, как сейчас. Понятно, что нужно как-то подсунуть приложению шрифт от Windows 10, который весит 2.34 Мбайт вместо 504 Кбайт от семерки.

Будем делать через подключение ресурса со шрифтом. И, да, размер исполняемого файла увеличится на 2.34 метра. Повторю для одного упертого оппонента — это не тема, а шрифт увеличивает размер файла. Без ресурса — 2.97 Мб, с ресурсом 5.31 Мб. С темой размер становится 5.39 Мб. Тема дает мизерный прирост.

Сделать файл RES

Делать будем по старинке. Создадим каталог Fonts, поместим в него файл шрифта seguisym.ttf. Создадим текстовый файл с расширением .rc — Fonts.rc. В нем пишем такую строку:

Создадим файл tores.cmd в котором пишем:

Запускаем, получаем файл Fonts.res. Файл ресурсов готов.

Подключить и получить ресурс

В файле проекта под {$R *.res} пишем {$R Fonts.res}

Получаем ресурс следующей фразой в файле проекта:

Функция LoadResourceFontByName написана давно, но актуальности не потеряла:

AddFontMemResourceEx — подключает ресурс шрифта на время работы приложения. Больше никто в ОС этот шрифт не видит. По окончании работы надо вызвать RemoveFontMemResourceEx, но по утверждению Microsoft, ресурс освободится и так.

Все отлично работает, ресурс загружается, но по прежнему кракозяблы. Дело в том, что шрифт Segoe UI Symbol, уже есть, он системный, его даже удалить не получится. Попытка переименовать шрифт, скажем в Segoe UI Symbol 10, тоже не увенчается успехом. Переименовать typograph’ом получится, от кракозяблов избавиться — нет. GDI от семерки не вытягивает.

Поэтому, нам нужен хук, где будем рисовать шрифт с помощью GDI+.

GDI+ Нарисовать текст шрифтом из ресурса

Вообще, рисовать силами GDI+ замысловатые unicode символы из десятки можно даже в XP. Проверено. Надежно. Стильно. Для начала задействуем третий параметр AddFontInit : TGPFontAddInit функции LoadResourceFontByName.

В файле проекта пишем следующую процедуру:

Модифицируем вызов функции загрузки шрифта.

GPFontCollection — синглтон в модуле IP76.GDIPRoutines. Представлен таким образом:

Коллекция TGPPrivateFontCollection специализируется на хранении пользовательского перечня шрифтов. Что мы и делаем, загружая в нее шрифт из области памяти AMemory с помощью функции AddMemoryFont.

Рисуем текст функцией GDIPDrawTextEx из модуля IP76.GDIPRoutines, которая полностью повторяет список параметров процедуры DrawTextEx из модуля IP76.DrawUtils.

Полный набор функций имеет смысл посмотреть в исходниках. Здесь указаны только те, которые имеют отношение непосредственно к делу.

TStyleHook для BitBtn

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

Рис.1. Так выглядят кнопки темы по умолчанию

Что-то слишком уж черные и серых рамок разной степени серости в интерфейсе перебор. Пусть рамки будут только на полях отрисовки и элементах ввода данных. Рисовать символ в нормальном состоянии будем серым цветом, при наведении и фокусе цветом WebSteelBlue, рамка будет только у сфокусированного элемента.

Видим, что для BitBtn уже есть хук, и в принципе все устраивает, кроме отрисовки. Которую и переопределяем.

Регистрируем

И почти готово:

Рис.2. Так выглядят кнопки темы с хуком

Рамки у нас берутся из этой строки

Ну не перерисовывать же всю кнопку, когда почти все устраивает.

Поэтому делаем так. В событии OnResize формы, в цикл по компонентам дописываем следующее:

При всяком изменении размеров и как следствие географии расположения кнопок, пересоздаем для кнопки регион, который на пиксель меньше прямоугольника кнопки. Описатель региона помещается в свойство Tag кнопки.

И сразу проверяем на Windows 7.

Рис.3. Кнопки в Windows 7

Левая подсвеченная кнопка с рамкой — под фокусом. Правая подсвеченная кнопка — под мышью. И никаких кракозяблов.

Еще одной проблемой меньше. В другом проекте будет другая тема и другие украшения. Важен подход, принцип решения.

PageControl без рамки

В дополнение к теме региона. Центральная панель, на которой расположены элементы управления и настройки, на самом деле PageControl. У которого отключено свойство TabVisible для всех вкладок. С отключенными вкладками он выглядит так:

Рис.4. PageControl без вкладок

Снова серая рамка, да еще с утолщением справа-снизу. Поэтому применяем тот же метод, что и для BitBtn.

Переключение вкладок осуществляется в выпадающем списке сверху. Зачем вообще появился PageControl.

Подразумевается, что список эффектов будет расширяться, и вкладки перестанут быть удобным средством навигации. Их будет много и в несколько рядов. В этом случае логичней выбирать из выпадающего списка. Но с другой стороны, на PageControl удобно размещать и настраивать элементы в дизайне. Поэтому видимость с вкладок снимается уже в Real-Time, в обработчике OnCreate формы.

TStyleHook для Checkbox Switch

Нравится мне эта тема с переключателями вместо галочки Checkbox’а. И очень не нравится рамка из точек на сфокусированном checkbox’е.

Рис.5. Checkbox’ы, которые хочется заменить

Поэтому делаем хук для Сheckbox’а и заодно для RadioButton.

Переписываем всю отрисовку. Много кода, поэтому в спойлере.

Отрисовка Сheckbox/RadioButton

[свернуть]

Регистрируем:

На всякий случай рисовашка для переключателя. Конечно GDI+, нужны плавные закругления. Код также оформлен спойлером, чтобы не загромождать.

Функция рисования переключателя

[свернуть]

Получилось следующее:

Рис.6. Checkbox’ы переключатели

Симпатично получилось. Вместо точечного фрейма элемент под фокусом обрамляет прямоугольник в стиле темы.

Ну и наконец, TrackBar.

TStyleHook для TrackBar

А с ним-то что не так? Да все та же рамка из точек. Выпадает из стиля.

Мы со всем согласны, только если компонент под фокусом, рисуем рамку из точек, затирая тем самым уже существующий фрейм, и рисуем поверх светло-серый прямоугольник.

Баги

Дергается окно темы

К сожалению, это так. При растяжении по вертикали, окно припадочно меняет размер. Это не добавляет красоты проекту. Поэтому в свойстве StyleElements формы ставим везде FALSE. Рамка окна приобрела обычный для текущей ОС вид. Зато и поведение стало обычным — без рывков. Не самая большая жертва.

Моргает надпись «not supported»

Тоже неприятный момент. Эта надпись появляется в Windows 7, так как имеет на борту Direct2D версии 1.0 и никакого контекста, и никаких эффектов, в нем нет. На рис.3. эта надпись продемонстрирована. Моргает при изменении размеров окна. Лечится заменой TLabel на TStaticText.

Анонс. Direct2D компонент

Возник вопрос, можно ли использовать Direct2D при написании визуальных компонент. Можно. О чем свидетельствует рисунок ниже.

Рис.7. Direct2D TLabel в заголовке. Windows 8.1

Компонент рисует текст с контуром и тенью, о чем не так давно говорили. При наведении мышью окрашивается справа налево цветом контура. При «уходе» мыши с компонента, окрашивание «уходит» слева направо. На рис.7. мышь наведена на слово «Effects».

Хочется сказать, что использовать Direct2D для таких компонент — непростительная роскошь. Все это можно сделать силами GDI+. Смысл использовать Direct2D возникает для очень больших отрисовок. Графиков, диаграмм, инфографики. Когда между BeginDraw и EndDraw много действий, много рисовки.

О компонентах Direct2D, надеюсь, удастся поговорить попозже. Пока Light-реализацию конкретно для этого проекта можно подсмотреть в исходниках.

Ни Direct2D, ни компоненты, темой статьи не являются, поэтому будет развернуто в следующих статьях.


Друзья, спасибо за внимание!

Надеюсь, материал был полезен.

В следующей статье будут рассмотрены фото эффекты Direct2D, такие как: яркость, контрастность, резкость, инверсия, сепия, виньетка и другие.

Не пропустите, подписывайтесь на телегу.

Если есть вопросы, с удовольствием отвечу.


Скачать

Исходники (Delphi XE 7-10) 2.8 Мб

Исполняемый файл 2.6 Мб


5 4 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x
()
x