Поддержка высокого DPI в WinForms — почему интерфейс размывается или разваливается на 4K-мониторах, и как это исправить
· Го Комура · WinForms, Высокий DPI, Windows, .NET, C#, .NET Framework, Поддержка устаревших систем, UI, Техническая консультация
«После замены на новый ноутбук текст в рабочем приложении стал размытым и трудночитаемым», «после подключения к 4K-монитору кнопки и подписи начали накладываться друг на друга», «предпросмотр отчёта ломается только в окружении клиента при масштабе 125%». В последние годы количество подобных обращений резко растёт именно в периоды замены компьютерного парка. Дисплеи с разрешением выше Full HD и масштабирование отображения 125–200% стали стандартом даже для рабочих ПК, и WinForms-приложения, созданные в эпоху 96 DPI и масштаба 100%, больше не могут отображаться комфортно «как есть». Пожалуй, сейчас это самая частая тема среди обращений по сопровождению старых WinForms-приложений.
Сложность в том, что симптомы выглядят разрозненно: «размывается», «ломается», «часть элементов слишком маленькая». На деле причины сводятся к нескольким категориям, и если понимать, какой симптом какой причине соответствует, можно и починить проблему, и решить, «до какого уровня стоит доводить исправление». В этой статье, опираясь на устройство DPI-масштабирования Windows и режимы DPI-осведомлённости, мы разберём поддержку высокого DPI в WinForms-приложениях (и на .NET, и на .NET Framework) с практической точки зрения — от способов настройки и типичных ловушек до поэтапной стратегии исправления.
1. Сначала вывод
- Размытие старого приложения в среде с высоким DPI — не баг, а защитная мера ОС (DPI-виртуализация). Windows заставляет DPI-неосведомлённое приложение рисовать при 96 DPI, а затем растягивает результат как битмап, поэтому раскладка не ломается, но становится размытой. 1
- Обратная ситуация — «чётко, но раскладка разваливается» — означает, что приложение объявило поддержку DPI, но раскладка за ней не поспевает. У размытия и поломки противоположные причины, поэтому перед исправлением определите, с каким из двух случаев вы имеете дело.
- Первый шаг — выяснить, в каком режиме DPI-осведомлённости (Unaware / System Aware / Per-Monitor V2) сейчас работает ваше приложение. Проверьте, где это объявлено — в манифесте, app.config, через вызов API — или не объявлено вовсе. 2
- Способ настройки различается по поколениям. Для .NET (Core 3.1 – .NET 8) — файл проекта или
Application.SetHighDpiMode; для .NET Framework 4.7 и новее — app.config; для версий 4.6 и старее реалистичный предел — объявление System Aware через манифест. 34 - Дизайнер обязательно открывайте на мониторе со 100% (96 DPI). Если открыть и сохранить форму в окружении со 150%,
AutoScaleDimensionsперезаписывается, и это классический инцидент, ломающий раскладку у всей команды. 5 - Полная поддержка (Per-Monitor V2) требует значительных трудозатрат. Реалистичный подход — двухэтапная схема: сначала «чёткость только на основном мониторе через System Aware», затем полная поддержка PMv2 — так инвестиции соответствуют сроку жизни приложения и среде использования.
- Также стоит знать о временной мере на стороне пользователя, если своими силами доработать приложение не получается или не успеваете: «параметры высокого DPI», которые можно переопределить через свойства exe-файла → вкладку «Совместимость» — это заметно облегчает работу с обращениями в поддержку. 6
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», а затем показывает результат их отрисовки — выполненной в предположении 96 DPI — как растянутый битмап. Это и есть DPI-виртуализация (растяжение битмапа). 1
С учётом этого механизма симптомы и причины выстраиваются в чёткое соответствие. Используйте эту таблицу для первичной диагностики.
| Симптом | Причина | Состояние |
|---|---|---|
| Всё равномерно размыто, но раскладка не ломается | DPI-виртуализация (ОС растягивает битмап) | Приложение не осведомлено о DPI (Unaware). Безопасно, но некрасиво |
| Текст чёткий, но элементы управления перекрываются или обрезаются | Поддержка DPI объявлена, но раскладка за ней не поспевает | Недоделанная поддержка DPI. Это цель для доработки |
| На основном мониторе чётко, при переносе на дополнительный — размыто | System Aware (DPI зафиксирован по основному монитору на момент запуска) | Работает как задумано. Решаем, переходить ли на PMv2 |
| Размер текста нормальный, но значки/изображения мелкие или грубые | Битмап-ресурсы остались рассчитаны на 96 DPI | Графические ресурсы не обновлены (раздел 6) |
| Ломается только конкретный экран или элемент управления | Раскладка с фиксированными координатами, собственная отрисовка, сторонние элементы управления | Цель для индивидуальной доработки (раздел 6) |
Важно понимать: приложение, которое просто «размыто», на самом деле не сломано — защитная мера ОС работает корректно. Доработка означает отключение этой защитной меры и заявление «я буду масштабировать сам» (то есть повышение режима DPI-осведомлённости), а значит, в момент этого объявления вся ответственность за раскладку переходит к вам. Обращения вида «попробовали переключиться на PMv2 как быстрое решение, и стало ещё хуже» возникают именно по этой схеме.
3. Разбираем режимы DPI-осведомлённости
Приложение сообщает ОС — в рамках процесса (точнее, начиная с Windows 10, в рамках каждого окна верхнего уровня), — насколько само способно обрабатывать DPI. Фактически существует четыре режима. 16
| Режим | Появился в | 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. 6
Есть ещё режим масштабирования GDI (DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, Windows 10 1809+). Само приложение остаётся Unaware, но текст и фигуры, отрисованные через GDI, дополнительно увеличивает ОС на векторном уровне — это улучшенная версия растяжения битмапа. 6 Поскольку так можно снизить размытие текста, вообще не меняя код, стоит держать этот вариант в уме как временную меру для приложений, которые нельзя доработать (раздел 4.3).
Отдельно стоит упомянуть, что начиная с Windows 10 1607 существует Mixed-Mode, позволяющий разным окнам верхнего уровня в рамках одного процесса работать в разных режимах одновременно — например, «основной экран работает как PMv2, а диалог, который пока не удалось исправить, остаётся Unaware и растягивается ОС». Такая поэтапная миграция поддерживается на уровне Win32. 7 Из WinForms им не так просто воспользоваться напрямую, но сама философия «не обязательно чинить все экраны сразу» напрямую перекликается с поэтапной стратегией из раздела 7.
4. Настройка по поколениям — как сделать правильно
Способ объявления режима DPI-осведомлённости зависит от поколения вашего приложения. Ошибётесь здесь — потратите время на «настроил, а не работает», поэтому разберём по поколениям отдельно.
4.1 WinForms на .NET (Core 3.1 – .NET 8)
В .NET есть готовый Application.SetHighDpiMode, а сгенерированный шаблоном ApplicationConfiguration.Initialize() (.NET 6 и новее) вызывает его автоматически. По умолчанию используется SystemAware. 3 Настраивать рекомендуется через файл проекта — на это же значение опирается и дизайнер 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. 3 В проектах на .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% элементы управления смещаются», — во многом устранены. 3 Если вы намерены всерьёз заняться поддержкой PMv2, .NET явно даётся легче, чем .NET Framework — так подсказывает опыт. Материалы о том, стоит ли переходить с .NET Framework, собраны в статье «Чек-лист перед миграцией с .NET Framework на .NET».
4.2 .NET Framework 4.7 и новее
В .NET Framework поддержка высокого DPI была значительно усилена в версии 4.7 — улучшено масштабирование элементов управления, добавлены события смены DPI (семейство DpiChanged), свойство DeviceDpi и другое. Однако это опциональная функция, включаемая только при одновременной настройке следующих двух пунктов. 4
Сначала нужно объявить совместимость с 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 — не использовать их одновременно. 4 Если вы обновляете приложение, где ранее для 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), это будет корректно интерпретировано и на старых, и на новых версиях ОС. 2
<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, но изменить режим после создания окна нельзя, и официально рекомендуется именно объявление через манифест. 8 Тот же подход применим к приложениям со смешанным кодом VB6 или MFC — режим для всего процесса определяется манифестом exe-файла (сам MFC не поддерживает автоматическое масштабирование, поэтому после объявления System Aware обязательна проверка каждого экрана вручную; о работе с активами этого поколения см. также статью «Что такое MFC в Windows»).
Также расскажем о временной мере на стороне пользователя на случай, если доработка своими силами невозможна. Через правый клик по exe-файлу → «Свойства» → вкладка «Совместимость» → «Изменение высокого качества масштабирования при высоком разрешении экрана» можно переопределить поведение масштабирования DPI для этого приложения. Если задать значение «Приложение» (в оригинале — «System»), принудительно включается DPI-виртуализация (раскладка не ломается, но размывается); значение «Система (расширенная)» применяет упомянутое в разделе 3 масштабирование GDI, при котором чётким становится только текст, отрисованный через GDI. 6 Это не панацея, но такой способ стоит включить в инструкцию для службы поддержки — им можно подсказать клиенту по телефону, когда нужно «исправить прямо сейчас».
5. AutoScaleMode и ловушка дизайнера
Помимо режима DPI-осведомлённости, у WinForms есть собственный механизм автоматического масштабирования на уровне формы. Без понимания этого механизма переход на System Aware не приведёт к корректному масштабированию.
Устроено это так. Во время проектирования каждая форма (ContainerControl) записывает основу масштабирования в AutoScaleMode, а в AutoScaleDimensions — базовое значение из окружения, в котором она была спроектирована. Во время выполнения это значение сравнивается с текущим значением окружения (CurrentAutoScaleDimensions), и при наличии разницы все дочерние элементы управления масштабируются соответственно. 9 Иными словами, это механизм, компенсирующий «разницу между окружением проектирования и окружением выполнения», а базовое значение оказывается зашито в код (Designer.cs).
Фактически у AutoScaleMode два осмысленных варианта.
| AutoScaleMode | Основа | Особенности |
|---|---|---|
| Font (рекомендуется, по умолчанию) | Размеры шрифта формы | Поскольку фактический размер системного шрифта меняется вместе с DPI, этот режим заодно отслеживает DPI. Также подстраивается под изменения шрифта, заданные пользователем |
| Dpi | DPI экрана | Масштабируется строго пропорционально DPI. Подходит для экранов, ориентированных на графику |
| None | — | Автоматическое масштабирование отключено. Приложения с жёстко заданными значениями под 96 DPI часто настроены именно так |
Если сомневаетесь — выбирайте Font. Важный нюанс: официально задокументировано, что смешение разных режимов у базовой и производной формы приводит к непредсказуемым результатам. 9 В приложениях, использующих наследование форм, сначала стоит провести ревизию и привести все формы к единому режиму.
А теперь — основная ловушка. Поскольку AutoScaleDimensions — это механизм, записывающий «значение из окружения проектирования», если открыть дизайнер Visual Studio на мониторе со 150% и сохранить форму, значение AutoScaleDimensions в Designer.cs перезаписывается под 150% (для режима Font, например, 6F, 12F становится 9F, 18F). Координаты и размеры тоже сохраняются с коэффициентом 1.5. Когда участник команды с окружением на 100% собирает и запускает эту форму, всё отображается уменьшенным, а в ревью изменений виден огромный diff, где изменились координаты всех элементов управления. «Новому сотруднику выдали ноутбук с высоким DPI, он тронул одну форму — и раскладка во всём репозитории сломалась» — классический инцидент среди обращений по высокому DPI.
Решение в принципе одно — открывать дизайнер только при 100% (96 DPI). Сама Visual Studio — DPI-осведомлённое приложение, но дизайнер WinForms (для .NET Framework) — нет, поэтому при открытии на мониторе с высоким DPI появляется жёлтая информационная панель с предложением «перезапустить Visual Studio с масштабированием 100%». 5 Сделайте правилом команды: следовать этой подсказке, перезапускать в режиме без учёта DPI, редактировать форму, а по завершении возвращаться в обычный режим. Запустить в этом режиме можно и из командной строки через devenv /noScale. Отметим также, что в проектах на .NET 6 и новее, начиная с Visual Studio 2022 17.8, в файле проекта можно задать <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware> — тогда без учёта DPI будет работать только вкладка дизайнера, без перезапуска всей VS (для проектов на .NET Framework этот параметр недоступен). 5
Как способ автоматизировать проверку хорошо и просто работает следующее: настроить CI или pre-commit-хук так, чтобы он отклонял изменения, где 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, требуют минимум усилий даже после повышения режима DPI-осведомлённости. Если сделать правилом собирать новые экраны с самого начала на панелях раскладки, будущий технический долг заметно снизится.
Базовый подход к масштабированию собственной отрисовки — конвертировать значения, рассчитанные для 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. 4 «Сколько экранов с собственной отрисовкой в этом приложении» напрямую влияет на оценку трудозатрат для поддержки 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 ломается» — частый отчёт)
Если поступил отчёт, что «ломается только при 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 — что это такое и как с этим работать сегодня
Смежные области консультаций
Komura Software Co., Ltd. занимается поддержкой высокого DPI для старых приложений на WinForms / MFC (оценка текущего состояния, определение необходимого уровня поддержки, доработка раскладки), расследованием причин проблем отображения после замены компьютеров, а также консультациями по модернизации UI.
- Техническая консультация / ревью архитектуры
- Использование и миграция существующих активов
- Разработка Windows-приложений
- Контакты
Источники
-
Microsoft Learn, High DPI Desktop Application Development on Windows. О предпосылках DPI-масштабирования, поведении каждого режима DPI-осведомлённости, механизме растяжения битмапа у DPI-неосведомлённых приложений и особенностях тестирования в окружениях со смешанным DPI. ↩ ↩2 ↩3 ↩4
-
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 Learn, DPI_AWARENESS_CONTEXT handle. Об определениях и поведении контекстов Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED (масштабирование GDI, Windows 10 1809+). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. О смешении режимов DPI-осведомлённости для отдельных окон верхнего уровня через SetThreadDpiAwarenessContext и о DPI-осведомлённых API вроде GetDpiForWindow. ↩
-
Microsoft Learn, SetProcessDpiAwareness function. О настройке DPI-осведомлённости процесса по умолчанию через API, о том, почему рекомендуется объявление через манифест, и о невозможности изменить значение после установки. ↩
-
Microsoft Learn, Automatic form scaling - Windows Forms. О поведении автоматического масштабирования через AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions и о том, что смешение режимов Font и Dpi не поддерживается. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Высокий DPI в WPF — почему всё «должно быть само в порядке», а на деле размывается и плывёт, и что с этим делать
WPF по умолчанию System DPI Aware, но при переносе окна на монитор с другим DPI всё изображение размывается, а растровые картинки становя...
Встраиваем аутентификацию Entra ID в приложения WinForms/WPF — практическая архитектура на MSAL.NET и брокере WAM
Разбираем порядок встраивания аутентификации Entra ID в десктопные приложения WinForms/WPF: концепцию публичного клиента, регистрацию при...
Дата, время и часовые пояса в бизнес-приложениях — от ловушек DateTime до принципа хранения в UTC и проектирования тестов
Перенос сервера сдвигает время на 9 часов — разбираем причины подобных сбоев начиная со свойства Kind у DateTime и неявных преобразований...
Почему процесс EXCEL.EXE остаётся висеть после работы с Excel через COM в C# — паттерны освобождения ссылок и когда стоит отказаться от COM
Разбираем, почему при автоматизации Excel из C# через Microsoft.Office.Interop.Excel остаётся висеть процесс EXCEL.EXE, опираясь на подсч...
Значки в области уведомлений и всплывающие (toast) уведомления в Windows-приложениях — подводные камни NotifyIcon и выбор правильного AppNotification
Практическое руководство о том, как удерживать бизнес-приложение Windows в области уведомлений (system tray) и оповещать пользователя с п...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы 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, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.
Публичные ссылки