Поддержка высокого DPI в WinForms — почему на 4K размывается или ломается макет
· Обновлено: · Го Комура · WinForms, Высокий DPI, Windows, .NET, C#, .NET Framework, Сопровождение унаследованного кода, UI, Техническая консультация
История изменений (2 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Переведены японские заголовки сносок. Утверждения статьи не менялись.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619939)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Го Комура (2026). Поддержка высокого DPI в WinForms — почему на 4K размывается или ломается макет. KomuraSoft LLC. https://comcomponent.com/ru/blog/winforms-high-dpi-guide/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.21619939
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.21619940
«После замены на новый ноутбук текст в рабочем приложении стал размытым и плохо читается», «подключили 4K-монитор — кнопки и подписи наехали друг на друга», «предпросмотр отчёта ломается только у заказчика, где масштаб 125%». В последние годы таких обращений резко больше именно в периоды замены парка ПК. Дисплеи с разрешением выше Full HD и масштабирование 125–200% стали нормой и на рабочих машинах, и WinForms-приложения, написанные в эпоху 96 DPI и масштаба 100%, уже не выглядят нормально «как есть». Среди обращений по сопровождению старых WinForms это, пожалуй, самая частая тема.
Сложность в том, что симптомы кажутся разрозненными: «размыто», «ломается макет», «часть элементов слишком мелкая». На деле причин несколько, и если понять, какой симптом какой причине соответствует, можно и починить, и решить, до какого уровня вообще стоит доводить исправление. В этой статье, опираясь на устройство DPI-масштабирования Windows и режимы осведомлённости о DPI, разбираем поддержку высокого DPI в WinForms (и на .NET, и на .NET Framework) с практической стороны: способы настройки, типичные ловушки и поэтапная стратегия.
Термины, которые встречаются в статье
До выводов зафиксируем только те слова, которые дальше повторяются. Для каждого указано, в какой главе оно разбирается — если смысл потерялся, вернитесь сюда.
| Термин | Смысл | Подробнее |
|---|---|---|
| DPI / масштабирование отображения | Число точек на дюйм. 96 DPI = 100%; 125% = 120 DPI, 150% = 144 DPI | гл. 2 |
| Виртуализация DPI | Мера совместимости: для приложения, которое не объявило поддержку DPI, ОС врёт, что экран — 96 DPI, даёт ему нарисовать картинку и растягивает результат как растр. Макет не ломается, зато всё расплывается1 | гл. 2 |
| Режим осведомлённости о DPI | Как приложение сообщает ОС, насколько само умеет работать с DPI. Четыре варианта: Unaware / System Aware / Per-Monitor / Per-Monitor V22 | гл. 3 |
| System Aware | Режим «подстроиться под DPI основного монитора на момент входа в систему». На основном мониторе чётко; при переносе на другой ОС растягивает и появляется размытие | гл. 3 |
| PMv2 | Сокращение от Per-Monitor V2. Режим, который следует за DPI каждого монитора. Неклиентскую область (заголовок окна и т. п.) масштабирует сама ОС2 | гл. 3 |
| Масштабирование GDI | Приложение остаётся Unaware, а текст и фигуры, нарисованные через GDI, ОС увеличивает на векторном уровне — улучшенный вариант растягивания растра. Код менять не нужно, можно только уменьшить размытие текста2 | гл. 3 и п. 4.3 |
| Манифест | XML, встраиваемый в исполняемый файл. Один из способов объявить режим осведомлённости о DPI | гл. 4 |
| AutoScaleMode | Другой механизм: собственное автоматическое масштабирование формы WinForms, не то же самое, что режим осведомлённости о DPI | гл. 5 |
1. Сначала вывод
- Размытие старого приложения в среде с высоким DPI — не баг, а мера совместимости ОС (виртуализация DPI). Windows заставляет приложение без поддержки DPI рисовать при 96 DPI, затем растягивает результат как растр: макет не ломается, но выглядит нечётко. 1
- Обратная картина — «чётко, но макет ломается» — значит, поддержку DPI объявили, а макет за ней не следует. У размытия и поломки макета причины противоположные, поэтому перед исправлением отличите одно от другого.
- Первый шаг — выяснить, в каком режиме осведомлённости о DPI (Unaware / System Aware / Per-Monitor V2) сейчас работает приложение. Проверьте, где это объявлено — в манифесте, app.config, вызовом API — или не объявлено вовсе. 3
- Способ настройки зависит от поколения. Для .NET (Core 3.1 – .NET 8) — файл проекта или
Application.SetHighDpiMode; для .NET Framework 4.7 и новее — app.config; для 4.6 и старше реалистичный предел — объявить System Aware через манифест. 45 - Конструктор обязательно открывайте на мониторе со 100% (96 DPI). Если открыть и сохранить форму в окружении 150%, перезапишется
AutoScaleDimensions— классический сбой, который ломает макет у всей команды. 6 - Полная поддержка (Per-Monitor V2) стоит дорого по трудозатратам. Реалистично идти в два этапа: сначала «чёткость только на основном мониторе через System Aware», затем полная поддержка PMv2 — так вложения соответствуют сроку жизни приложения и среде использования.
- Если своими силами доработать не получается или не успеваете, держите в уме временную меру на стороне пользователя: через свойства exe → вкладку «Совместимость» можно переопределить «параметры высокого DPI». С обращениями в поддержку становится проще. 2
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 21, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Почему возникает размытие — как устроена виртуализация DPI
Масштабирование отображения в Windows задаётся коэффициентом, где 96 DPI = 100%. 125% = 120 DPI, 150% = 144 DPI, 200% = 192 DPI. На 27-дюймовом 4K-мониторе (3840×2160) при 100% текст слишком мелкий, поэтому Windows по умолчанию предлагает около 150%. Распространение дисплеев высокого разрешения — это и есть распространение окружений, которые работают не на 96 DPI.
Проблема в том, что многие старые desktop-приложения написаны из допущения «экран всегда 96 DPI». Координаты, шрифты и значки прописаны напрямую в пикселях; если нарисовать их «как есть» при 150%, всё физически выглядит на две трети меньше задуманного. Поэтому Windows врёт приложениям без объявленной поддержки DPI: говорит «экран — 96 DPI», даёт нарисовать картинку из этого допущения и показывает её как растянутый растр. Это и есть виртуализация DPI (растягивание растра). 1
С учётом этого механизма симптомы и причины выстраиваются один к одному. Для первой диагностики пользуйтесь таблицей.
| Симптом | Причина | Состояние |
|---|---|---|
| Всё равномерно расплылось и размыто. Макет не ломается | Виртуализация DPI (ОС растягивает растр) | Без поддержки DPI (Unaware). Безопасно, но некрасиво |
| Текст чёткий, но элементы управления наезжают друг на друга или обрезаются | Поддержку DPI объявили, макет за ней не следует | Недоделанная поддержка DPI. Отсюда начинается доработка |
| На основном мониторе чётко, при переносе на дополнительный — размыто | System Aware (DPI зафиксирован по основному монитору на момент запуска) | Работает как задумано. Дальше решаем, нужен ли PMv2 |
| Размер текста нормальный, но значки и изображения мелкие или грубые | Растровые ресурсы остались рассчитаны на 96 DPI | Графические ресурсы не обновлены (гл. 6) |
| Ломается только конкретный экран или элемент управления | Макет с фиксированными координатами, собственная отрисовка, сторонние элементы управления | Цель точечной доработки (гл. 6) |
Важно: приложение, которое просто «размыто», на самом деле не сломано. Мера совместимости ОС работает как надо. Доработка как раз в том, чтобы отказаться от этой меры и заявить «масштабировать буду сам» (то есть поднять режим осведомлённости о DPI). В тот момент вся ответственность за макет переходит к вам. Обращения вида «на всякий случай включили PMv2 — стало ещё хуже» возникают именно по этой схеме.
2.1 Как воспроизвести симптомы и отличить их на своей машине
Скриншотов в статье нет. Разницу между «размыто» и «ломается макет» надёжнее увидеть, переключая настройки на живой машине, чем на статичном кадре. Следующими шагами отличите первую строку таблицы от второй. Этого хватает и с одним монитором.
- Переключите масштаб между 100% и 150%. Путь: Пуск > Параметры > Система > Дисплей > «Масштаб и разметка» > «Изменение размера текста, приложений и других элементов». 7 Разница лучше всего видна именно при сравнении 100% и 150%.
- Каждый раз перезапускайте приложение. Ни Unaware, ни System Aware не подхватывают новый масштаб на уже запущенном процессе. После смены настройки запускайте заново. Для System Aware при смене основного монитора нужны ещё выход из системы и повторный вход: System DPI фиксируется в момент входа.
- Сравните детали через экранную лупу. Клавиша с логотипом Windows +
+(плюс) запускает экранную лупу. 8 Поднимите увеличение примерно до 400% и смотрите, какой случай перед вами.- Контуры букв равномерно размыты, линии растекаются промежуточным цветом — это виртуализация DPI (первая строка таблицы). Текст, линии сетки и значки деградируют одинаково — вот критерий; это не проблема приложения.
- Текст чёткий, но кнопки и подписи наезжают друг на друга или обрезаются по краю — поддержку DPI уже объявили, макет не следует (вторая строка). Дальше это доработка из главы 6.
- Смотрите отдельно на значки. Текст чёткий, а значки ToolStrip мелкие как крупинки или грубые — четвёртая строка таблицы (растр под 96 DPI вставлен напрямую).
- Перетащите окно между мониторами (если их два и больше). Если размытие появляется только на мониторе с другим масштабом, это System Aware (третья строка) и штатное поведение.
Текущий режим почти всегда можно вывести из того, как это выглядит. Если и при 100%, и при 150% всё чётко, и после переноса между мониторами тоже чётко — это уровень PMv2. Если чётко только на основном мониторе — System Aware. Если в любом окружении всё равномерно расплывается — Unaware. Где именно объявлен режим (манифест, app.config, вызов API), проверяем в главе 4.
3. Режимы осведомлённости о DPI
Приложение сообщает ОС — в рамках процесса (точнее, начиная с Windows 10, в рамках окна верхнего уровня), — насколько само умеет работать с DPI. По сути режимов четыре. 12
| Режим | Появился в | DPI, который видит приложение | При переносе между мониторами / смене DPI | Практическая роль в WinForms |
|---|---|---|---|---|
| Unaware | — | Всегда 96 | ОС растягивает растр (размыто) | Значение по умолчанию, если ничего не объявлено. Размыто, но макет цел |
| System Aware | Vista | Зафиксирован по DPI основного монитора на момент входа | На любом мониторе, кроме основного, или после смены DPI растягивает ОС (размыто) | Работает во всех поколениях. Основной вариант прагматичного решения |
| Per-Monitor (V1) | 8.1 | DPI монитора, на котором окно | Только уведомление окну верхнего уровня; масштабирование целиком на приложении | Поддержки фреймворка нет, практической ценности мало. Не выбирать |
| Per-Monitor V2 | 10 (1703) | DPI монитора, на котором окно | Уведомляются и дочерние окна; неклиентскую область масштабирует ОС | Основной вариант полной поддержки. Доступен в .NET Framework 4.7+ / .NET |
Разница между Per-Monitor V1 и V2 на практике решающая. V1 — это голый Win32-механизм «уведомление пришло, дальше всё сами»; из WinForms его брать незачем. В V2 ОС сама масштабирует неклиентскую область (заголовок, полосы прокрутки, меню), и поддержка со стороны WinForms (об этом ниже) тоже рассчитана на V2. Если брать Per-Monitor — только V2. 2
Есть ещё масштабирование GDI (DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, Windows 10 1809+). Само приложение остаётся Unaware, но текст и фигуры, нарисованные через GDI, ОС дополнительно увеличивает на векторном уровне — улучшенный вариант растягивания растра. 2 Код не трогаете, а размытие текста может стать меньше, поэтому это стоит помнить как временную меру для приложений, которые нельзя доработать (п. 4.3).
Отдельно: начиная с Windows 10 1607 есть Mixed-Mode, в котором разные окна верхнего уровня одного процесса могут жить в разных режимах. Например: «основной экран — PMv2, а диалог, который пока не починили, остаётся Unaware и его растягивает ОС». Такая поэтапная миграция поддерживается на уровне Win32. 9 Из WinForms этим не очень удобно пользоваться напрямую, но сама идея «не обязательно чинить все экраны сразу» перекликается с поэтапной стратегией из главы 7.
4. Настройка — правильный способ для каждого поколения
Как объявлять режим осведомлённости о DPI, зависит от поколения приложения. Если перепутать, уйдёте в «настроил, а не работает». Поэтому разберём поколения по отдельности.
4.1 WinForms на .NET (Core 3.1 – .NET 8)
В .NET есть Application.SetHighDpiMode; шаблонный ApplicationConfiguration.Initialize() (.NET 6 и новее) вызывает его сам. По умолчанию — SystemAware. 4 Настраивать лучше в файле проекта: на это же значение опирается и конструктор Visual Studio.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<!-- SystemAware (по умолчанию) / PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
</PropertyGroup>
</Project>
Важно: ApplicationHighDpiMode в файле проекта и ApplicationConfiguration.Initialize() — механизмы из .NET 6. 4 В проектах на .NET Core 3.1 / .NET 5 это свойство ничего не делает, поэтому вызывайте Application.SetHighDpiMode прямо в коде, как ниже (то же самое, если у вас старый стиль Main без ApplicationConfiguration.Initialize()). В обоих случаях вызов нужен до создания хотя бы одного окна.
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
В .NET 6 и новее лучше масштабируются контейнерные элементы и дочерние окна MDI при PMv2; многие проблемы вплоть до .NET 5 — например, «перенесли окно с монитора 200% на монитор 100% и элементы сместились» — в значительной мере ушли. 4 Если всерьёз браться за PMv2, на новом .NET это заметно проще, чем на .NET Framework — так подсказывает опыт. Стоит ли уходить с .NET Framework, собрано в «Чек-лист перед миграцией с .NET Framework на .NET».
4.2 .NET Framework 4.7 и новее
В .NET Framework поддержка высокого DPI заметно усилилась в 4.7: лучше масштабируются элементы управления, появились события смены DPI (семейство DpiChanged), свойство DeviceDpi и другое. Но это opt-in: функции включаются, только если задать оба следующих пункта. 5
Сначала объявите совместимость с Windows 10 в манифесте (без этого сами функции высокого DPI из 4.7 не включаются).
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Объявление совместимости с Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
Затем объявите режим осведомлённости о DPI в секции System.Windows.Forms.ApplicationConfigurationSection файла app.config.
<configuration>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
<!-- Если отдельные экраны уже масштабируются сами, соответствующие функции можно отключить -->
<!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
</System.Windows.Forms.ApplicationConfigurationSection>
</configuration>
Заодно убедитесь, что в начале Main вызывается Application.EnableVisualStyles(). Здесь есть тонкость. Старый способ — записать в манифест <dpiAware> / <dpiAwareness> — перезаписывает настройку из app.config, поэтому официальная рекомендация для WinForms на 4.7 — не использовать их вместе. 5 Если поднимаете приложение, где ради System Aware в манифест когда-то прописали <dpiAware>true</dpiAware>, до 4.7+PMv2, начните с того, чтобы убрать это объявление из манифеста. «Прописали в app.config, а не работает» почти всегда из-за этого.
4.3 .NET Framework 4.6 и старше, смешанный код VB6/MFC
В WinForms 4.6 и старше нет кода фреймворка, который поддерживал бы PMv2. Принудительно объявить режим можно, но все поломки макета придётся разгребать вручную. Реалистичный предел для этого поколения — объявить System Aware через манифест. На уровне ОС манифест — рекомендуемый способ; если указать и <dpiAware> (начиная с Vista), и <dpiAwareness> (начиная с Windows 10 1607), и старые, и новые ОС прочитают это как задумано. 3
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
При System Aware автоматическое масштабирование WinForms (гл. 5) срабатывает один раз при запуске, подстраиваясь под DPI основного монитора, и на основном мониторе интерфейс выглядит чётко. Если у формы корректно задан AutoScaleMode.Font, часто одного объявления достаточно, чтобы вид стал приемлемым. Объявить режим во время выполнения можно и через API вроде SetProcessDpiAwareness, но после создания окна его уже не сменить, и официально рекомендуется именно манифест. 10 Тот же подход у приложений со смешанным кодом VB6 или MFC: режим всего процесса задаёт манифест exe (у самого MFC автоматического масштабирования нет, поэтому после объявления System Aware каждый экран нужно пройти глазами; о работе с кодом этого поколения см. также «Что такое MFC в Windows»).
Если доработать само приложение нельзя, есть временная мера на стороне пользователя. Через свойства exe можно переопределить DPI-масштабирование именно этого приложения. 2 Ниже только названия пунктов на экране, в том порядке, в каком их можно диктовать по телефону.
- Щёлкните правой кнопкой файл exe и выберите «Свойства». Если под рукой только ярлык, через «Расположение файла» перейдите в папку с самим exe.
- Откройте вкладку «Совместимость».
- Нажмите кнопку «Изменить параметры высокого DPI».
- В открывшемся диалоге включите флажок «Переопределить режим масштабирования в высоком разрешении».
- В раскрывающемся списке «Масштабирование выполняется» выберите поведение. «Система» принудительно включает виртуализацию DPI (макет не ломается, но расплывается); «Система (расширенная)» включает масштабирование GDI из главы 3 — чётким становится только текст, нарисованный через GDI. 2
- Закройте оба диалога кнопкой «ОК» и перезапустите приложение, чтобы сравнить вид.
Что лучше — «Система» или «Система (расширенная)» — зависит от приложения; пусть сравнят по шагам из п. 2.1. Универсального решения нет, но в инструкцию поддержки это стоит внести: когда у заказчика «нужно сегодня», по телефону можно провести. У свойств ярлыка этих пунктов нет, поэтому одно требование при консультации не пропускайте: открывать свойства именно exe, а не ярлыка.
5. AutoScaleMode и ловушка конструктора
Помимо режима осведомлённости о DPI у WinForms есть собственный механизм автоматического масштабирования формы. Если его не понимать, переход на System Aware не даст корректного масштаба.
Устроено так. На этапе проектирования каждая форма (ContainerControl) записывает основу масштабирования в AutoScaleMode, а в AutoScaleDimensions — базовое значение окружения, в котором форму спроектировали. Во время выполнения это сравнивается с текущим значением окружения (CurrentAutoScaleDimensions); если есть разница, дочерние элементы масштабируются все вместе. 11 То есть механизм закрывает «разницу между окружением проектирования и окружением выполнения», а базовое значение оказывается зашито в код (Designer.cs).
У AutoScaleMode по сути два осмысленных варианта.
| AutoScaleMode | Основа | Особенности |
|---|---|---|
| Font (рекомендуется, по умолчанию) | Размеры шрифта формы | Фактический размер системного шрифта меняется вместе с DPI, поэтому режим заодно следует за DPI. Подстраивается и под смену шрифта пользователем |
| Dpi | DPI экрана | Масштабируется строго пропорционально DPI. Для экранов, где главная графика |
| None | — | Автоматическое масштабирование выключено. Приложения с жёстко прописанными значениями под 96 DPI часто стоят именно так |
Если сомневаетесь — Font. Нюанс: в документации прямо сказано, что смешение разных режимов у базовой и производной формы даёт непредсказуемый результат. 11 В приложениях с наследованием форм сначала сведите все формы к одному режиму.
А теперь основная ловушка. AutoScaleDimensions записывает «значение из окружения проектирования», поэтому если открыть конструктор Visual Studio на мониторе 150% и сохранить форму, AutoScaleDimensions в Designer.cs перезапишется под 150% (для режима Font, например, 6F, 12F станет 9F, 18F). Координаты и размеры тоже сохранятся с коэффициентом 1.5. Когда участник команды с окружением 100% соберёт и запустит эту форму, всё отобразится уменьшенным, а в ревью всплывёт огромный diff, где сдвинулись координаты всех элементов. «Новому сотруднику выдали ноутбук с высоким DPI, он тронул одну форму — и макет во всём репозитории сломался» — классический сбой среди обращений по высокому DPI.
Этот сбой всегда виден в git diff до коммита. Если вы только открыли и сохранили одну форму, а в Designer.cs появилось такое, это доказательство, что форму открывали в окружении 150%.
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+ this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);
Следом идут ClientSize и Location / Size всех элементов — почти все значения ×1.5. В ревью смотрите именно на эту одну строку AutoScaleDimensions. Если она изменилась, координаты дальше можно не читать и сразу возвращать правку.
Защита по сути одна: открывать конструктор только при 100% (96 DPI). Сама Visual Studio — DPI-aware приложение, а конструктор WinForms (для .NET Framework) — нет. Поэтому на мониторе с высоким DPI появляется жёлтая информационная панель с предложением «перезапустить Visual Studio со масштабированием 100%». 6 Скриншотов нет, но последовательность такая.
- На мониторе с высоким DPI в Visual Studio откройте форму проекта
.NET Frameworkв конструкторе. - Вверху конструктора появляется жёлтая информационная панель с предложением перезапуска при 100%. В этом состоянии форму не сохраняйте. Иначе получите diff выше.
- По ссылке на панели перезапустите Visual Studio. VS целиком поднимается в режиме без учёта DPI: интерфейс самой студии слегка размыт — так и должно быть.
- В этом состоянии правьте и сохраняйте форму и в
git diffпроверяйте, чтоAutoScaleDimensionsне изменился. - Когда закончите, запустите Visual Studio как обычно и снимите режим без учёта DPI.
Из командной строки в это состояние можно войти командой devenv /noScale. В проектах на .NET 6 и новее, начиная с Visual Studio 2022 17.8, в файле проекта можно задать <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware> — тогда без учёта DPI работает только вкладка конструктора, без перезапуска всей VS (для проектов на .NET Framework параметр недоступен). 6 Если цель — .NET 6 и новее, сначала смотрите этот вариант: шаги 3–5 тогда не нужны.
Как проверку автоматизировать, хорошо работает простое правило: CI или pre-commit отклоняет diff, где AutoScaleDimensions в Designer.cs отличается от ожидаемого значения. Дешевле остановить сбой на входе в коммит, чем чинить его потом.
6. Типичные поломки макета и как их чинить
Когда вы поднимаете (или собираетесь поднять) режим осведомлённости о DPI, ломается обычно одно и то же.
| Что ломается | Причина | Как чинить |
|---|---|---|
| Наложение или обрезка элементов управления | Макет с фиксированными координатами и размерами | Заменить на Anchor/Dock, TableLayoutPanel / FlowLayoutPanel. Использовать AutoSize |
| Значки и изображения мелкие или грубые | Растр под 96 DPI вставлен напрямую | Подготовить изображения нескольких разрешений и переключать по DPI. По возможности уменьшать из одного крупного исходника |
| Собственная отрисовка (графики, чертежи, предпросмотр отчётов) | Пиксели прописаны напрямую в Graphics | Масштабировать координаты, толщину линий и шрифты от DeviceDpi |
| Строки DataGridView сжаты | RowHeight и подобное задано в пикселях | Использовать AutoSizeRowsMode либо задавать значения, масштабированные под DPI |
| Значки ToolStrip мелкие | Фиксированный размер 16×16 | Задавать ImageScalingSize в зависимости от DPI |
| Ломается только конкретный сторонний / ActiveX-элемент | Сам элемент не поддерживает DPI | Обновить до версии поставщика с поддержкой DPI. Если такой нет — это потолок достижимого |
Замена макетов — основная часть трудозатрат. Верно и обратное: экраны, изначально собранные на TableLayoutPanel и Dock, почти не требуют работы даже после повышения режима. Если новые экраны сразу собирать на панелях компоновки и закрепить это в правилах, будущий долг заметно меньше.
Базовый приём для собственной отрисовки — переводить значения, рассчитанные на 96 DPI, через Control.DeviceDpi (.NET Framework 4.7+ / .NET).
public partial class ChartPanel : Panel
{
// перевести проектное значение, заданное для 96 DPI, в текущий DPI
private int Scale(int value96) => value96 * DeviceDpi / 96;
protected override void OnPaint(PaintEventArgs e)
{
using var pen = new Pen(Color.Navy, Scale(2));
e.Graphics.DrawRectangle(pen,
Scale(16), Scale(16), Scale(320), Scale(120));
}
// в PMv2 при переносе между мониторами DPI меняется — запросить перерисовку
protected override void OnDpiChangedAfterParent(EventArgs e)
{
base.OnDpiChangedAfterParent(e);
Invalidate();
}
}
До System Aware DPI фиксируется при запуске, поэтому достаточно ввести Scale. При PMv2 DeviceDpi меняется при каждом переносе окна между мониторами, значит кэшированные шрифты, изображения и значения макета нужно пересобирать в обработчиках семейства DpiChanged. 5 «Сколько экранов с собственной отрисовкой» напрямую бьёт в оценку трудозатрат на PMv2.
Сторонние элементы управления и ActiveX/OCX — фактор, который задаёт потолок этой доработки. Если сам элемент не поддерживает DPI, хост этот экран до конца не починит, как ни старайся. Проверьте статус у поставщика; для ActiveX, обновления которого ждать не стоит, сначала решите судьбу по таблице в «Оставить, обернуть или заменить ActiveX/OCX», и только потом ставьте цель по высокому DPI.
7. Поэтапная стратегия — до какого уровня доводить
С учётом всего выше уровни доработки сводятся к трём. Технически идеально привести все приложения к полной поддержке PMv2, но при бюджете на доработку и сроке жизни приложений часто правильнее остановиться на компромиссе.
| (1) Ничего не делать (Unaware) | (2) Переход на System Aware | (3) Полная поддержка PMv2 | |
|---|---|---|---|
| Внешний вид | Размыто во всех окружениях (макет цел) | Чётко на основном мониторе. Размыто на дополнительных и после смены DPI | Чётко на всех мониторах |
| Основная работа | Нет | Объявление через манифест/конфиг + проверка AutoScaleMode + проверка отображения всех экранов | (2) + полная проверка макета + поддержка DPI для собственной отрисовки и графических ресурсов + обработка DpiChanged |
| Трудозатраты | Нулевые | От малых до средних (в основном проверка, пропорционально числу экранов) | Большие (в основном устранение поломок; потолок задают сторонние элементы) |
| Подходящие случаи | Через несколько лет выведут из эксплуатации, пользователи мирятся с видом | Корпоративные приложения с фиксированным рабочим столом / одним монитором. Как первый этап | Смешанное использование ноутбука и внешнего монитора. Долгоживущие ключевые приложения. Продукты, которые отдают заказчикам |
Оси решения три. Срок жизни приложения (сколько лет ещё будут пользоваться), среда использования (если у всех один и тот же фиксированный рабочий стол, System Aware по сути достаточно; если часто ноутбук плюс внешний монитор, размытие при каждом переносе окна в System Aware будет раздражать) и бюджет на доработку. Рекомендуемый порядок: сначала внедрить (2) как стандарт для всех приложений, а затем по объёму найденных поломок и ограничениям сторонних элементов решать, идти ли в (3) и на каких экранах. Этап (2) — в основном «объявление + проверка»: риск невелик, а ощущения пользователей заметно лучше.
Отдельно про тесты. Проблемы высокого DPI на машине разработчика часто не воспроизводятся, поэтому такие окружения стоит собрать специально. 1
- Подключить два монитора с разным масштабом (например, встроенный экран ноутбука 150% и внешний 100%) и гонять окно туда-сюда
- Поменять основной монитор и войти в систему заново перед запуском (System DPI фиксируется при входе, без повторного входа переключения не будет)
- Менять масштаб, пока приложение уже запущено
- Подключаться через удалённый рабочий стол с клиента с высоким DPI (RDP приносит DPI клиентской стороны, поэтому «на консоли сервера всё нормально, а по RDP ломается» — частый отчёт)
Схематично это выглядит так. Основа — один физический ПК и два монитора; недостающие масштабы добавляют виртуальные машины, проверку через RDP кладут сверху.
flowchart TB
APP["Проверяемое WinForms-приложение"]
subgraph PHYS["Один физический ПК — основа: два монитора"]
NB["Встроенный дисплей 150%<br/>сторона основного монитора"]
EXT["Внешний монитор 100%"]
NB <-->|"перетаскивать окно туда и обратно"| EXT
end
subgraph VMS["Виртуальные машины — недостающие масштабы"]
VM1["125%"]
VM2["200%"]
end
RDPC["Удалённый рабочий стол<br/>подключение с клиента с высоким DPI"]
APP --> NB
APP --> EXT
APP --> VM1
APP --> VM2
APP --> RDPC
Горизонтальная стрелка туда-сюда — операция, на которой разница System Aware и PMv2 видна лучше всего. Смену основного монитора делают внутри PHYS, меняя роли NB и EXT, и каждый раз входят в систему заново.
Если пришёл отчёт «ломается только при 125%», по опыту быстрее всего просто выставить этот масштаб и посмотреть живьём. Масштаб можно менять и в виртуальной машине, поэтому заранее иметь стенды 100 / 125 / 150 / 200% ускоряет разбор.
8. Итог
В среде с высоким DPI «размыто» — это мера совместимости ОС (виртуализация DPI), «ломается макет» — недоделанная поддержка DPI. Стоит запомнить это соответствие — и от симптома можно идти к причине и способу устранения. Настройка зависит от поколения: для .NET — свойство ApplicationHighDpiMode в файле проекта; для .NET Framework 4.7 и новее — app.config (не сочетать с dpiAware в манифесте); для 4.6 и старше — объявить System Aware через манифест, дальше обычно не стоит. Вместе с этим единый AutoScaleMode.Font и правило всегда открывать конструктор при 100% — неброские, но самые действенные меры против сбоев.
Дальше вопрос, до какого уровня доводить, — три этапа из главы 7. Сначала сделать основной монитор чётким через System Aware, затем, если среда и срок жизни приложения это окупают, перейти к PMv2: так мы на практике ведём реальные проекты доработки. Если пришло время пересмотреть сам UI-фреймворк (WPF изначально спроектирован с сильной поддержкой DPI), это тоже материал для решения — см. «Как выбрать между WinForms, WPF и WinUI». Если неясно, до какого уровня реалистично довести ваше приложение, можем помочь начать с ревизии состава экранов и элементов управления.
Похожие статьи
- Как выбрать между WinForms, WPF и WinUI — таблица решений
- Оставить, обернуть или заменить ActiveX/OCX — таблица решений
- Чек-лист перед миграцией с .NET Framework на .NET
- Что такое MFC в Windows — базовые сведения для сопровождения существующего кода
Смежные области консультаций
KomuraSoft LLC занимается поддержкой высокого DPI в старых приложениях на WinForms / MFC (обследование текущего состояния, выбор уровня поддержки, доработка макета), разбором причин сбоев отображения после замены ПК и консультациями по обновлению UI.
- Техническая консультация / ревью архитектуры
- Использование и миграция существующих активов
- Разработка Windows-приложений
- Контакты
Источники
-
Microsoft Learn, High DPI Desktop Application Development on Windows. О предпосылках DPI-масштабирования, поведении каждого режима осведомлённости о DPI, механизме растягивания растра у приложений без поддержки DPI и о том, на что смотреть при тестах в окружениях со смешанным DPI. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, DPI_AWARENESS_CONTEXT handle. Определения и поведение контекстов Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED (масштабирование GDI, Windows 10 1809+). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Setting the default DPI awareness for a process. Объявление через элементы dpiAware / dpiAwareness манифеста, приоритет между ними и почему объявление через API не рекомендуется. ↩ ↩2
-
Microsoft Learn, What’s new in Windows Forms .NET 6. Начальная загрузка через ApplicationConfiguration.Initialize, настройка ApplicationHighDpiMode в файле проекта (по умолчанию SystemAware) и улучшения масштабирования PerMonitorV2 в .NET 6. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, High DPI support - Windows Forms. Что усилили по высокому DPI в .NET Framework 4.7, секция System.Windows.Forms.ApplicationConfigurationSection в app.config (DpiAwareness=PerMonitorV2), почему объявление в манифесте не рекомендуется — оно перезаписывает app.config, — а также события семейства DpiChanged и DeviceDpi. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Fix DPI display issues in Windows Form Designer. Конструктор WinForms не поддерживает DPI; информационная панель на мониторах с высоким DPI предлагает перезапуск со 100% масштабированием; devenv /noScale и ForceDesignerDPIUnaware для .NET 6+. ↩ ↩2 ↩3
-
Поддержка Microsoft, Изменение разрешения экрана и макета в Windows. Как выбрать масштаб в «Пуск > Параметры > Система > Дисплей» через «Масштаб и разметка» → «Изменение размера текста, приложений и других элементов». ↩
-
Поддержка Microsoft, Использование экранной лупы для удобного просмотра элементов на экране. Клавиша с логотипом Windows + плюс запускает экранную лупу и увеличивает часть экрана. ↩
-
Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. Смешение режимов осведомлённости о DPI для отдельных окон верхнего уровня через SetThreadDpiAwarenessContext и DPI-aware API вроде GetDpiForWindow. ↩
-
Microsoft Learn, SetProcessDpiAwareness function. Настройка осведомлённости процесса о DPI по умолчанию через API, почему рекомендуется объявление через манифест и почему значение нельзя сменить после установки. ↩
-
Microsoft Learn, Automatic form scaling - Windows Forms. Поведение автоматического масштабирования через AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions и то, что смешение режимов Font и Dpi не поддерживается. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Высокий DPI в WPF — почему «к DPI должен быть готов», а на деле всё размывается
WPF по умолчанию System DPI Aware, но при переносе окна на монитор с другим DPI всё изображение размывается, а растр теряет резкость. Раз...
Аутентификация Entra ID в WinForms и WPF: MSAL.NET и брокер WAM
Как добавить вход через Entra ID в десктопное приложение WinForms или WPF: общедоступный клиент, регистрация приложения, AcquireTokenSile...
Дата, время и часовые пояса в бизнес-приложениях — ловушки DateTime, хранение в UTC и тесты
После переноса сервера время сдвигается на 9 часов. Разбираем, откуда берутся такие сбои: Kind у DateTime и неявные преобразования. Дальш...
Окончание драйверов принтера Windows ── как готовить печать форм и этикеток в бизнес-приложениях
Microsoft поэтапно прекращает сопровождение драйверов принтера v3/v4; с июля 2026 IPP class driver предпочтут. Что исчезает в Windows pro...
Значок в области уведомлений и toast в Windows-приложении: ошибки NotifyIcon и выбор AppNotification
Разбираем, как держать бизнес-приложение Windows в области уведомлений и показывать toast. Правильная работа с NotifyIcon, повторная реги...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Поток UI и таймеры
Поток UI WPF / WinForms, асинхронные операции, Dispatcher и проектирование таймеров.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Почему WinForms-приложение размывается на 4K-мониторе?
- Это не баг, а мера совместимости ОС — виртуализация DPI. Для приложений, которые не объявили поддержку DPI, Windows врёт: сообщает, что экран работает при 96 DPI, а затем растягивает как растр то, что приложение нарисовало исходя из этого допущения. Макет не ломается, но всё выглядит нечётко. Обратная ситуация — «чётко, но макет ломается» — значит, поддержку DPI объявили, а макет за ней не следует; причины здесь противоположные. Первый шаг перед исправлением — понять, с каким из двух симптомов вы имеете дело.
- Как настроить поддержку высокого DPI в WinForms?
- Способ зависит от поколения. В .NET 6 и новее — свойство ApplicationHighDpiMode в файле проекта (по умолчанию SystemAware); в .NET Core 3.1/.NET 5 — вызов Application.SetHighDpiMode до создания первого окна. В .NET Framework 4.7 и новее нужно объявить совместимость с Windows 10 в манифесте и задать DpiAwareness в app.config, но dpiAware в манифесте при этом официально не рекомендуется: он перезаписывает настройку из app.config. Для версий 4.6 и старше реалистичный предел — объявить System Aware через манифест.
- System Aware или Per-Monitor V2 — что выбрать?
- Имеет смысл идти в два этапа. Первый — переход на System Aware: масштабирование подстраивается под DPI основного монитора на момент запуска, и на нём интерфейс выглядит чётко. Основная работа — объявление и проверка, риск ошибиться невелик, и для корпоративных приложений с фиксированным рабочим столом этого по сути достаточно. Per-Monitor V2 даёт чёткость на всех мониторах даже при переносе окна, но требует полной проверки макета, доработки собственной отрисовки и графических ресурсов под DPI, а также обработки события DpiChanged. Объём работы большой, и если сторонние элементы управления этого не умеют, именно они задают потолок. Выбор зависит от срока жизни приложения, среды использования и бюджета на доработку.
- Почему после сохранения формы в конструкторе сломался макет?
- Если открыть и сохранить форму в конструкторе WinForms Visual Studio на мониторе с высоким DPI (например, 150%), значение AutoScaleDimensions в Designer.cs перезаписывается под 150%, а координаты и размеры сохраняются с коэффициентом 1.5. Когда после этого участник команды с окружением 100% собирает проект, всё отображается уменьшенным. Решение — всегда открывать конструктор при 100% (96 DPI): на мониторе с высоким DPI Visual Studio можно перезапустить со масштабированием 100% через жёлтую информационную панель. В проектах на .NET 6 и новее можно также использовать параметр ForceDesignerDPIUnaware. Полезно отклонять в CI или pre-commit неожиданные изменения AutoScaleDimensions.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.