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

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

Вопросов:

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

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

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

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

  • 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

  • Избегайте беды Invoke / BeginInvoke в обработке событий в WinForm для кросс-streamов?
  • Как я могу сделать ComboBox недоступным для редактирования в .NET?
  • Будет ли Windows Forms устаревшим в пользу WPF?
  • Выделение WinForms с несколькими столбцами (C #)?
  • Панель не получает фокус
  • Как делиться данными между формами?
  • Как заставить мою программу C # Winforms запускаться как администратор на любом компьютере?
  • this.Visible не работает в Windows Forms
  • Как очистить текст всех текстовых полей в форме?
  • Использует ли Mutex для предотвращения безопасной работы нескольких экземпляров одной и той же программы?
  • Как разместить текст на ProgressBar?
  • Interesting Posts

    Как локально (в локальной сети) и публично (через Интернет) получить доступ к IP-камере, используя только один IP-адрес?

    Как предотвратить печать экрана

    Автоматический выбор сетевых параметров на основе сети Wi-Fi

    Присвоение делает указатель из целого без приведения

    Нужна помощь в разработке логики БД

    как сохранить возвращаемое значение при входе в scala

    Строгое поведение управляющей вкладки в Notepad ++

    SQLite «ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ В» и «ОБНОВИТЬ … ГДЕ»

    Почему локальные переменные не инициализируются в Java?

    Предварительный просмотр камеры на нескольких устройствах Android

    Информация о выставлении счетов aws с использованием aws java sdk

    Двухцветный фон, разделенный диагональной линией, используя css

    Ошибка Visual Studio 2015 «Ссылка на объект не установлена ​​в экземпляр объекта» после установки ASP.NET и веб-инструментов 2015 (RC1 Update 1)

    Итерирование через таблицу Lua из C ++?

    jQuery Мобильная блокировка блокировки

    Давайте будем гением компьютера.