Compartir a través de


Solucionar problemas de PPP

Un número creciente de dispositivos se envían con pantallas de "alta resolución". Estas pantallas suelen tener más de 200 píxeles por pulgada (ppi). Trabajar con una aplicación en estos equipos requerirá que el contenido se escale verticalmente para satisfacer las necesidades de ver el contenido a una distancia de visualización normal para el dispositivo. A partir de 2014, el objetivo principal para pantallas de alta densidad es dispositivos informáticos móviles (tabletas, portátiles de clamshell y teléfonos).

Windows 8.1 y versiones posteriores contienen varias características para permitir que estas máquinas funcionen con pantallas y entornos en los que la máquina está conectada a pantallas de alta densidad y densidad estándar al mismo tiempo.

  • Windows puede permitirle escalar contenido al dispositivo mediante la opción "Hacer que el texto y otros elementos sean más grandes o más pequeños" (disponibles desde Windows XP).

  • Windows 8.1 y versiones posteriores escalarán automáticamente el contenido para que la mayoría de las aplicaciones sean coherentes cuando se muevan entre pantallas de densidades de píxeles diferentes. Cuando la pantalla principal es de alta densidad (escalado del 200 %) y la pantalla secundaria es densidad estándar (100%), Windows escalará automáticamente el contenido de la ventana de la aplicación en la pantalla secundaria (1 píxel mostrado por cada 4 píxeles representados por la aplicación).

  • Windows tendrá como valor predeterminado el escalado adecuado para la densidad de píxeles y la distancia de visualización de la pantalla (Windows 7 y versiones posteriores, configurables por OEM).

  • Windows puede escalar automáticamente el contenido hasta un 250 % en dispositivos nuevos que superen los 280 ppp (a partir de Windows 8.1 S14).

    Windows tiene una manera de tratar con el escalado vertical de la interfaz de usuario para aprovechar el aumento de los recuentos de píxeles. Una aplicación opta por este sistema mediante la declaración de "reconocimiento de PPP del sistema". El sistema escala verticalmente las aplicaciones que no lo hacen. Esto puede dar lugar a una experiencia de usuario aproximada en la que toda la aplicación está uniformemente estirada por píxeles. Por ejemplo:

    DPI Issues Fuzzy

    Visual Studio opta por ser compatible con el escalado de PPP y, por lo tanto, no está "virtualizado".

    Windows (y Visual Studio) aprovechan varias tecnologías de interfaz de usuario, que tienen diferentes formas de tratar con factores de escalado establecidos por el sistema. Por ejemplo:

  • WPF mide los controles de forma independiente del dispositivo (unidades, no píxeles). La interfaz de usuario de WPF se escala verticalmente automáticamente para el PPP actual.

  • Todos los tamaños de texto independientemente del marco de interfaz de usuario se expresan en puntos, por lo que el sistema lo trata como independiente de PPP. El texto de Win32, WinForms y WPF ya se escalan verticalmente correctamente cuando se dibujan en el dispositivo de visualización.

  • Los diálogos y ventanas win32/WinForms tienen medios para habilitar el diseño que cambia de tamaño con texto (por ejemplo, a través de paneles de cuadrícula, flujo y diseño de tabla). Esto permite evitar ubicaciones de píxeles codificadas de forma rígida que no se escalan cuando se aumentan los tamaños de fuente.

  • Los iconos proporcionados por el sistema o los recursos en función de las métricas del sistema (por ejemplo, SM_CXICON y SM_CXSMICON) ya se escalan verticalmente.

Interfaz de usuario anterior basada en Win32 (GDI, GDI+) y WinForms

Aunque WPF ya es altamente consciente de PPP, gran parte de nuestro código basado en Win32/GDI no se escribió originalmente con reconocimiento de PPP en mente. Windows ha proporcionado API de escalado de PPP. Las correcciones de los problemas de Win32 deben usarse de forma coherente en todo el producto. Visual Studio ha proporcionado una biblioteca de clases auxiliares para evitar duplicar la funcionalidad y garantizar la coherencia en todo el producto.

Imágenes de alta resolución

