Як записати код 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, а не зміни в налаштуваннях розміру шрифту системи.)

  • Усі ContainerControls також повинні бути встановлені однаково AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, припускаючи 96dpi (див. Наступну кулю) та шрифт за замовчуванням MS Sans Serif (див. Кулю дві вниз). Це автоматично додано дизайнером на основі DPI, який ви відкриваєте в дизайнері ..., але він відсутній у багатьох наших найстаріших дизайнерських файлах. Можливо, Visual Studio .NET (версія до VS 2005) не додав це належним чином.

  • Виконайте, що всі ваші дизайнери працюють в 96dpi (ми можемо перейти на 120dpi; але мудрість в Інтернеті говорить про те, щоб дотримуватися 96dpi; експерименти там впорядковані; для дизайну це не має значення, оскільки це просто змінює AutoScaleDimensionsлінію, що дизайнерські вставки). Щоб налаштувати Visual Studio на віртуальний 96dpi на дисплеї з високою роздільною здатністю, знайдіть його .exe-файл, клацніть правою кнопкою миші, щоб відредагувати властивості, і в розділі «Сумісність» виберіть пункт «Переотримати масштабність поведінки на високому рівні DPI. Масштабування виконується системою».

  • Будьте впевнені, що ви ніколи не встановлюєте шрифт на рівні контейнера ... лише на елементах керування аркушами АБО в конструкторі вашої самої базової форми, якщо ви хочете, щоб шрифт за замовчуванням на загальний додаток, крім MS Sans Serif. (Установлення шрифту на контейнері, здається, вимикає автоматичне масштабування цього контейнера, оскільки воно в алфавітному порядку відбувається після встановлення параметрів AutoScaleMode та AutoScaleDimensions.) Зверніть увагу, що якщо змінити шрифт у конструкторі основної форми, це спричинить ваші AutoScaleDimensions для обчислення інакше, ніж 6x13; зокрема, якщо ви перейдете на інтерфейс Segoe (шрифт за замовчуванням Win 10), то це буде 7x15 ... вам потрібно торкнутися кожної форми в конструкторі, щоб вона змогла перерахувати всі розміри у файлі .designer, включаючи то AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • НЕ використовуйте якір Rightабо Bottomприв’язаний до UserControl ... його позиціонування не буде автоматично масштабуватися; натомість киньте панель чи інший контейнер у свій UserControl та прив’яжіть інші елементи управління до цієї панелі; є використання панелі Dock Right, Bottomабо Fillв вашому UserControl.

  • Тільки елементи керування зі списку Controls, коли ResumeLayoutв кінці InitializeComponentвиклику буде автоматично масштабовано ... якщо ви динамічно додаєте елементи керування, тоді вам потрібно буде SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();ввімкнути цей елемент управління, перш ніж його додавати. якщо ви не використовуєте режими Dock або Менеджер макетів, як FlowLayoutPanelабо TableLayoutPanel.

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

  • Уникайте використання Form.MaxSizeстатично / в конструкторі. MinSizeа MaxSizeна Формі не масштабуйте так само, як усе інше. Отже, якщо ви робите всю свою роботу в 96dpi, тоді, коли при більшій DPI, ваші MinSizeпроблеми не створюватимуть проблем, але можуть бути не такими обмежувальними, як ви очікували, але ви MaxSizeможете обмежити масштабування вашого розміру, що може спричинити проблеми. Якщо ви хочете MinSize == Size == MaxSize, не робіть цього в конструкторі ... зробіть це у своєму конструкторі або OnLoadзамініть ... встановіть і те, MinSizeі MaxSizeрозмір розміру належним чином.

  • Усі елементи управління на певному Panelабо Containerповинні використовувати або якір, або стикування. Якщо їх змішати, то автоматичне масштабування, зроблене цим Panel, часто буде погано поводитись в тонких химерних способах.

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


NumericUpDownтакож не масштабує Marginналежним чином. Здається, маржа зменшується вдвічі. Якщо я її масштабую один раз, це виглядає добре.
ygoe

AutoScaleMode = Fontне працює добре для користувачів, які використовують дуже великий шрифт та з Ubuntu. Ми віддаємо перевагуAutoScaleMode = DPI
KindDragon

> TextBox з MultiLine = True та шрифт успадковані. Сходити з розуму цілий день - це було виправданням! Дуже дякую! До речі, те саме виправлення є і виправленням для елементів ListBox. : D
neminem

Для мене вікна списків із успадкованим шрифтом не масштабуються. Вони роблять після явного встановлення. (.NET 4.7)
PulseJet


27

Мій досвід був досить відмінним від нинішньої відповіді, яка проголосувала вище. Перебравши рамковий код .NET і переглянувши посилання вихідного коду, я дійшов висновку, що все є на місці, щоб автоматичне масштабування працювало, і десь це було зіпсовано лише тонку проблему. Це виявилося правдою.

Якщо ви створили макет, що відповідає зміні / автоматичному розміру, правильно все працює, як слід, автоматично, з налаштуваннями за замовчуванням, які використовує Visual Studio (а саме: AutoSizeMode = Шрифт у батьківській формі, і Наслідувати про все інше).

