Создание приложения с поддержкой 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 в настройках с высоким разрешением

  1. Перейдите в дизайнер форм, затем выберите форму (нажав на ее заголовок)
  2. Нажмите F4, чтобы открыть окно свойств,
  3. затем найдите свойство AutoScaleMode
  4. Измените его со шрифта (по умолчанию) на 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>
  1. Если вы хотите, чтобы ваше приложение 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]).

  1. Когда вы устанавливаете размер или расположение любого элемента управления динамически после завершения конструктора, создайте и используйте одну из следующих функций для получения масштабированных значений размера или расположения: ScaleByDPI_X \ ScaleByDPI_Y \ ScaleByDPI_Size \ ScaleByDPI_Point

  2. Чтобы пометить ваше приложение как поддерживающее DPI, добавьте элемент dpiAware в манифест сборки приложения.

  3. Установите для GraphicsUnit всех Control.Font значение System.Drawing.GraphicsUnit.Point.

  4. В файлах *.Designer.cs всех контейнеров установите значение AutoScaleMode равным System.Windows.Forms.AutoScaleMode.None.

  5. в элементах управления, таких как ComboBox и TextBox, изменение Control.Size.Hieght не влияет. В этом случае изменение Control.Font.Size исправит высоту элемента управления.

  6. Если значение StartPosition формы равно FormStartPosition.CenterScreen, вам потребуется пересчитать расположение окна.

  1. Добавление файла проекта (yourproject.csproj):

            <DpiAwarenessPerMonitor>true</DpiAwarenessPerMonitor>
    
  2. Файл Program.cs, если существует.

            ApplicationConfiguration.Initialize();
    
  3. Сделайте это бесплатно. Удалите эту строку. Поскольку он устанавливает настройки разрешения начальной загрузки, настройте его в соответствии с настройками проекта.

            Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(MainForm);
    
  4. Добавление функции конструктора MainForm.cs

            this.AutoScaleMode = AutoScaleMode.Dpi;
    
  5. И удалите из 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 с разрешением, отличным от исходного, иначе ВСЕ ВАШИ ОРГАНЫ УПРАВЛЕНИЯ будут перемещаться и изменять размер при открытии каждой формы, и пути назад нет...

Надеюсь, это поможет, счастливого программирования.

Другие вопросы по тегам