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

143

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

Вопросы:

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

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

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

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

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

Брайан Кеннеди
источник

Ответы:

127

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

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

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

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

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

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

  • Убедитесь, что вы никогда не устанавливаете шрифт на уровне контейнера ... только на листовых элементах управления ИЛИ в конструкторе вашей самой базовой формы, если вы хотите использовать шрифт по умолчанию для всего приложения, кроме MS Sans Serif. (Установка шрифта на контейнере, по-видимому, отключает автоматическое масштабирование этого контейнера, потому что оно происходит в алфавитном порядке после настройки параметров AutoScaleMode и AutoScaleDimensions.) ПРИМЕЧАНИЕ: если вы действительно измените шрифт в конструкторе самой базовой формы, это приведет к ваши AutoScaleDimensions для вычисления не так, как 6x13; в частности, если вы переключитесь на пользовательский интерфейс Segoe (шрифт Win 10 по умолчанию), то он будет 7x15 ... вам нужно будет дотронуться до каждой формы в Designer, чтобы она могла пересчитать все измерения в этом файле .designer, включая AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

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

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

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

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

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

  • Когда он выполняет автоматическое масштабирование, он будет пытаться масштабировать всю форму ... однако, если в этом процессе он достигает верхнего предела размера экрана, это жесткое ограничение, которое затем может испортить (обрезать) масштабирование Поэтому следует убедиться, что все формы в Designer с разрешением 100% / 96 точек на дюйм имеют размер не более 1024x720 (что соответствует 150% на экране 1080p или 300%, что является рекомендуемым значением Windows на экране 4K). Но вам нужно вычесть для гигантской строки заголовка / заголовка Win10 ... так что больше похоже на максимальный размер 1000x680 ... которое в конструкторе будет похоже на 994x642 ClientSize. (Таким образом, вы можете найти FindAll References на ClientSize, чтобы найти нарушителей.)

kjbartel
источник
NumericUpDownтоже не Marginправильно масштабирует . Кажется, маржа масштабируется дважды. Если я уменьшу его один раз, это выглядит хорошо.
ygoe
AutoScaleMode = Fontплохо работает для пользователей, которые используют очень большой шрифт и в Ubuntu. Мы предпочитаемAutoScaleMode = DPI
KindDragon
> TextBox с MultiLine = True и Font наследуется. Сходить с ума весь день - вот и все! Спасибо! Кстати, это же исправление также и для элементов управления ListBox. : D
neminem
Для меня списки с унаследованным шрифтом плохо масштабируются. Они делают после того, как явно установлены. (.NET 4.7)
PulseJet
27

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

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

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

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


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

Роман Старков
источник
1
Не настраивайтесь Fontв конструкторе: на ум приходит мысль: продолжайте и установите шрифт в конструкторе, чтобы вы могли создавать желаемый шрифт. ТОГДА в конструкторе, после разметки, прочитал это свойство шрифта и снова установил то же значение? Или, может быть, просто попросить сделать макет еще раз? [Предостережение: у меня не было причин тестировать этот подход.] Или, согласно ответу Ноулека , в конструкторе укажите пиксели (чтобы дизайнер Visual Studio не изменял масштаб на мониторе с высоким DPI), а в коде прочитайте это значение, конвертируйте из пикселей в баллы (чтобы получить правильное масштабирование).
ToolmakerSteve
1
Каждый бит нашего кода имеет размеры автоматического масштабирования, установленные прямо перед режимом автоматического масштабирования, и все это отлично масштабируется. Похоже, порядок не имеет значения в большинстве случаев.
Джош
Я искал свой код для случаев, когда AutoScaleDimensionsне было установлено в соответствии new SizeF(6F, 13F)с рекомендациями в верхнем ответе. Оказалось, что в каждом случае было установлено свойство Font формы (не по умолчанию). Похоже, что когда AutoScaleMode = Font, то AutoScaleDimensionsрассчитывается на основе свойства шрифта формы. Кроме того, настройка масштабирования в панели управления Windows, похоже, влияет на AutoScaleDimensions.
Вальтер Стабош
24

Настройте свое приложение для .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и объявите приложение Per Monitor Aware. Это сейчас сделано в 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 caling из меню 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)

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

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

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

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

magicandre1981
источник
12

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

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 и сделайте правый клик, сброс.
Полковник паника
источник
3
Для диалогов, размер которых можно изменить, AutoSize для всего будет кошмаром, я не хочу, чтобы мои кнопки становились все больше и меньше, так как я увеличиваю размер диалогов вручную при запуске программы.
Джош
10

Мне было очень сложно заставить 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
JRH
источник
6

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

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

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

Knowleech
источник
4

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

Brannon
источник