Esta sección es principalmente para desarrolladores que amplían Visual Studio 2013. Para Visual Studio 2015, use el servicio de imágenes integrado en Visual Studio. También puede que tenga que admitir o tener como destino muchas versiones de Visual Studio y, por tanto, usar el servicio de imágenes en 2015 no es una opción, ya que no existe en versiones anteriores. Esta sección también es para usted.

Escalado vertical de imágenes demasiado pequeñas

Las imágenes que son demasiado pequeñas se pueden escalar verticalmente y representar en GDI y WPF mediante algunos métodos comunes. Las clases auxiliares de PPP administradas están disponibles para integradores internos y externos de Visual Studio para abordar iconos de escalado, mapas de bits, imágenestrips e listas de imágenes. Los asistentes nativos de C/C++basados en Win32 están disponibles para escalar HICON, HBITMAP, HIMAGELIST y VsUI::GdiplusImage. El escalado de un mapa de bits normalmente solo requiere un cambio de una línea después de incluir una referencia a la biblioteca auxiliar. Por ejemplo:

(WinForms) DpiHelper.LogicalToDeviceUnits(ref image);

El escalado de una lista de imágenes depende de si la lista de imágenes se ha completado en tiempo de carga o se anexa en tiempo de ejecución. Si se completa en tiempo de carga, llame a LogicalToDeviceUnits() con la lista de imágenes como haría con un mapa de bits. Cuando el código necesite cargar un mapa de bits individual antes de redactar la lista de imágenes, asegúrese de escalar el tamaño de la imagen de la lista de imágenes:

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

En el código nativo, las dimensiones se pueden escalar al crear la lista de imágenes de la siguiente manera:

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

Las funciones de la biblioteca permiten especificar el algoritmo de cambio de tamaño. Al escalar imágenes que se van a colocar en listas de imágenes, asegúrese de especificar el color de fondo que se usa para la transparencia, o use el escalado NearestNeighbor (lo que provocará distorsiones al 125 % y 150 %).

Consulte la DpiHelper documentación de MSDN.

En la tabla siguiente se muestran ejemplos de cómo se deben escalar las imágenes en los factores de escalado de PPP correspondientes. Las imágenes descritas en naranja indican nuestro procedimiento recomendado a partir de Visual Studio 2013 (escalado de PPP del 100%-200%):

DPI Issues Scaling

Problemas de diseño

Los problemas comunes de diseño se pueden evitar principalmente manteniendo puntos en la interfaz de usuario escalados y relativos entre sí en lugar de usar ubicaciones absolutas (en concreto, en unidades de píxel). Por ejemplo:

  • Las posiciones de diseño y texto deben ajustarse para tener en cuenta las imágenes escaladas verticalmente.

  • Las columnas de las cuadrículas deben tener anchos ajustados para el texto escalado vertical.

  • También es necesario escalar verticalmente los tamaños codificados de forma rígida o el espacio entre los elementos. Los tamaños que solo se basan en dimensiones de texto suelen ser correctos, ya que las fuentes se escalan verticalmente automáticamente.

    Las funciones auxiliares están disponibles en la DpiHelper clase para permitir el escalado en el eje X e Y:

  • LogicalToDeviceUnitsX/LogicalToDeviceUnitsY (las funciones permiten el escalado en el eje X/Y)

  • int space = DpiHelper.LogicalToDeviceUnitsX (10);

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

    Hay sobrecargas LogicalToDeviceUnits para permitir el escalado de objetos como Rect, Point y Size.

Uso de la biblioteca o clase PPPHelper para escalar imágenes y diseño

La biblioteca auxiliar de PPP de Visual Studio está disponible en formularios nativos y administrados y se puede usar fuera del shell de Visual Studio mediante otras aplicaciones.

Para usar la biblioteca, vaya a los ejemplos de extensibilidad de VSSDK de Visual Studio y clone el ejemplo high-DPI_Images_Icons.

En los archivos de origen, incluya VsUIDpiHelper.h y llame a las funciones estáticas de VsUI::DpiHelper la clase :

#include "VsUIDpiHelper.h"

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

Nota:

No use las funciones auxiliares en variables estáticas de nivel de módulo o de clase. La biblioteca también usa estáticas para la sincronización de subprocesos y podría encontrarse con problemas de inicialización de orden. Convierta esas estáticas en variables miembro no estáticas o encapsularlas en una función (para que se construyan en el primer acceso).