Єдиний gotcha - це якщо ви встановили властивість Font у формі в дизайнері. Створений код буде сортувати призначення за алфавітом, а це означає, що AutoScaleDimensionsвони будуть призначені раніше Font . На жаль, це повністю порушує логіку автоматичного масштабування WinForms.

Однак виправлення просте. Або не встановлюйте Fontвластивість у дизайнері взагалі (встановіть його у конструкторі форм), або впорядкуйте ці завдання вручну (але тоді вам доведеться робити це кожен раз, коли ви редагуєте форму в дизайнері). Voila, майже ідеальне та повністю автоматичне масштабування з мінімальними клопотами. Навіть розміри форми масштабуються правильно.


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

  • Вкладений TableLayoutPanel невірно обчислює контрольні поля . Немає відомих обхідних ситуацій, щоб взагалі не уникати поля та прокладки - або уникати вкладених панелей планування таблиць.

1
Повторне налаштування Fontдизайнера: Думка приходить на думку: продовжуйте та встановіть шрифт у дизайнері, щоб ви могли створити потрібний шрифт. ТОГО в конструкторі після компонування прочитати цю властивість шрифту і знову встановити те саме значення? А може просто попросити макет зробити знову? [Caveat: У мене не було підстав перевіряти цей підхід.] Або за відповіддю Knowleech , в дизайнері вкажіть у пікселях (тому дизайнер Visual Studio не переставляє масштаб на високому моніторі DPI), а в коді читайте це значення, перетворюйте з пікселів до балів (для правильного масштабування).
ToolmakerSteve

1
Кожен біт нашого коду має розміри автоматичної шкали, встановлені перед режимом автоматичного масштабування, і все це ідеально масштабується. Схоже, порядок не має значення в більшості випадків.
Джош

Я шукав у своєму коді випадки, коли у верхній відповіді AutoScaleDimensionsне було встановлено так, new SizeF(6F, 13F)як рекомендовано. Виявилося, що в кожному випадку властивість шрифту форми була встановлена ​​(не за замовчуванням). Здається, що тоді 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 та динамічного 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 змінюється на пристрої відображення, де форма відображається в даний час.

Також у вас є три допоміжні методи щодо обробки / масштабування DPI:

  • Control.LogicalToDeviceUnits , який перетворює значення з логічного в пікселі пристрою.

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

  • Control.DeviceDpi , який повертає DPI для поточного пристрою.

Якщо проблеми все ще виникають, ви можете відмовитися від удосконалення DPI за допомогою записів app.config .

Якщо у вас немає доступу до вихідного коду, ви можете перейти до властивостей програми в Windows Explorer, перейти до сумісності та вибрати System (Enhanced)

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

що активує масштабування GDI, щоб також покращити обробку DPI:

Для програм, що базуються на GDI, Windows тепер може DPI масштабувати їх на основі монітора. Це означає, що ці програми, як магічно, стануть відомими DPI за монітором.

Зробіть усі ці кроки, і ви повинні отримати кращий досвід DPI для додатків WinForms. Але пам’ятайте, що вам потрібно націлити на ваш додаток для .net 4.7 і вам потрібно принаймні Windows 10 Build 15063 (Creators Update). У наступному оновлення Windows 10 1709 ми можемо отримати більше вдосконалень.


12

Посібник, який я написав на роботі:

WPF працює в "пристроях, незалежних від пристроїв", що означає, що всі елементи керування ідеально підходять до екранів з високим dpi. У WinForms потрібно більше дбати.

WinForms працює у пікселях. Текст буде масштабуватись відповідно до системної dpi, але його часто обрізатимуть неміцним керуванням. Щоб уникнути подібних проблем, потрібно уникати чітких розмірів та розміщення. Дотримуйтесь цих правил:

  1. Де б ви не знаходили (мітки, кнопки, панелі), встановіть для властивості AutoSize значення True.
  2. Для компонування використовуйте FlowLayoutPanel (a la WPF StackPanel) та TableLayoutPanel (a la WPF Grid) для компонування, а не ванільну панель.
  3. Якщо ви розробляєте на високому dpi-машині, дизайнер Visual Studio може бути розчаруванням. Якщо ви встановите AutoSize = True, він змінить розмір елемента керування на ваш екран. Якщо в елементі управління є AutoSizeMode = GrowOnly, він залишатиметься таким розміром для людей у ​​звичайному dpi, тобто. бути більшим, ніж очікувалося. Щоб виправити це, відкрийте конструктор на комп’ютері зі звичайним dpi та зробіть правою кнопкою миші та скиньте.

3
для діалогів, які можна змінити на автоматичний розмір, було б кошмаром, я не хочу, щоб мої кнопки збільшувалися та зменшувалися, оскільки я збільшую розміри діалогів вручну під час роботи програми.
Джош

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

6

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

Всього два мої центи. Я подумав, що я поділяюсь , тому що "збереження AutoScaleMode = шрифту" та "Встановлення розміру шрифту в пікселях для дизайнера" - те, що я не знайшов в Інтернеті.

У моєму блозі є кілька деталей: http://www.sgrottel.de/?p=1581&lang=en


4

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.