Поддержка высокого 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». Если сомневаетесь, до какого уровня реалистично довести доработку вашего приложения, мы можем помочь начать с ревизии состава экранов и элементов управления.

Похожие статьи

Смежные области консультаций

Komura Software Co., Ltd. занимается поддержкой высокого DPI для старых приложений на WinForms / MFC (оценка текущего состояния, определение необходимого уровня поддержки, доработка раскладки), расследованием причин проблем отображения после замены компьютеров, а также консультациями по модернизации UI.

Источники

  1. Microsoft Learn, High DPI Desktop Application Development on Windows. О предпосылках DPI-масштабирования, поведении каждого режима DPI-осведомлённости, механизме растяжения битмапа у DPI-неосведомлённых приложений и особенностях тестирования в окружениях со смешанным DPI.  2 3 4

  2. Microsoft Learn, Setting the default DPI awareness for a process. О способе объявления через элементы dpiAware / dpiAwareness манифеста, их приоритете относительно друг друга и о том, почему объявление через API не рекомендуется.  2

  3. Microsoft Learn, What’s new in Windows Forms .NET 6. О начальной загрузке через ApplicationConfiguration.Initialize, настройке ApplicationHighDpiMode в файле проекта (по умолчанию SystemAware) и улучшениях масштабирования PerMonitorV2 в .NET 6.  2 3 4

  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

  5. Microsoft Learn, Fix DPI display issues in Windows Form Designer. О том, что дизайнер WinForms не поддерживает DPI, об информационной панели на мониторах с высоким DPI, предлагающей перезапуск со 100% масштабированием, о devenv /noScale и о ForceDesignerDPIUnaware для .NET 6+.  2 3

  6. Microsoft Learn, DPI_AWARENESS_CONTEXT handle. Об определениях и поведении контекстов Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED (масштабирование GDI, Windows 10 1809+).  2 3 4 5

  7. Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. О смешении режимов DPI-осведомлённости для отдельных окон верхнего уровня через SetThreadDpiAwarenessContext и о DPI-осведомлённых API вроде GetDpiForWindow. 

  8. Microsoft Learn, SetProcessDpiAwareness function. О настройке DPI-осведомлённости процесса по умолчанию через API, о том, почему рекомендуется объявление через манифест, и о невозможности изменить значение после установки. 

  9. Microsoft Learn, Automatic form scaling - Windows Forms. О поведении автоматического масштабирования через AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions и о том, что смешение режимов Font и Dpi не поддерживается.  2

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Значки в области уведомлений и всплывающие (toast) уведомления в Windows-приложениях — подводные камни NotifyIcon и выбор правильного AppNotification

Практическое руководство о том, как удерживать бизнес-приложение Windows в области уведомлений (system tray) и оповещать пользователя с п...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Почему 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, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог