Как написать код WinForms, который автоматически масштабируется до системных настроек шрифта и dpi?

Intro: Есть много комментариев, которые говорят, что «WinForms не автоматически масштабируется до настроек DPI / шрифта, переключитесь в WPF». Однако, я думаю, что это основано на .NET 1.1; похоже, они действительно неплохо выполнили автоматическое масштабирование в .NET 2.0. По крайней мере, основываясь на наших исследованиях и тестировании. Однако, если некоторые из вас знают лучше, мы будем рады услышать от вас. (Пожалуйста, не беспокойтесь, утверждая, что мы должны переключиться на WPF … это сейчас не вариант).

Вопросов:

Руководства по дизайну, которые мы определили до сих пор:

См. Ответ сообщества wiki ниже.

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

7 Solutions collect form web for “Как написать код WinForms, который автоматически масштабируется до системных настроек шрифта и dpi?”

Элементы управления, которые не поддерживают масштабирование должным образом:

  • Label с AutoSize = False и Font унаследованы. Явно установите Font на элементе управления, чтобы он появился жирным шрифтом в окне «Свойства».
  • Ширины столбцов ListView не масштабируются. Вместо этого ScaleControl формы. См. Этот ответ
  • SplitContainer Panel1MinSize , Panel2MinSize и SplitterDistance Panel2MinSize
  • TextBox с MultiLine = True и Font унаследованы. Явно установите Font на элементе управления, чтобы он появился жирным шрифтом в окне «Свойства».
  • Изображение ToolStripButton . В конструкторе формы:

    • Установить ToolStrip.AutoSize = False
    • Установите ToolStrip.ImageScalingSize соответствии с CreateGraphics.DpiX и .DpiY
    • При необходимости установите ToolStrip.AutoSize = True .

    Иногда AutoSize можно оставить на True но иногда он не может изменять размер без этих шагов. Работает без изменений с .NET Framework 4.5.2 и EnableWindowsFormsHighDpiAutoResizing .

  • Изображения TreeView . Установите ImageList.ImageSize соответствии с CreateGraphics.DpiX и .DpiY . Для StateImageList работает без изменений с .NET Framework 4.5.1 и EnableWindowsFormsHighDpiAutoResizing .
  • Размер формы. Масштабировать фиксированный размер Form вручную после создания.

Рекомендации по проектированию:

  • Для всех ContainerControls должен быть установлен тот же AutoScaleMode = Font . (Шрифт будет обрабатывать как изменения DPI, так и изменения настроек размера системного шрифта, DPI будет обрабатывать только изменения DPI, а не изменения настроек размера шрифта системы).

  • Все ContainerControls также должны быть установлены с помощью AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); , предполагая 96dpi (см. следующий маркер). Это автоматически добавляется дизайнером на основе DPI, который вы открываете в дизайнере … но он отсутствовал во многих наших старых файлах дизайнера. Возможно, Visual Studio .NET (версия до VS 2005) не добавляла это правильно.

  • Сделайте все, что ваш дизайнер работает в 96dpi (мы могли бы переключиться на 120dpi, но мудрость в Интернете говорит о том, чтобы придерживаться 96dpi, эксперименты в порядке, по дизайну это не имеет значения, поскольку это просто изменяет линию AutoScaleDimensions что дизайнер вставляет). Чтобы настроить Visual Studio на виртуальный 96dpi на дисплее с высоким разрешением, найдите его .exe-файл, щелкните правой кнопкой мыши, чтобы изменить свойства, и в разделе «Совместимость» выберите «Переопределить поведение масштабирования с высоким DPI. Масштабирование выполняется с помощью системы».

  • Убедитесь, что вы никогда не устанавливаете шрифт на уровне контейнера … только на элементах управления листьями. (Настройка шрифта в контейнере, похоже, отключает автомасштабирование этого контейнера).

  • НЕ используйте привязку якоря Right или Bottom привязанного к UserControl … его позиционирование не будет автоматически масштабироваться; вместо этого отбросьте панель или другой контейнер в свой UserControl и привяжите свои другие элементы управления к этой панели; чтобы панель использовала Dock Right или Dock Bottom в вашем UserControl.

  • Только элементы управления в списках Controls, когда ResumeLayout в конце InitializeComponent будут автоматически масштабироваться … если вы динамически добавляете элементы управления, тогда вам необходимо SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout(); на этом элементе управления, прежде чем добавлять его. И ваше позиционирование также необходимо будет отрегулировать, если вы не используете режимы док-станции или диспетчер компоновки, например FlowLayoutPanel или TableLayoutPanel .

  • Базовые classы, полученные из ContainerControl должны оставить AutoScaleMode установленным для Inherit (значение по умолчанию, установленное в classе ContainerControl , но НЕ установлено по умолчанию разработчиком). Если вы установите его на что-нибудь еще, а затем ваш производный class попытается установить его в Font (как и должно быть), тогда действие установки этого Font очистит настройку AutoScaleDimensions , что приведет к фактическому отключению AutoScaleDimensions ! (Это руководство в сочетании с предыдущим означает, что вы никогда не сможете создавать базовые classы в дизайнере … все classы должны быть либо разработаны как базовые classы, либо как classы листьев!)

  • Избегайте использования Form.MaxSize статически / в конструкторе. MinSize и MaxSize в форме не масштабируются так же, как все остальное. Итак, если вы выполняете всю свою работу в 96dpi, тогда, когда в более высоком DPI ваш MinSize не вызовет проблем, но может и не быть настолько ограничительным, как вы ожидали, но ваш MaxSize может ограничить масштабирование вашего размера, что может вызвать проблемы. Если вы хотите MinSize == Size == MaxSize , не делайте этого в Дизайнере … сделайте это в своем конструкторе или переопределите OnLoad … установите как MinSize и MaxSize в ваш правильно масштабированный размер.

  • Все элементы управления на конкретной Panel или Container должны либо использовать привязку, либо стыковку. Если вы их смешиваете, автоматическое масштабирование, выполняемое этой Panel будет часто ошибочным в тонких причудливых путях.

Мой опыт был довольно отличным от нынешнего главного голосового ответа. Пройдя через код платформы .NET и просматривая исходный код ссылки, я пришел к выводу, что все, что нужно для автоматического масштабирования, работает, и там была только тонкая проблема, которая помешала ему. Это оказалось правдой.

Если вы создаете макет подходящего reflowable / auto-size, то почти все работает точно так же, как и автоматически, с настройками по умолчанию, используемыми Visual Studio (а именно, AutoSizeMode = Font в родительской форме и Inherit на все остальное).

Единственное, что вы получили, – это установить свойство Font в форме в дизайнере. Сгенерированный код сортирует присвоения в алфавитном порядке, а это означает, что AutoScaleDimensions будут назначены перед AutoScaleDimensions . К сожалению, это полностью нарушает логику автоматического масштабирования WinForms.

Исправить это просто. Либо не устанавливайте свойство Font в дизайнере вообще (установите его в свой конструктор форм), либо вручную измените порядок этих назначений (но тогда вы должны продолжать делать это каждый раз, когда редактируете форму в дизайнере). Voila, почти идеальное и полностью автоматическое масштабирование с минимальными хлопотами. Даже размеры формы масштабируются правильно.


Я буду перечислять известные проблемы здесь, когда я сталкиваюсь с ними:

  • Вложенная TableLayoutPanel вычисляет контрольные поля . Никакой известной работы не обойтись без полей и прокладок вообще – или избегать вложенных панелей макета таблицы.

Задайте свое приложение для .Net Framework 4.7 и запустите его под Windows 10 v1703 (Creators Update Build 15063). С .Net 4.7 в Windows 10 (v1703) MS сделала много улучшений DPI .

