it-swarm.com.ru

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

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

Вопросы:

  • Что в WinForms НЕ выполняет автоматическое масштабирование, и поэтому его следует избегать?

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

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

Смотрите ответ вики сообщества ниже.

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

130
Brian Kennedy

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

  • Label с AutoSize = False и Font унаследованы. Явно установите Font в элементе управления, чтобы он отображался жирным шрифтом в окне "Свойства".
  • ListView ширина столбцов не масштабируется. Переопределите ScaleControl формы, чтобы сделать это вместо этого. Смотрите этот ответ
  • свойства SplitContainer's Panel1MinSize, Panel2MinSize и SplitterDistance
  • 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. Масштабировать фиксированный размер Form вручную после создания.

Руководство по проектированию:

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

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

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

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

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

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

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

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

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

107
kjbartel

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

Если вы создаете правильно перекомпоновываемый макет с автоматическим изменением размера, то почти все работает точно так же, как и автоматически, с настройками по умолчанию, используемыми Visual Studio (а именно, AutoSizeMode = Font в родительской форме и Inherit во всем остальном).

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

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


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

23
Roman Starkov

Настройте свое приложение для .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. Они включают:

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

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

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

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

<compatibility xmlns="urn:schemas-Microsoft.comn:compatibility.v1">
    <application>
        <!-- Windows 10 compatibility -->
        <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
    </application>
</compatibility>

Затем добавьте app.config и объявите приложение для каждого монитора. Это СЕЙЧАС сделано в app.config, а НЕ в манифесте, как раньше!

<System.Windows.Forms.ApplicationConfigurationSection>
   <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection> 

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

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

enter image description here

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

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

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

17
magicandre1981

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

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

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

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

Мне было очень трудно заставить WinForms играть в Nice с высоким 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
8
user3950597

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

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

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

5
Knowleech

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

4
Brannon