Para acceder a las funciones auxiliares de PPP desde código administrado que se ejecutará dentro del entorno de Visual Studio:

  • El proyecto de consumo debe hacer referencia a la versión más reciente de MPF de Shell. Por ejemplo:

    <Reference Include="Microsoft.VisualStudio.Shell.14.0.dll" />
    
  • Asegúrese de que el proyecto tiene referencias a System.Windows.Forms, PresentationCore y PresentationUI.

  • En el código, use el espacio de nombres Microsoft.VisualStudio.PlatformUI y llame a funciones estáticas de la clase DpiHelper. Para los tipos admitidos (puntos, tamaños, rectángulos, etc.), se proporcionan funciones de extensión que devuelven nuevos objetos escalados. Por ejemplo:

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

Tratar con la información aproximada de la imagen de WPF en la interfaz de usuario con zoom

En WPF, WPF cambia el tamaño automáticamente de los mapas de bits para el nivel de zoom de PPP actual mediante un algoritmo bicubo de alta calidad (valor predeterminado), que funciona bien para imágenes o capturas de pantalla grandes, pero no es adecuado para los iconos de elementos de menú porque presenta la aproximada percepción.

Recomendaciones:

  • En el caso de la imagen de logotipo y las ilustraciones de banners, se podría usar el modo de cambio de tamaño predeterminado BitmapScalingMode .

  • En el caso de los elementos de menú y las imágenes de iconografía, BitmapScalingMode se debe usar cuando no provoca que otros artefactos de distorsión eliminen la ambigez (al 200 % y al 300 %).

  • Para niveles de zoom grandes no múltiplos del 100 % (por ejemplo, 250 % o 350 %), escalando imágenes de iconografía con resultados bicubicos en la interfaz de usuario aproximada y limpia. Un mejor resultado se obtiene escalando primero la imagen con NearestNeighbor al múltiplo más grande de 100 % (por ejemplo, 200 % o 300 %) y escalado con bicubic desde allí. Vea Caso especial: escalado previo de imágenes de WPF para niveles de PPP grandes para obtener más información.

    La clase DpiHelper del espacio de nombres Microsoft.VisualStudio.PlatformUI proporciona un miembro BitmapScalingMode que se puede usar para el enlace. Permitirá que el shell de Visual Studio controle el modo de escalado de mapa de bits en el producto uniformemente, en función del factor de escalado de PPP.

    Para usarlo en XAML, agregue:

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

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

El shell de Visual Studio ya establece esta propiedad en ventanas y diálogos de nivel superior. La interfaz de usuario basada en WPF que se ejecuta en Visual Studio ya la heredará. Si la configuración no se propaga a tus partes específicas de la interfaz de usuario, se puede establecer en el elemento raíz de la interfaz de usuario XAML/WPF. Los lugares en los que esto sucede incluyen elementos emergentes, en elementos con elementos primarios de Win32 y ventanas de diseñador que se ejecutan fuera de proceso, como Blend.

Algunas interfaces de usuario se pueden escalar independientemente del nivel de zoom de PPP del conjunto del sistema, como el editor de texto de Visual Studio y los diseñadores basados en WPF (Escritorio de WPF y Tienda Windows). En estos casos, no se debe usar DpiHelper.BitmapScalingMode. Para corregir este problema en el editor, el equipo del IDE creó una propiedad personalizada titulada RenderOptions.BitmapScalingMode. Establezca ese valor de propiedad en HighQuality o NearestNeighbor en función del nivel de zoom combinado del sistema y de la interfaz de usuario.

Caso especial: escalado previo de imágenes de WPF para niveles de PPP grandes

Para niveles de zoom muy grandes que no son múltiplos del 100 % (por ejemplo, 250 %, 350 %, etc.), el escalado de imágenes de iconografía con resultados bicubicos en la interfaz de usuario aproximada y limpia. La impresión de estas imágenes junto con texto nítido es casi como la de una ilusión óptica. Las imágenes parecen estar más cerca del ojo y del foco en relación con el texto. El resultado de escalado en este tamaño ampliado se puede mejorar escalando primero la imagen con NearestNeighbor al múltiplo más grande de 100 % (por ejemplo, 200 % o 300 %) y escalado con bicubo al resto (un 50 %).