Начиная с .NET Framework 4.7, Windows Forms включает усовершенствования для обычных сценариев с высоким уровнем DPI и динамического DPI. К ним относятся:

  • Улучшения в масштабировании и компоновке нескольких элементов управления Windows Forms, таких как элемент управления MonthCalendar и элемент управления CheckedListBox.

  • Однопроходное масштабирование. В .NET Framework 4.6 и более ранних версиях масштабирование выполнялось с помощью нескольких проходов, что заставляло некоторые элементы управления масштабироваться больше, чем это было необходимо.

  • Поддержка динамических сценариев DPI, в которых пользователь изменяет DPI или масштабный коэффициент после запуска приложения Windows Forms.

Чтобы поддержать его, добавьте манифест приложения в приложение и сообщите, что ваше приложение поддерживает Windows 10:

       

Затем добавьте app.config и объявите приложение Per Monitor Aware. Это выполняется сейчас в app.config и NOT в манифесте, как раньше!

    

Этот PerMonitorV2 является новым с момента обновления Windows 10 Creators:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Также известен как Per Monitor v2. Продвижение по сравнению с первоначальным режимом распознавания DPI для каждого монитора, что позволяет приложениям получать доступ к новым связанным с DPI масштабируемым поведением на основе windows верхнего уровня.

  • Уведомление об изменении дочернего windows уведомления DPI. В контекстах Per Monitor v2 все дерево окон уведомляется о любых изменениях DPI, которые происходят.

  • Масштабирование неклиентской области – все windows автоматически будут иметь свою неклиентскую область, нарисованную чувствительным способом DPI. Вызовы EnableNonClientDpiScaling не нужны.

  • S caling из меню Win32 – все меню NTUSER, созданные в контекстах Per Monitor v2, будут масштабироваться для каждого монитора.

  • Диалоговое масштабирование. Диалоги Win32, созданные в контекстах Per Monitor v2, автоматически будут реагировать на изменения DPI.

  • Улучшенное масштабирование элементов управления comctl32. Различные элементы управления comctl32 улучшили поведение масштабирования DPI в контексте Per Monitor v2.

  • Улучшенное поведение в темах – дескрипторы UxTheme, открытые в контексте windows Per2 v2, будут работать в терминах DPI, связанных с этим окном.

Теперь вы можете подписаться на 3 новых события, чтобы получать уведомления о изменениях DPI:

  • Control.DpiChangedAfterParent , который запускается, возникает, когда параметр DPI для элемента управления изменяется программно после события изменения DPI для его родительского элемента управления или формы.

  • Control.DpiChangedBeforeParent , который запускается, когда параметр DPI для элемента управления изменяется программно до того, как произошло событие изменения DPI для его родительского элемента управления или формы.

  • Form.DpiChanged , который запускается, когда параметр DPI изменяется на устройстве отображения, где отображается форма.

У вас также есть 3 вспомогательных метода обработки / масштабирования DPI:

  • Control.LogicalToDeviceUnits , который преобразует значение из логического в пиксели устройства.

  • Control.ScaleBitmapLogicalToDevice , который масштабирует bitmap для логического DPI для устройства.

  • Control.DeviceDpi , который возвращает DPI для текущего устройства.

Если вы все еще видите проблемы, вы можете отказаться от улучшений DPI через записи app.config .

Если у вас нет доступа к исходному коду, вы можете перейти к свойствам приложения в проводнике Windows, перейти к совместимости и выбрать « System (Enhanced)

введите описание изображения здесь

который активирует масштабирование GDI, чтобы улучшить обработку DPI:

Для приложений, работающих под Windows на базе GDI, теперь DPI может масштабировать их для каждого монитора. Это означает, что эти приложения, по волшебству, станут доступными для каждого DPI-монитора.

Выполняйте все эти шаги, и вы должны получить лучший опыт DPI для приложений WinForms. Но помните, что вам нужно настроить таргетинг на приложение .net 4.7 и вам понадобится хотя бы Windows 10 Build 15063 (Creators Update). В следующем обновлении Windows 10 1709 мы можем получить больше улучшений.

Руководство, которое я написал на работе:

