-
Notifications
You must be signed in to change notification settings - Fork 10
.NET WinForms easy color scheme
Как-то ночью я писал код. Мониторы ярко светили, и, чтобы уменьшить яркость, я включил тёмную тему IDE. Стало лучше, но оставался второй монитор с открытым проводником Windows, поэтому пришлось включить тёмную тему Windows. Стало нормально, до того момента, как я не запустил WinForms программу, над которой в этот момент работал.
Какой она была до этого:
Что предстало моим глазам:
Так жизнь показала мне, почему не следует хардкодить цвета. Вместо этого нужно везде использовать...
SystemColors.ControlText
, а также системные кисти SystemBrushes.ActiveCaption
, и карандаши.
Как вы думаете, какой системный цвет соответствует розовой подсветке синтаксической ошибки на скрине выше? Никакой, в этом легко убедиться, взглянув в таблицу системных цветов Windows, источник stackoverflow. Розовых, оранжевых и зелёных цветов нет.
Чтобы в этих условиях избегать хардкода таких цветов, как розовый, придётся в буквальном смысле слова выкручиваться.
Чёрные иконки были хороши на белом / почти белом фоне. На чёрном же фоне потребуются светлые.
К счастью, System.Drawing.Bitmap
является изменяемым типом, поэтому ничто не мешает нам при изменении системных цветов перерисовать непосредственно используемый Bitmap.
Главное подобрать разумный алгоритм адаптации иконки под цветовую схему.
Казалось бы, пора поздравить себя с успехом и заняться другими делами, но не тут-то было. Мне нравится новый вид моего приложения, мысль о его потенциальном разнообразии греет душу, но чтобы пользоваться этим, нужно менять скин Windows. Это какой-то неправильный успех.
Нельзя ли, пользуясь проделанной ранее работой, реализовать настраиваемую цветовую схему отдельно для приложения? Хочется использовать SystemColors
и дальше, так как он
- поддерживается DesignTime редактором в Visual Studio
- используется самим .NET Framework и Win32 API
Таким образом, новые элементы, добавляемые в UI смогут учитывать цвета скина без дополнительных усилий при разработке.
Рассмотрим реализацию структуры System.Drawing.Color
в исходном коде .NET Framework
Структура System.Drawing.Color
реализует 2 способа поведения.
- Хранить байты цвета непосредственно
- Получать из таблицы
KnownColorsTable
по значению перечисленияKnownColor
KnownColorsTable
хранит значения системных цветов в приватном массиве colorTable
, содержимое которого по мере необходимости обновляется с использованием запросов Win32 API.
Нам достаточно подменять значения в colorTable
, чтобы для нашего приложения системные цвета имели такие значения, как нам нужно.
Вот как это было сделано.
На скриншоте ниже контекстное меню, которое сходу правильно отображается с изменёнными цветами:
Для быстрого редактирования цветовой схемы был реализован простой редактор:
Контрол для выбора цвета взят отсюда.
Некоторые элементы UI из .NET Framework будут игнорировать подмену SystemColors
, потому что они рисуют себя полностью за счёт запросов Win32 API. Характерный и распространённый пример - System.Windows.Forms.ScrollBar
. То же отчасти относится и к ListView
, TreeView
, ComboBox
и так далее.
В случае ScrollBar
ничего не остаётся, как сделать свой. Я нашёл хорошую реализацию, с небольшим количеством исправлений и улучшений получился вполне приличный вариант.
Большую часть недостатков отрисовки ComboBox можно решить переносом кода отображения в .NET framework, это можно сделать единообразно.
Но и в этих деталях есть дьявол - границу выпадающего списка, в том числе скролл выпадающего списка, так просто не победить.
По-видимому, он имеет собственный кеш цвета, потому что простой Invalidate()
не приводит к перерисовке с изменённым SystemColor
. Также не помогает переустановка BackColor
/ ForeColor
, потому что он умный и замечает, что как был SystemColors.WindowText
, так и остался.
Немного дополнительной настойчивости всё же заставит его склониться перед вашей волей.
Ценой умеренных усилий, около 4 человеко-дней, удалось решить следующие задачи:
- Весь UI отображается с учётом системных цветов Windows
- Реализован простой, но достаточный UI для редактирования цветовой схемы приложения.
- Подавляющая часть UI допускает применение цветов из редактируемой цветовой схемы приложения.
- Монохромные иконки приложения адаптируются к цветовой схеме и хорошо различимы при условии адекватности самой схемы.
- Как и раньше, можно редактировать цвета в DesignTime WinForms редакторе Visual Studio, не пришлось вводить отдельную сущность для цветовой схемы приложения отдельно от цветовой схемы Windows.
- В моём приложении теперь есть своя Тёмная цветовая схема 😎
В WPF того же можно добиться легче и с использованием штатных средств.
Хотя описанный выше подход дал неплохие результаты в моём приложении mtgdb.gui, качество результата оказалось не идеальным, например не удалось правильно окрасить границу меню выпадающего списка (ComboBox), собственные скроллы контролов, того же ComboBox, RichTextBox. В конечном итоге, пришлось написать с нуля собственные суррогаты ComboBox, Button, Checkbox, TabControl, и т.д. RichTextBox так и остался с нативным скроллом.
В проектах серьезного размера, над которыми работает множество разработчиков, следует идти другим путем, вероятно легче и лучше перейти на WPF.
И всё же, моя идея "цветовой схемы для бедняка" под .NET WinForms, получает продолжение в проекте GitExtensions. Потому что я им много пользуюсь, хочу темную тему, и не думаю, что он будет переписан на WPF, скажем к Новому Году.
- Introduction
- Search cards
- Search decks
- Edit collection / decks
- Analyze collection / deck
- Import collection / decks from…