Создание приложения с поддержкой DPI
У меня есть форма заявки в C#. Когда я меняю DPI монитора, все элементы управления перемещаются. Я использовал код this.AutoScaleMode = AutoScaleMode.Dpi
, но это не избежало проблемы.
У кого-нибудь есть идея?
11 ответов
РЕДАКТИРОВАТЬ: Начиная с.NET 4.7, формы Windows улучшил поддержку высокого DPI. Узнайте больше об этом на docs.microsoft.com. Он работает только для Win 10 Creators Update и более поздних версий, поэтому его использование может оказаться невозможным, в зависимости от вашей пользовательской базы.
Сложно, но не невозможно. Конечно, лучше всего перейти на WPF, но это может оказаться невозможным.
Я провел много времени с этой проблемой. Вот некоторые правила / рекомендации, чтобы заставить его работать правильно без FlowLayoutPanel или TableLayoutPanel:
- Всегда редактируйте / создавайте свои приложения по умолчанию 96 DPI (100%). Если вы проектируете с разрешением 120 точек на дюйм (125% f.ex), будет очень плохо, когда вы вернетесь к 96 DPI, чтобы работать с ним позже.
- Я успешно использовал AutoScaleMode.Font, я не очень много пробовал AutoScaleMode.DPI.
- Убедитесь, что вы используете размер шрифта по умолчанию для всех ваших контейнеров (форм, панелей, вкладок, пользовательских контролей и т. Д.). 8,25 пикс. Предпочтительно его вообще не следует устанавливать в файле.Designer.cs для всех контейнеров, чтобы он использовал шрифт по умолчанию из класса контейнера.
- Все контейнеры должны использовать один и тот же AutoScaleMode
- Убедитесь, что во всех контейнерах установлена строка ниже в файле Designer.cs:
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); // for design in 96 DPI
- Если вам нужно установить разные размеры шрифта для надписей / текстовых полей и т. Д., Установите их для каждого элемента управления вместо установки шрифта для класса контейнера, потому что winforms использует настройку шрифта контейнеров для масштабирования его содержимого и наличия панели f.ex с другим размером шрифта. чем он содержит форму гарантированно создает проблемы. Это может сработать, если форма и все контейнеры в ней используют одинаковый размер шрифта, но я не пробовал.
- Используйте другую машину или установку виртуальных окон (VMware, Virtual PC, VirtualBox) с более высоким значением DPI, чтобы немедленно протестировать ваш дизайн. Просто запустите скомпилированный файл.exe из папки /bin/Debug на компьютере DEV.
Я гарантирую, что если вы будете следовать этим рекомендациям, все будет в порядке, даже если вы разместили элементы управления с определенными якорями и не используете поточную панель. У нас есть приложение, созданное таким образом, развернутое на сотнях машин с различными настройками DPI, и у нас больше нет жалоб. Все размеры форм / контейнеров / сеток / кнопок / текстовых полей и т. Д. Масштабируются правильно, как и шрифт. Изображения тоже работают, но они имеют тенденцию становиться немного пиксельными при высоком DPI.
РЕДАКТИРОВАТЬ: Эта ссылка имеет много полезной информации, особенно если вы решите использовать AutoScaleMode.DPI: ссылка на связанный вопрос стекопотока
Как исправить размытые Windows Forms в настройках с высоким разрешением
- Перейдите в дизайнер форм, затем выберите форму (нажав на ее заголовок)
- Нажмите F4, чтобы открыть окно свойств,
- затем найдите свойство AutoScaleMode
- Измените его со шрифта (по умолчанию) на Dpi.
Теперь перейдите в Program.cs (или в файл, где находится ваш метод Main) и измените его так:
namespace myApplication
{
static class Program
{
[STAThread]
static void Main()
{
// ***this line is added***
if (Environment.OSVersion.Version.Major >= 6)
SetProcessDPIAware();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
// ***also dllimport of that function***
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetProcessDPIAware();
}
}
Сохраните и скомпилируйте. Теперь ваша форма снова должна выглядеть хрустящей.
источник: http://crsouza.com/2015/04/13/how-to-fix-blurry-windows-forms-windows-in-high-dpi-settings/
Я наконец нашел решение проблемы как Ориентации экрана, так и обработки DPI.
Microsoft уже предоставила документ, объясняющий это, но с небольшим недостатком, который полностью убьет обработку DPI. Просто следуйте решению, предоставленному в документе ниже в разделе "Создание отдельного кода макета для каждой ориентации" http://msdn.microsoft.com/en-us/library/ms838174.aspx
Тогда ВАЖНАЯ часть! Внутри кода для методов Landscape() и Portrait() в самом конце каждого из них добавьте следующие строки:
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
Итак, код для этих 2 методов будет выглядеть так:
protected void Portrait()
{
this.SuspendLayout();
this.crawlTime.Location = new System.Drawing.Point(88, 216);
this.crawlTime.Size = new System.Drawing.Size(136, 16);
this.crawlTimeLabel.Location = new System.Drawing.Point(10, 216);
this.crawlTimeLabel.Size = new System.Drawing.Size(64, 16);
this.crawlStartTime.Location = new System.Drawing.Point(88, 200);
this.crawlStartTime.Size = new System.Drawing.Size(136, 16);
this.crawlStartedLabel.Location = new System.Drawing.Point(10, 200);
this.crawlStartedLabel.Size = new System.Drawing.Size(64, 16);
this.light1.Location = new System.Drawing.Point(208, 66);
this.light1.Size = new System.Drawing.Size(16, 16);
this.light0.Location = new System.Drawing.Point(192, 66);
this.light0.Size = new System.Drawing.Size(16, 16);
this.linkCount.Location = new System.Drawing.Point(88, 182);
this.linkCount.Size = new System.Drawing.Size(136, 16);
this.linkCountLabel.Location = new System.Drawing.Point(10, 182);
this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
this.currentPageBox.Location = new System.Drawing.Point(10, 84);
this.currentPageBox.Size = new System.Drawing.Size(214, 90);
this.currentPageLabel.Location = new System.Drawing.Point(10, 68);
this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
this.addressLabel.Location = new System.Drawing.Point(10, 4);
this.addressLabel.Size = new System.Drawing.Size(214, 16);
this.noProxyCheck.Location = new System.Drawing.Point(10, 48);
this.noProxyCheck.Size = new System.Drawing.Size(214, 20);
this.startButton.Location = new System.Drawing.Point(8, 240);
this.startButton.Size = new System.Drawing.Size(216, 20);
this.addressBox.Location = new System.Drawing.Point(10, 24);
this.addressBox.Size = new System.Drawing.Size(214, 22);
//note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; //IMPORTANT
this.ResumeLayout(false);
}
protected void Landscape()
{
this.SuspendLayout();
this.crawlTime.Location = new System.Drawing.Point(216, 136);
this.crawlTime.Size = new System.Drawing.Size(96, 16);
this.crawlTimeLabel.Location = new System.Drawing.Point(160, 136);
this.crawlTimeLabel.Size = new System.Drawing.Size(48, 16);
this.crawlStartTime.Location = new System.Drawing.Point(64, 120);
this.crawlStartTime.Size = new System.Drawing.Size(248, 16);
this.crawlStartedLabel.Location = new System.Drawing.Point(8, 120);
this.crawlStartedLabel.Size = new System.Drawing.Size(48, 16);
this.light1.Location = new System.Drawing.Point(296, 48);
this.light1.Size = new System.Drawing.Size(16, 16);
this.light0.Location = new System.Drawing.Point(280, 48);
this.light0.Size = new System.Drawing.Size(16, 16);
this.linkCount.Location = new System.Drawing.Point(80, 136);
this.linkCount.Size = new System.Drawing.Size(72, 16);
this.linkCountLabel.Location = new System.Drawing.Point(8, 136);
this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
this.currentPageBox.Location = new System.Drawing.Point(10, 64);
this.currentPageBox.Size = new System.Drawing.Size(302, 48);
this.currentPageLabel.Location = new System.Drawing.Point(10, 48);
this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
this.addressLabel.Location = new System.Drawing.Point(10, 4);
this.addressLabel.Size = new System.Drawing.Size(50, 16);
this.noProxyCheck.Location = new System.Drawing.Point(168, 16);
this.noProxyCheck.Size = new System.Drawing.Size(152, 24);
this.startButton.Location = new System.Drawing.Point(8, 160);
this.startButton.Size = new System.Drawing.Size(304, 20);
this.addressBox.Location = new System.Drawing.Point(10, 20);
this.addressBox.Size = new System.Drawing.Size(150, 22);
//note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; //IMPORTANT
this.ResumeLayout(false);
}
Работает как шарм для меня.
Похоже, это проблема с Windows. Снятие этих двух строк исправило все.
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
Вот где я получил решение:
В Windows Forms действительно сложно создавать приложения с поддержкой DPI. Вы должны будете использовать контейнеры макета, которые правильно изменяют размер при изменении DPI (например, TableLayoutPanel или FlowLayoutPanel). Все элементы управления также нуждаются в изменении размера. Конфигурация этих контейнеров может быть проблемой.
Для простых приложений это можно сделать за разумное время, но для больших приложений это действительно много работы.
Из опыта:
- не используйте DPI DPI с окнами, если не критично
- с этой целью всегда установлены
AutoScaleMode
собственность наNone
на всех формах и пользовательских элементах управления в вашем приложении - Результат: WYSIWYG-тип интерфейса при изменении настроек DPI
Некоторое время я боролся с этим, в конце концов я нашел супер простое решение для Windows 10 и, возможно, других систем.
В вашем WinForms
App.config
файл вставьте это:
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>
Затем создайте
app.manifest
файл и вставьте или прокомментируйте в этой строке:
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
После выполнения вышеизложенного я смог получить отличные результаты DPI на своих экранах 4K.
Проверьте это и это для получения дополнительной информации.
Будучи разработчиком с монитором 4K, я пробовал всевозможные решения. Все окна WPF работают нормально, но все WinForms масштабируются неправильно. Это происходит в приложении WinForms в WPF, а также в виде отдельных WinForms. Оба приложения в .NET 4.7.2. поэтому он должен работать, как описано во всех других ответах, но это не так.
Последнее решение, которым я закончил, — использовать «не знаю» вместо «PerMonitorV2».
В файле app.manifest
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">unaware</dpiAwareness>
</windowsSettings>
</application>
- Если вы хотите, чтобы ваше приложение WinForms было приложением с поддержкой DPI, в дополнение к хорошему ответу Trygve, если у вас большой проект, вы можете автоматически масштабировать свои формы и их содержимое, это можно сделать, создав функцию ScaleByDPI:
Функция ScaleByDPI получит параметр Control, который обычно является формой, а затем рекурсивно перебирает все субэлементы управления (if (control.HasChildren == true)), а также масштабирует местоположение и размеры от элементов управления вашего приложения, а также размеров и размеров шрифтов для ОС настроена DPI. Вы можете попробовать реализовать это также для изображений, иконок и графики.
Специальные примечания для функции ScaleByDPI:
а. Для всех элементов управления с размерами шрифтов по умолчанию вам нужно установить их Font.Size равным 8,25.
б. Вы можете получить значения devicePixelRatioX и devicePixelRatioY с помощью (control.CreateGraphics().DpiX / 96) и (control.CreateGraphics().DpiY / 96).
с. Вам потребуется масштабировать Control.Size & Control.Location по алгоритму, основанному на значениях control.Dock & control.Anchor. Обратите внимание, что control.Dock может иметь 1 из 6 возможных значений, а control.Anchor может иметь 1 из 16 возможных значений.
д. этому алгоритму потребуется установить значения для следующих переменных bool: isDoizeWidth, isDoSizeHeight, isDoLocationX, isDoLocationY, isDoRefactorSizeWidth, isDoRefactorSizeHeight, isDoRefactorLocationX, isDoRefactorLocationY, isDoClacLocationXBasedOlabLocOgnLocOgn_LocationOgn.LocationOgn.
е. Если в вашем проекте используется библиотека элементов управления, отличная от элементов управления Microsoft, для этих элементов управления может потребоваться специальная обработка.
Больше информации по выше (d.) Переменным bool:
* Иногда группу элементов управления (может быть кнопками) необходимо размещать друг за другом на одной вертикальной линии, и их значение привязки включает в себя право, но не слева, или их нужно размещать друг за другом на одной горизонтальной линии, и их Значения привязки включают Bottom, но не Top, в этом случае вам необходимо пересчитать значения элементов управления Location.
* В случае элементов управления, которые Anchor содержат Top & Bottom и \ или Left & Right, вам нужно будет повторно учитывать элементы управления Size & Location.
Использование функции ScaleByDPI:
а. Добавьте следующую команду в конец любого конструктора Form: ScaleByDPI(this);
б. Также при динамическом добавлении любого элемента управления в вызов Form для ScaleByDPI([ControlName]).
Когда вы устанавливаете размер или расположение любого элемента управления динамически после завершения конструктора, создайте и используйте одну из следующих функций для получения масштабированных значений размера или расположения: ScaleByDPI_X \ ScaleByDPI_Y \ ScaleByDPI_Size \ ScaleByDPI_Point
Чтобы пометить ваше приложение как поддерживающее DPI, добавьте элемент dpiAware в манифест сборки приложения.
Установите для GraphicsUnit всех Control.Font значение System.Drawing.GraphicsUnit.Point.
В файлах *.Designer.cs всех контейнеров установите значение AutoScaleMode равным System.Windows.Forms.AutoScaleMode.None.
в элементах управления, таких как ComboBox и TextBox, изменение Control.Size.Hieght не влияет. В этом случае изменение Control.Font.Size исправит высоту элемента управления.
Если значение StartPosition формы равно FormStartPosition.CenterScreen, вам потребуется пересчитать расположение окна.
Добавление файла проекта (yourproject.csproj):
<DpiAwarenessPerMonitor>true</DpiAwarenessPerMonitor>
Файл Program.cs, если существует.
ApplicationConfiguration.Initialize();
Сделайте это бесплатно. Удалите эту строку. Поскольку он устанавливает настройки разрешения начальной загрузки, настройте его в соответствии с настройками проекта.
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(MainForm);
Добавление функции конструктора MainForm.cs
this.AutoScaleMode = AutoScaleMode.Dpi;
И удалите из MainForm.designer.cs
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
Они четко работают для приложения .Net 6 Winform.
Поскольку форма заявки Winform может управлять содержимым и изображениями, разрешение системы изменять размер ВАШЕГО окна НЕ является решением, но если вам удастся получить одну форму для разрешения DPI с правильно масштабированными изображениями... И это не очень хорошая идея, с ростом размера экрана размер шрифта уменьшается.
При использовании другого разрешения DPI система заставляет вашу форму переопределять размер, расположение и шрифт своего элемента управления, НО НЕ ИЗОБРАЖЕНИЯ, решение состоит в том, чтобы изменить DPI формы во время выполнения, при загрузке, чтобы все возвращалось к исходному размеру и расположению.
Это возможное решение, которое я протестировал с помощью приложения для карточных игр, в котором у меня есть около 80 кнопок изображений, TabControls и т. Д.
В каждом событии form_Load формы добавьте этот фрагмент кода:
Dim dpi As Graphics = Me.CreateGraphics Select Case dpi.DpiX Case 120 '-- Do nothing if your app has been desigbned with 120 dpi
Case Else
'-- I use 125 AND NOT 120 because 120 is 25% more than 96
Me.Font = New Font(Me.Font.FontFamily, Me.Font.Size * 125 / dpi.DpiX)
End Select
Кроме того, быстрый трюк для тестирования различных разрешений на одном компьютере без перезагрузки:
Из панели управления измените разрешение. Не перезагружать! Вместо этого закройте сеанс и откройте новый с тем же пользователем.
Есть еще одна оговорка: если вы устанавливаете размер и положение элемента управления во время выполнения, тогда вы должны применить тот же фактор DPI (например, 125 / Dpi.Dpix) к новым координатам. Поэтому вам лучше настроить глобальную переменную DPIFactor из события application.startup.
Последний, но тем не менее важный:
НЕ открывайте свое приложение в Visual Studio с разрешением, отличным от исходного, иначе ВСЕ ВАШИ ОРГАНЫ УПРАВЛЕНИЯ будут перемещаться и изменять размер при открытии каждой формы, и пути назад нет...
Надеюсь, это поможет, счастливого программирования.