WPF работает в «независимых устройствах», что означает, что все элементы управления отлично подходят для экранов с высоким разрешением. В WinForms это требует большего внимания.

WinForms работает в пикселях. Текст будет масштабироваться в соответствии с системным разрешением, но его часто обрезают с помощью немасштабированного элемента управления. Чтобы избежать таких проблем, вы должны избегать явного определения размеров и позиционирования. Следуйте этим правилам:

  1. Где бы вы ни находили (метки, кнопки, панели), для свойства AutoSize задано значение True.
  2. Для компоновки используйте FlowLayoutPanel (a la WPF StackPanel) и TableLayoutPanel (a la WPF Grid) для макета, а не для панели ванили.
  3. Если вы работаете на машине с высоким разрешением, дизайнер Visual Studio может разочаровываться. Когда вы устанавливаете AutoSize = True, он изменит размер элемента управления на ваш экран. Если элемент управления имеет значение AutoSizeMode = GrowOnly, он останется таким размером для людей с обычным dpi, т. Е. быть больше, чем ожидалось. Чтобы исправить это, откройте конструктор на компьютере с нормальным dpi и сделайте щелчок правой кнопкой мыши, сбросьте.

Мне показалось очень трудным заставить WinForms играть хорошо с высоким DPI. Итак, я написал метод VB.NET, чтобы переопределить поведение формы:

 Public Shared Sub ScaleForm(WindowsForm As System.Windows.Forms.Form) Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics Dim sngScaleFactor As Single = 1 Dim sngFontFactor As Single = 1 If g.DpiX > 96 Then sngScaleFactor = g.DpiX / 96 'sngFontFactor = 96 / g.DpiY End If If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then 'ucWindowsFormHost.ScaleControl(WindowsForm, sngFontFactor) WindowsForm.Scale(sngScaleFactor) End If End Using End Sub 

В дополнение к якорям, которые не работают очень хорошо: я бы сделал шаг дальше и сказал, что точное позиционирование (иначе, используя свойство Location) не очень хорошо работает с масштабированием шрифта. Мне пришлось решить эту проблему в двух разных проектах. В обоих случаях нам пришлось преобразовать расположение всех элементов управления WinForms в использование TableLayoutPanel и FlowLayoutPanel. Использование свойства Dock (обычно устанавливается для Fill) внутри TableLayoutPanel очень хорошо работает и масштабируется с помощью системного шрифта DPI.

Я недавно столкнулся с этой проблемой, особенно в сочетании с масштабированием Visual Studio при открытии редактора в системе с высоким разрешением. Мне было лучше сохранить AutoScaleMode = Font , но установить шрифт Forms на шрифт по умолчанию, но указав размер в пикселе , а не в точке, то есть: Font = MS Sans; 11px Font = MS Sans; 11px . В коде я затем устанавливаю шрифт по умолчанию: Font = SystemFonts.DefaultFont и все в порядке.

Только мои два цента. Я думал, что я разделяю, потому что «сохранение AutoScaleMode = Font» и «Установить размер шрифта в пикселе для Дизайнера» было чем-то, что я не нашел в Интернете.

У меня есть более подробная информация о моем блоге: http://www.sgrottel.de/?p=1581&lang=en

  • C # - Как сделать две формы ссылки друг на друга
  • Как создать событие в Usercontrol и обработать его в основной форме?
  • Остановите «Дин» при нажатии Enter
  • Текстовое свойство в UserControl в C #
  • Отображение формы Windows на дополнительном мониторе?
  • Как отключить звук щелчка в WebBrowser Control
  • Как настроить системное меню формы Windows?
  • Создание контроля прозрачным
  • Безопасно ли просто устанавливать CheckForIllegalCrossThreadCalls на false, чтобы избежать ошибок сквозной streamовой передачи во время отладки?
  • Как создать кнопку, которая может отправлять ключи в систему без потери фокуса - Виртуальная клавиатура
  • как я могу показать сумму в столбце datagridview?
  • Давайте будем гением компьютера.