A continuación se muestra un ejemplo de las diferencias en los resultados, donde se escala la primera imagen con el algoritmo de escalado doble mejorado 100%->200%->250%, y el segundo solo con bicubic 100%->250%.

DPI Issues Double Scaling Example

Para permitir que la interfaz de usuario use este escalado doble, deberá modificarse el marcado XAML para mostrar cada elemento Image. En los ejemplos siguientes se muestra cómo usar el escalado doble en WPF en Visual Studio mediante la biblioteca DpiHelper y Shell.12/14.

Paso 1: Escale previamente la imagen a 200 %, 300 %, etc. mediante NearestNeighbor.

Escale previamente la imagen mediante un convertidor aplicado en un enlace o con una extensión de marcado XAML. Por ejemplo:

<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" />

Si la imagen también debe tener una temática (la mayoría, si no todas, debe), el marcado puede usar un convertidor diferente que primero haga el creación de temáticas de la imagen y, a continuación, el escalado previo. El marcado puede usar o DpiPrescaleThemedImageConverterDpiPrescaleThemedImageSourceConverter, en función de la salida de conversión deseada.

<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>

Paso 2: Asegúrese de que el tamaño final es correcto para el PPP actual.

Dado que WPF escalará la interfaz de usuario para el PPP actual mediante la propiedad BitmapScalingMode establecida en UIElement, un control Image con una imagen preescalada, ya que su origen tendrá un aspecto dos o tres veces mayor que el que debería. A continuación se muestran dos maneras de contrarrestar este efecto:

  • Si conoce la dimensión de la imagen original al 100 %, puede especificar el tamaño exacto del control Image. Estos tamaños reflejarán el tamaño de la interfaz de usuario antes de aplicar el escalado.

    <Image Source="{Binding Path=SelectedImage, Converter={StaticResource DpiPrescaleImageSourceConverter}}" Width="16" Height="16" />
    
  • Si no se conoce el tamaño de la imagen original, se puede usar layoutTransform para reducir verticalmente el objeto Image final. Por ejemplo:

    <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>
    

Habilitación de la compatibilidad con HDPI en WebOC

De forma predeterminada, los controles WebOC (como el control WebBrowser en WPF o la interfaz IWebBrowser2) no habilitan la detección y compatibilidad de HDPI. El resultado será un control incrustado con contenido de pantalla demasiado pequeño en una pantalla de alta resolución. A continuación se describe cómo habilitar la compatibilidad con valores altos de PPP en una instancia de WebOC web específica.

Implemente la interfaz IDocHostUIHandler (consulte el artículo de MSDN sobre 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);
    }

Opcionalmente, implemente la interfaz ICustomDoc (consulte el artículo de MSDN sobre ICustomDoc:

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

Asocie la clase que implementa IDocHostUIHandler con el documento de WebOC. Si implementó la interfaz ICustomDoc anterior, tan pronto como la propiedad de documento de WebOC sea válida, la convierta en ICustomDoc y llame al método SetUIHandler, pasando la clase que implementa 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);

Si NO implementó la interfaz ICustomDoc, tan pronto como la propiedad de documento de WebOC sea válida, deberá convertirla en IOleObject y llamar al SetClientSite método , pasando la clase que implementa IDocHostUIHandler. Establezca la marca DOCHOSTUIFLAG_DPI_AWARE en el DOCHOSTUIINFO pasado a la GetHostInfo llamada al método:

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;
}

Esto debe ser todo lo que necesita para obtener el control de WebOC para admitir HPDI.

Recomendaciones

  1. Si cambia la propiedad del documento en el control WebOC, es posible que tenga que volver a asociar el documento con la clase IDocHostUIHandler.

  2. Si lo anterior no funciona, hay un problema conocido con webOC que no recoge el cambio en la marca de PPP. La forma más confiable de corregir esto es alternar el zoom óptico de WebOC, lo que significa dos llamadas con dos valores diferentes para el porcentaje de zoom. Además, si se requiere esta solución alternativa, es posible que sea necesario realizarla en cada llamada de navegación.

    // 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);
    }