Поделиться через


Устранение проблем С DPI

Все больше устройств отправляются с экранов с высоким разрешением. Эти экраны обычно имеют более 200 пикселей на дюйм (ppi). Для работы с приложением на этих компьютерах потребуется масштабировать содержимое в соответствии с потребностями просмотра содержимого на обычном расстоянии просмотра для устройства. По состоянию на 2014 год основной целью для отображения высокой плотности является мобильные вычислительные устройства (планшеты, ноутбуки и телефоны).

Windows 8.1 и более поздних версий содержит несколько функций, позволяющих этим компьютерам работать с дисплеями и средами, где компьютер подключен как к высокой плотности, так и к стандартной плотности.

  • Windows позволяет масштабировать содержимое на устройстве с помощью параметра "Сделать текст и другие элементы больше или меньше" (доступно с Windows XP).

  • Windows 8.1 и более поздних версий автоматически масштабирует содержимое для большинства приложений, которые будут согласованы при перемещении между отображением разных плотностей пикселей. Если основной дисплей является высокой плотностью (масштабирование на 200 %, а дополнительный дисплей — стандартная плотность (100%), Windows автоматически масштабируется содержимое окна приложения вниз на дополнительном дисплее (1 пиксель, отображаемый каждые 4 пикселя, отрисованных приложением).

  • Windows по умолчанию используется для правильного масштабирования для плотности пикселей и расстояния просмотра для дисплея (Windows 7 и более поздней, настраиваемой OEM).

  • Windows может автоматически масштабировать содержимое до 250 % на новых устройствах, превышающих 280 пикселей (по состоянию на Windows 8.1 S14).

    Windows имеет способ масштабирования пользовательского интерфейса, чтобы воспользоваться преимуществами увеличения количества пикселей. Приложение выбирает эту систему, объявляя себя "системным DPI с учетом". Приложения, которые этого не делают, масштабируются системой. Это может привести к "нечеткой" пользовательской среде, в которой все приложение равномерно растянуто в пикселях. Например:

    DPI Issues Fuzzy

    Visual Studio выбирает масштабирование DPI и поэтому не является "виртуализированным".

    Windows (и Visual Studio) используют несколько технологий пользовательского интерфейса, которые имеют различные способы работы с коэффициентами масштабирования, заданными системой. Например:

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

  • Все размеры текста независимо от платформы пользовательского интерфейса выражаются в точках и поэтому обрабатываются системой как независимо от DPI. Текст в Win32, WinForms и WPF уже масштабируются правильно при рисовании на отображаемом устройстве.

  • Диалоговые окна и окна Win32/WinForms позволяют включить макет, который изменяет размер текста (например, с помощью панелей сетки, потока и макета таблицы). Это позволяет избежать жестко закодированных расположений пикселей, которые не масштабируются при увеличении размера шрифта.

  • Значки, предоставляемые системой или ресурсами на основе системных метрик (например, SM_CXICON и SM_CXSMICON), уже масштабируются.

Старый пользовательский интерфейс Win32 (GDI, GDI+) и WinForms

Хотя WPF уже поддерживает высокий уровень DPI, большая часть кода на основе Win32/GDI изначально не была написана с учетом осведомленности о DPI. Windows предоставила API-интерфейсы масштабирования DPI. Исправление проблем Win32 должно использовать эти проблемы последовательно в рамках продукта. Visual Studio предоставила вспомогательной библиотеке классов, чтобы избежать дублирования функций и обеспечения согласованности в продукте.

Изображения с высоким разрешением

Этот раздел предназначен в основном для разработчиков, расширяющих Visual Studio 2013. Для Visual Studio 2015 используйте службу образов, встроенную в Visual Studio. Вы также можете обнаружить, что вам нужно поддерживать или использовать многие версии Visual Studio, поэтому использование службы образов в 2015 году не является вариантом, так как он не существует в предыдущих версиях. Этот раздел также предназначен для вас.

Увеличение масштаба изображений, слишком маленьких

Изображения, которые слишком малы, можно масштабировать и отображать в GDI и WPF с помощью некоторых распространенных методов. Вспомогательные классы управляемого DPI доступны для внутренних и внешних интеграторов Visual Studio для решения значков масштабирования, растровых изображений, изображений и списков изображений. Вспомогательные средства на основе Win32 на основе C/C++доступны для масштабирования HICON, HBITMAP, HIMAGELIST и VsUI::GdiplusImage. Масштабирование растрового изображения обычно требует только однострочного изменения после включения ссылки на вспомогательной библиотеке. Например:

(WinForms) DpiHelper.LogicalToDeviceUnits(ref image);

Масштабирование списка изображений зависит от того, завершен ли список изображений во время загрузки или добавляется во время выполнения. При завершении загрузки вызовите LogicalToDeviceUnits() список изображений, как и растровое изображение. Если коду необходимо загрузить отдельный растровый рисунок перед созданием списка изображений, обязательно масштабируйте размер изображения списка изображений:

imagelist.ImageSize = DpiHelper.LogicalToDeviceUnits(imagelist.ImageSize);

В машинном коде измерения можно масштабировать при создании списка изображений следующим образом:

ImageList_Create(VsUI::DpiHelper::LogicalToDeviceUnitsX(16),VsUI::DpiHelper::LogicalToDeviceUnitsY(16), ILC_COLOR32|ILC_MASK, nCount, 1);

Функции в библиотеке позволяют указывать алгоритм изменения размера. При масштабировании изображений в списках изображений обязательно укажите цвет фона, используемый для прозрачности, или используйте масштабирование NearestNeighbor (что приведет к искажениям в 125% и 150%).

Ознакомьтесь с DpiHelper документацией по MSDN.

В следующей таблице показаны примеры масштабирования изображений с помощью соответствующих факторов масштабирования DPI. Изображения, описанные в оранжевый цвет, указывают на нашу рекомендацию по масштабированию Visual Studio 2013 (100%-200% масштабирования DPI):

DPI Issues Scaling

Проблемы с макетом

Распространенные проблемы макета можно избежать в первую очередь путем сохранения точек в масштабируемом пользовательском интерфейсе и относительно друг друга, а не с помощью абсолютных расположений (в частности, в единицах пикселей). Например:

  • Позиции макета или текста должны быть скорректированы в учетную запись для масштабируемых изображений.

  • Столбцы в сетках должны иметь ширину, скорректированную для масштабируемого текста.

  • Жестко закодированные размеры или пробелы между элементами также необходимо увеличить масштаб. Размеры, основанные только на размерах текста, обычно являются тонкими, так как шрифты автоматически масштабируются.

    Вспомогательные функции доступны в DpiHelper классе, чтобы разрешить масштабирование на оси X и Y:

  • LogicalToDeviceUnitsX/LogicalToDeviceUnitsY (функции позволяют масштабирование на оси X/Y)

  • int space = DpiHelper.LogicalToDeviceUnitsX (10);

  • int height = VsUI::D piHelper::LogicalToDeviceUnitsY(5);

    Существуют перегрузки LogicalToDeviceUnits, позволяющие масштабировать такие объекты, как Rect, Point и Size.

Использование библиотеки или класса DPIHelper для масштабирования изображений и макета

Вспомогательные библиотеки DPI Visual Studio доступны в собственных и управляемых формах и могут использоваться вне оболочки Visual Studio другими приложениями.

Чтобы использовать библиотеку, перейдите к примерам расширяемости VSSDK Visual Studio и клонируйте пример высокой DPI_Images_Icons.

В исходных файлах включите VsUIDpiHelper.h и вызовите статические функции VsUI::DpiHelper класса:

#include "VsUIDpiHelper.h"

int cxScaled = VsUI::DpiHelper::LogicalToDeviceUnitsX(cx);
VsUI::DpiHelper::LogicalToDeviceUnits(&hBitmap);

Примечание.

Не используйте вспомогательные функции в статических переменных уровня модуля или класса. Библиотека также использует статические статики для синхронизации потоков, и могут возникнуть проблемы с инициализацией порядка. Преобразуйте эти статические значения в нестатические переменные-члены или заключите их в функцию (поэтому они создаются при первом доступе).

Чтобы получить доступ к вспомогательным функциям DPI из управляемого кода, который будет выполняться в среде Visual Studio:

  • Используемый проект должен ссылаться на последнюю версию Shell MPF. Например:

    <Reference Include="Microsoft.VisualStudio.Shell.14.0.dll" />
    
  • Убедитесь, что проект содержит ссылки на System.Windows.Forms, PresentationCore и PresentationUI.

  • В коде используйте пространство имен Microsoft.VisualStudio.PlatformUI и вызовите статические функции класса DpiHelper. Для поддерживаемых типов (точек, размеров, прямоугольников и т. д.) предоставляются функции расширения, возвращающие новые масштабируемые объекты. Например:

    using Microsoft.VisualStudio.PlatformUI;
    double x = DpiHelper.LogicalToDeviceUnitsX(posX);
    Point ptScaled = ptOriginal.LogicalToDeviceUnits();
    DpiHelper.LogicalToDeviceUnits(ref bitmap);
    
    

Работа с нечеткостью изображения WPF в масштабируемом пользовательском интерфейсе

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

Рекомендации.

  • Для изображения логотипа и баннеров можно использовать режим изменения размера по умолчанию BitmapScalingMode .

  • Для элементов меню и изображений BitmapScalingMode значков следует использовать, если он не вызывает другие артефакты искажения, чтобы устранить нечеткость (в 200% и 300%).

  • Для больших уровней масштабирования не кратно 100 % (например, 250% или 350%), масштабирование изображений значков с бикубическими результатами в нечетких, вымытый пользовательский интерфейс. Лучший результат получается путем первого масштабирования изображения с помощью БлижайшегоNeighbor до самого большого числа 100 % (например, 200% или 300%) и масштабирования с бикубической оттуда. Дополнительные сведения см. в разделе "Особый случай: предмасштабирование образов WPF для больших уровней DPI".

    Класс DpiHelper в пространстве имен Microsoft.VisualStudio.PlatformUI предоставляет элемент BitmapScalingMode , который можно использовать для привязки. Она позволит оболочке Visual Studio управлять режимом масштабирования растрового изображения в продукте равномерно в зависимости от коэффициента масштабирования DPI.

    Чтобы использовать его в XAML, добавьте:

xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0"

<Setter Property="RenderOptions.BitmapScalingMode" Value="{x:Static vs:DpiHelper.BitmapScalingMode}" />

Оболочка Visual Studio уже задает это свойство в окнах верхнего уровня и диалоговых окнах. Пользовательский интерфейс на основе WPF, запущенный в Visual Studio, уже наследует его. Если параметр не распространяется на определенные части пользовательского интерфейса, его можно задать в корневом элементе пользовательского интерфейса XAML/WPF. Места, где это происходит, включают всплывающие окна, элементы с родителями Win32 и окна конструктора, которые выполняются из процесса, например Blend.

Некоторый пользовательский интерфейс может масштабироваться независимо от уровня масштабирования набора DPI, таких как текстовый редактор Visual Studio и конструкторы на основе WPF (рабочий стол WPF и Магазин Windows). В этих случаях не следует использовать DpiHelper.BitmapScalingMode. Чтобы устранить эту проблему в редакторе, команда интегрированной среды разработки создала настраиваемое свойство RenderOptions.BitmapScalingMode. Задайте для этого свойства значение HighQuality или NearestNeighbor в зависимости от объединенного уровня масштабирования системы и пользовательского интерфейса.

Особый случай: предмасштабирование образов WPF для больших уровней DPI

Для очень больших уровней масштабирования, которые не кратны 100 % (например, 250%, 350 %, и т. д.), масштабирование изображений значков с бикубическими результатами в нечетких, вымытый пользовательский интерфейс. Впечатление этих изображений наряду с хрустящим текстом почти как у оптической иллюзии. Изображения, как представляется, ближе к глазу и вне фокуса в отношении текста. Результат масштабирования в этом увеличенном размере можно улучшить путем первого масштабирования изображения с помощью БлижайшегоNeighbor до наибольшего числа 100 % (например, 200% или 300 %) и масштабирования с бикубическим на оставшуюся часть (дополнительно 50 %).

Ниже приведен пример различий в результатах, где первый образ масштабируется с улучшенным алгоритмом двойного масштабирования 100%-200%->>250%, а второй — только с бикубическим 100%->250%.

DPI Issues Double Scaling Example

Чтобы пользовательский интерфейс мог использовать это двойное масштабирование, разметка XAML для отображения каждого элемента Image должна быть изменена. В следующих примерах показано, как использовать двойное масштабирование в WPF в Visual Studio с помощью библиотеки DpiHelper и Shell.12/14.

Шаг 1. Предварительное масштабирование изображения до 200%, 300 %, и т. д. с помощью БлижайшегоNeighbor.

Предмасштабирование изображения с помощью преобразователя, примененного к привязке, или с расширением разметки XAML. Например:

<vsui:DpiPrescaleImageSourceConverter x:Key="DpiPrescaleImageSourceConverter" />

<Image Source="{Binding Path=SelectedImage, Converter={StaticResource DpiPrescaleImageSourceConverter}}" Width="16" Height="16" />

<Image Source="{vsui:DpiPrescaledImage Images/Help.png}" Width="16" Height="16" />

Если изображение также должно быть тематическим (большинство, если не все, должно), разметка может использовать другой преобразователь, который сначала выполняет их создание, а затем предварительное масштабирование. Разметка может использовать либо DpiPrescaleThemedImageConverter DpiPrescaleThemedImageSourceConverterв зависимости от требуемого выходных данных преобразования.

<vsui:DpiPrescaleThemedImageSourceConverter x:Key="DpiPrescaleThemedImageSourceConverter" />

<Image Width="16" Height="16">
  <Image.Source>
    <MultiBinding Converter="{StaticResource DpiPrescaleThemedImageSourceConverter}">
      <Binding Path="Icon" />
      <Binding Path="(vsui:ImageThemingUtilities.ImageBackgroundColor)"
               RelativeSource="{RelativeSource Self}" />
      <Binding Source="{x:Static vsui:Boxes.BooleanTrue}" />
    </MultiBinding>
  </Image.Source>
</Image>

Шаг 2. Убедитесь, что окончательный размер правильно для текущего DPI.

Так как WPF масштабирует пользовательский интерфейс для текущего DPI с помощью свойства BitmapScalingMode, заданного в UIElement, элемент управления Image с предварительно масштабируемым изображением будет выглядеть в два или три раза больше, чем должно. Ниже приведены несколько способов противодействия этому эффекту:

  • Если вы знаете измерение исходного изображения на 100 %, можно указать точный размер элемента управления Image. Эти размеры отражают размер пользовательского интерфейса перед применением масштабирования.

    <Image Source="{Binding Path=SelectedImage, Converter={StaticResource DpiPrescaleImageSourceConverter}}" Width="16" Height="16" />
    
  • Если размер исходного изображения не известен, макетTransform можно использовать для уменьшения размера конечного объекта Image. Например:

    <Image Source="{Binding Path=SelectedImage, Converter={StaticResource DpiPrescaleImageSourceConverter}}" >
        <Image.LayoutTransform>
            <ScaleTransform
                ScaleX="{x:Static vsui:DpiHelper.PreScaledImageLayoutTransformScale}"
                ScaleY="{x:Static vsui:DpiHelper.PreScaledImageLayoutTransformScale}" />
        </Image.LayoutTransform>
    </Image>
    

Включение поддержки HDPI для WebOC

По умолчанию элементы управления WebOC (например, элемент управления WebBrowser в WPF или интерфейс IWebBrowser2) не поддерживают обнаружение и поддержку HDPI. Результатом будет внедренный элемент управления с содержимым дисплея, слишком небольшим на дисплее с высоким разрешением. В следующем описано, как включить поддержку высокого уровня DPI в определенном веб-экземпляре WebOC.

Реализуйте интерфейс IDocHostUIHandler (см. статью MSDN по IDocHostUIHandler:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
 Guid("BD3F23C0-D43E-11CF-893B-00AA00BDCE1A")]
public interface IDocHostUIHandler
{
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int ShowContextMenu(
        [In, MarshalAs(UnmanagedType.U4)] int dwID,
        [In] POINT pt,
        [In, MarshalAs(UnmanagedType.Interface)] object pcmdtReserved,
        [In, MarshalAs(UnmanagedType.IDispatch)] object pdispReserved);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int GetHostInfo([In, Out] DOCHOSTUIINFO info);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int ShowUI(
        [In, MarshalAs(UnmanagedType.I4)] int dwID,
        [In, MarshalAs(UnmanagedType.Interface)] object activeObject,
        [In, MarshalAs(UnmanagedType.Interface)] object commandTarget,
        [In, MarshalAs(UnmanagedType.Interface)] object frame,
        [In, MarshalAs(UnmanagedType.Interface)] object doc);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int HideUI();
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int UpdateUI();
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int EnableModeless([In, MarshalAs(UnmanagedType.Bool)] bool fEnable);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int OnDocWindowActivate([In, MarshalAs(UnmanagedType.Bool)] bool fActivate);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int OnFrameWindowActivate([In, MarshalAs(UnmanagedType.Bool)] bool fActivate);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int ResizeBorder(
        [In] COMRECT rect,
        [In, MarshalAs(UnmanagedType.Interface)] object doc,
        bool fFrameWindow);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int TranslateAccelerator(
        [In] ref MSG msg,
        [In] ref Guid group,
        [In, MarshalAs(UnmanagedType.I4)] int nCmdID);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int GetOptionKeyPath(
        [Out, MarshalAs(UnmanagedType.LPArray)] string[] pbstrKey,
        [In, MarshalAs(UnmanagedType.U4)] int dw);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int GetDropTarget(
        [In, MarshalAs(UnmanagedType.Interface)] IOleDropTarget pDropTarget,
        [MarshalAs(UnmanagedType.Interface)] out IOleDropTarget ppDropTarget);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int GetExternal([MarshalAs(UnmanagedType.IDispatch)] out object ppDispatch);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int TranslateUrl(
        [In, MarshalAs(UnmanagedType.U4)] int dwTranslate,
        [In, MarshalAs(UnmanagedType.LPWStr)] string strURLIn,
        [MarshalAs(UnmanagedType.LPWStr)] out string pstrURLOut);
    [return: MarshalAs(UnmanagedType.I4)]
    [PreserveSig]
    int FilterDataObject(
        IDataObject pDO,
        out IDataObject ppDORet);
    }

При необходимости реализуйте интерфейс ICustomDoc (см. статью MSDN по ICustomDoc:

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
 Guid("3050F3F0-98B5-11CF-BB82-00AA00BDCE0B")]
public interface ICustomDoc
{
    void SetUIHandler(IDocHostUIHandler pUIHandler);
}

Свяжите класс, реализующий IDocHostUIHandler с документом WebOC. Если вы реализовали приведенный выше интерфейс ICustomDoc, то как только свойство документа WebOC допустимо, присвойте его ICustomDoc и вызовите метод SetUIHandler, передав класс, реализующий IDocHostUIHandler.

// "this" references that class that owns the WebOC control and in this case also implements the IDocHostUIHandler interface
ICustomDoc customDoc = (ICustomDoc)webBrowser.Document;
customDoc.SetUIHandler(this);

Если вы не реализовали интерфейс ICustomDoc, то как только свойство документа WebOC допустимо, необходимо привести его к IOleObject и вызвать SetClientSite метод, передавая в класс, реализующий IDocHostUIHandler. Задайте флаг DOCHOSTUIFLAG_DPI_AWARE в DOCHOSTUIINFO, переданном вызову GetHostInfo метода:

public int GetHostInfo(DOCHOSTUIINFO info)
{
    // This is what the default site provides.
    info.dwFlags = (DOCHOSTUIFLAG)0x5a74012;
    // Add the DPI flag to the defaults
    info.dwFlags |=.DOCHOSTUIFLAG.DOCHOSTUIFLAG_DPI_AWARE;
    return S_OK;
}

Это должно быть все, что необходимо получить элемент управления WebOC для поддержки HPDI.

Советы

  1. Если свойство документа в элементе управления WebOC изменяется, может потребоваться повторно связать документ с классом IDocHostUIHandler.

  2. Если приведенный выше параметр не работает, существует известная проблема с WebOC, не требующая изменения флага DPI. Самый надежный способ исправления заключается в переключение оптического масштабирования WebOC, что означает два вызова с двумя разными значениями для процента масштабирования. Кроме того, если это решение необходимо, может потребоваться выполнить его при каждом вызове навигации.

    // browser2 is a SHDocVw.IWebBrowser2 in this case
    // EX: Call the Exec twice with DPI%-1 and then DPI% as the zoomPercent values
    IOleCommandTarget cmdTarget = browser2.Document as IOleCommandTarget;
    if (cmdTarget != null)
    {
        object commandInput = zoomPercent;
        cmdTarget.Exec(IntPtr.Zero,
                       OLECMDID_OPTICAL_ZOOM,
                       OLECMDEXECOPT_DONTPROMPTUSER,
                       ref commandInput,
                       ref commandOutput);
    }