Résoudre les problèmes DPI

Un nombre croissant d’appareils sont expédiés avec des écrans « haute résolution ». Ces écrans ont généralement plus de 200 pixels par pouce (ppp). L’utilisation d’une application sur ces ordinateurs nécessite que le contenu soit mis à l’échelle pour répondre aux besoins de l’affichage du contenu à une distance normale pour l’appareil. À compter de 2014, la cible principale pour les écrans à haute densité est l’informatique mobile (tablettes, ordinateurs portables palourdes et téléphones).

Windows 8.1 et versions ultérieures contient plusieurs fonctionnalités pour permettre à ces ordinateurs d’utiliser des affichages et des environnements où l’ordinateur est attaché à des affichages à haute densité et à densité standard en même temps.

  • Windows peut vous permettre de mettre à l’échelle le contenu sur l’appareil à l’aide du paramètre « Rendre le texte et d’autres éléments plus volumineux ou plus petits » (disponible depuis Windows XP).

  • Windows 8.1 et versions ultérieures met automatiquement à l’échelle le contenu pour que la plupart des applications soient cohérentes lorsqu’elles sont déplacées entre les affichages de densités de pixels différentes. Lorsque l’affichage principal est à haute densité (mise à l’échelle de 200 % ) et que l’affichage secondaire est de densité standard (100 %), Windows met automatiquement à l’échelle le contenu de la fenêtre d’application sur l’affichage secondaire (1 pixel affiché pour chaque 4 pixels affiché par l’application).

  • Windows effectue par défaut la mise à l’échelle appropriée pour la densité de pixels et la distance d’affichage de l’affichage (Windows 7 et versions ultérieures, configurables par OEM).

  • Windows peut mettre automatiquement à l’échelle le contenu jusqu’à 250 % sur les nouveaux appareils qui dépassent 280 ppp (à partir de Windows 8.1 S14).

    Windows dispose d’un moyen de faire évoluer l’interface utilisateur pour tirer parti d’un nombre accru de pixels. Une application opte pour ce système en déclarant elle-même « prise en charge du DPI système ». Les applications qui ne le font pas sont mises à l’échelle par le système. Cela peut entraîner une expérience utilisateur « approximative » où l’ensemble de l’application est uniformément étiré en pixels. Par exemple :

    DPI Issues Fuzzy

    Visual Studio choisit d’être prenant en charge la mise à l’échelle DPI et n’est donc pas « virtualisé ».

    Windows (et Visual Studio) tirent parti de plusieurs technologies d’interface utilisateur, qui ont différentes façons de traiter les facteurs de mise à l’échelle définis par le système. Par exemple :

  • WPF mesure les contrôles de manière indépendante de l’appareil (unités, et non pixels). L’interface utilisateur WPF est automatiquement mise à l’échelle pour l’ppp actuel.

  • Toutes les tailles de texte, quelle que soit l’infrastructure de l’interface utilisateur, sont exprimées en points, et sont donc traitées par le système comme indépendantes des ppp. Le texte dans Win32, WinForms et WPF est déjà mis à l’échelle correctement lorsqu’il est dessiné sur l’appareil d’affichage.

  • Les dialogues win32/WinForms et les fenêtres ont des moyens d’activer la disposition qui se redimensionne avec du texte (par exemple, via des panneaux de grille, de flux et de disposition de tableau). Elles permettent d’éviter les emplacements de pixels codés en dur qui ne sont pas mis à l’échelle lorsque les tailles de police sont augmentées.

  • Les icônes fournies par le système ou les ressources en fonction des métriques système (par exemple, SM_CXICON et SM_CXSMICON) sont déjà mises à l’échelle.

Ancienne interface utilisateur Win32 (GDI, GDI+) et WinForms

Bien que WPF soit déjà très sensible aux PPP, une grande partie de notre code Win32/GDI n’a pas été écrite à l’origine avec la prise en compte des PPP. Windows a fourni des API de mise à l’échelle DPI. Les correctifs apportés aux problèmes Win32 doivent les utiliser de manière cohérente sur le produit. Visual Studio a fourni une bibliothèque de classes d’assistance pour éviter la duplication des fonctionnalités et garantir la cohérence entre le produit.

Images haute résolution

Cette section concerne principalement les développeurs qui étendent Visual Studio 2013. Pour Visual Studio 2015, utilisez le service d’images intégré à Visual Studio. Vous pouvez également constater que vous devez prendre en charge/cibler de nombreuses versions de Visual Studio et, par conséquent, l’utilisation du service d’images en 2015 n’est pas une option, car elle n’existe pas dans les versions précédentes. Cette section est également destinée à vous.

Montée en puissance des images trop petites

Les images trop petites peuvent être mises à l’échelle et rendues sur GDI et WPF à l’aide de méthodes courantes. Les classes d’assistance DPI managées sont disponibles pour les intégrateurs Visual Studio internes et externes pour traiter les icônes de mise à l’échelle, les bitmaps, les imagestrips et les listes d’images. Les helpers natifs Win32 C/C++sont disponibles pour la mise à l’échelle de HICON, HBITMAP, HIMAGELIST et VsUI ::GdiplusImage. La mise à l’échelle d’une bitmap nécessite généralement une modification d’une ligne après avoir inclus une référence à la bibliothèque d’assistance. Par exemple :

(WinForms) DpiHelper.LogicalToDeviceUnits(ref image);

La mise à l’échelle d’une liste d’images dépend de la fin de la liste d’images au moment du chargement ou de l’ajout au moment de l’exécution. Si vous êtes terminé au moment du chargement, appelez LogicalToDeviceUnits() avec la liste d’images comme vous le feriez pour une bitmap. Lorsque le code doit charger une image bitmap individuelle avant de composer la liste d’images, veillez à mettre à l’échelle la taille de l’image de la liste d’images :

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

Dans le code natif, les dimensions peuvent être mises à l’échelle lors de la création de la liste d’images comme suit :

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

Les fonctions de la bibliothèque permettent de spécifier l’algorithme de redimensionnement. Lors de la mise à l’échelle des images à placer dans des listes d’images, veillez à spécifier la couleur d’arrière-plan utilisée pour la transparence, ou utilisez la mise à l’échelle NearestNeighbor (ce qui entraînera des distorsions à 125 % et 150 %).

Consultez la DpiHelper documentation sur MSDN.

Le tableau suivant montre des exemples de mise à l’échelle des images à des facteurs de mise à l’échelle DPI correspondants. Les images décrites en orange indiquent notre meilleure pratique à partir de Visual Studio 2013 (mise à l’échelle 100 %-200 % DPI) :

DPI Issues Scaling

Problèmes de disposition

Les problèmes courants de disposition peuvent être évités principalement en conservant les points de l’interface utilisateur mis à l’échelle et par rapport à l’autre plutôt qu’en utilisant des emplacements absolus (en unités de pixels). Par exemple :

  • Les positions de disposition/texte doivent être ajustées pour prendre en compte les images mises à l’échelle.

  • Les colonnes dans les grilles doivent avoir des largeurs ajustées pour le texte mis à l’échelle.

  • Les tailles codées en dur ou l’espace entre les éléments doivent également être mises à l’échelle. Les tailles basées uniquement sur les dimensions de texte sont généralement correctes, car les polices sont automatiquement mises à l’échelle.

    Les fonctions d’assistance sont disponibles dans la classe pour permettre la mise à l’échelle DpiHelper sur l’axe X et Y :

  • LogicalToDeviceUnitsX/LogicalToDeviceUnitsY (les fonctions permettent la mise à l’échelle sur l’axe X/Y)

  • int space = DpiHelper.LogicalToDeviceUnitsX (10) ;

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

    Il existe des surcharges LogicalToDeviceUnits pour permettre la mise à l’échelle d’objets tels que Rect, Point et Size.

Utilisation de la bibliothèque/classe DPIHelper pour mettre à l’échelle les images et la disposition

La bibliothèque d’assistance DPI Visual Studio est disponible dans des formulaires natifs et managés et peut être utilisée en dehors de l’interpréteur de commandes Visual Studio par d’autres applications.

Pour utiliser la bibliothèque, accédez aux exemples d’extensibilité VSSDK Visual Studio et clonez l’exemple High-DPI_Images_Icons.

Dans les fichiers sources, incluez VsUIDpiHelper.h et appelez les fonctions statiques de VsUI::DpiHelper la classe :

#include "VsUIDpiHelper.h"

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

Remarque

N’utilisez pas les fonctions d’assistance dans les variables statiques au niveau du module ou au niveau de la classe. La bibliothèque utilise également des statiques pour la synchronisation de threads et vous pouvez rencontrer des problèmes d’initialisation par ordre. Convertissez ces statiques en variables membres non statiques ou en encapsulez-les dans une fonction (afin qu’elles soient construites sur le premier accès).

Pour accéder aux fonctions d’assistance DPI à partir du code managé qui s’exécute dans l’environnement Visual Studio :

  • Le projet consommateur doit référencer la dernière version de Shell MPF. Par exemple :

    <Reference Include="Microsoft.VisualStudio.Shell.14.0.dll" />
    
  • Vérifiez que le projet a des références à System.Windows.Forms, PresentationCore et PresentationUI.

  • Dans le code, utilisez l’espace de noms Microsoft.VisualStudio.PlatformUI et appelez des fonctions statiques de la classe DpiHelper. Pour les types pris en charge (points, tailles, rectangles, et ainsi de suite), il existe des fonctions d’extension fournies qui retournent de nouveaux objets mis à l’échelle. Par exemple :

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

Traitement de l’exactitude de l’image WPF dans l’interface utilisateur zoomable

Dans WPF, les bitmaps sont redimensionnées automatiquement par WPF pour le niveau de zoom PPP actuel à l’aide d’un algorithme bicubique de haute qualité (valeur par défaut), qui fonctionne bien pour les images ou les grandes captures d’écran, mais est inapproprié pour les icônes d’élément de menu, car elle introduit des flous perçues.

Recommandations :

  • Pour les illustrations d’image de logo et de bannières, le mode de redimensionnement par défaut BitmapScalingMode peut être utilisé.

  • Pour les éléments de menu et les images d’iconographie, il BitmapScalingMode doit être utilisé lorsqu’il n’entraîne pas d’autres artefacts de distorsion pour éliminer le flou (à 200 % et 300 %).

  • Pour les niveaux de zoom volumineux ne sont pas multiples de 100 % (par exemple, 250 % ou 350 %), mise à l’échelle d’images iconographiques avec des résultats bicubiques dans l’interface utilisateur floue et lavée. Un meilleur résultat est obtenu en mettant d’abord à l’échelle l’image avec NearestNeighbor vers le plus grand multiple de 100 % (par exemple, 200 % ou 300 %) et la mise à l’échelle avec bicubic à partir de là. Pour plus d’informations, consultez Cas spécial : précalage d’images WPF pour les niveaux d’ppp volumineux.

    La classe DpiHelper dans l’espace de noms Microsoft.VisualStudio.PlatformUI fournit un membre BitmapScalingMode qui peut être utilisé pour la liaison. Il permet à l’interpréteur de commandes Visual Studio de contrôler le mode de mise à l’échelle bitmap sur le produit de manière uniforme, en fonction du facteur de mise à l’échelle ppp.

    Pour l’utiliser en XAML, ajoutez :

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

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

L’interpréteur de commandes Visual Studio définit déjà cette propriété sur les fenêtres et les dialogues de niveau supérieur. L’interface utilisateur basée sur WPF exécutée dans Visual Studio l’héritera déjà. Si le paramètre ne se propage pas à vos éléments d’interface utilisateur particuliers, il peut être défini sur l’élément racine de l’interface utilisateur XAML/WPF. Les emplacements où cela se produit incluent des fenêtres contextuelles, sur des éléments avec des parents Win32 et des fenêtres de concepteur qui s’exécutent hors processus, telles que Blend.

Certaines interfaces utilisateur peuvent être mises à l’échelle indépendamment du niveau de zoom PPP défini par le système, comme l’éditeur de texte Visual Studio et les concepteurs WPF (Bureau WPF et Windows Store). Dans ces cas, DpiHelper.BitmapScalingMode ne doit pas être utilisé. Pour résoudre ce problème dans l’éditeur, l’équipe IDE a créé une propriété personnalisée intitulée RenderOptions.BitmapScalingMode. Définissez cette valeur de propriété sur HighQuality ou NearestNeighbor en fonction du niveau de zoom combiné du système et de votre interface utilisateur.

Cas spécial : précaling d’images WPF pour les niveaux d’ppp volumineux

Pour les niveaux de zoom très volumineux qui ne sont pas multiples de 100 % (par exemple, 250 %, 350 %, et ainsi de suite), la mise à l’échelle d’images iconographiques avec des résultats bicubiques aboutit à une interface utilisateur floue et lavée. L’impression de ces images avec du texte croustillant est presque comme celle d’une illusion optique. Les images semblent être plus proches de l’œil et hors focus par rapport au texte. Le résultat de mise à l’échelle à cette taille agrandie peut être amélioré en mettant d’abord à l’échelle l’image avec NearestNeighbor vers le plus grand multiple de 100 % (par exemple, 200 % ou 300 %) et la mise à l’échelle avec bicubic vers le reste (50 %).

Voici un exemple des différences dans les résultats, où la première image est mise à l’échelle avec l’algorithme de double mise à l’échelle amélioré 100%-200%->>250%, et le deuxième juste avec bicubic 100%->250%.

DPI Issues Double Scaling Example

Pour permettre à l’interface utilisateur d’utiliser cette double mise à l’échelle, le balisage XAML pour l’affichage de chaque élément Image doit être modifié. Les exemples suivants montrent comment utiliser la mise à l’échelle double dans WPF dans Visual Studio à l’aide de la bibliothèque DpiHelper et de Shell.12/14.

Étape 1 : Préscalez l’image à 200 %, 300 %, et ainsi de suite à l’aide de NearestNeighbor.

Préscalez l’image à l’aide d’un convertisseur appliqué à une liaison ou avec une extension de balisage XAML. Par exemple :

<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 l’image doit également être à thème (la plupart, si ce n’est pas tout, doit), le balisage peut utiliser un convertisseur différent qui effectue d’abord le thème de l’image, puis pré-mise à l’échelle. Le balisage peut utiliser ou DpiPrescaleThemedImageConverterDpiPrescaleThemedImageSourceConverter, selon la sortie de conversion souhaitée.

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

Étape 2 : Vérifiez que la taille finale est correcte pour l’ppp actuel.

Étant donné que WPF met à l’échelle l’interface utilisateur pour l’ppp actuel à l’aide de la propriété BitmapScalingMode définie sur UIElement, un contrôle Image utilisant une image préscaled, car sa source se présente deux ou trois fois plus grande que prévu. Voici quelques façons de contrer cet effet :

  • Si vous connaissez la dimension de l’image d’origine à 100 %, vous pouvez spécifier la taille exacte du contrôle Image. Ces tailles reflètent la taille de l’interface utilisateur avant l’application de la mise à l’échelle.

    <Image Source="{Binding Path=SelectedImage, Converter={StaticResource DpiPrescaleImageSourceConverter}}" Width="16" Height="16" />
    
  • Si la taille de l’image d’origine n’est pas connue, un LayoutTransform peut être utilisé pour effectuer un scale-down de l’objet Image final. Par exemple :

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

Activation de la prise en charge hdPI pour WebOC

Par défaut, les contrôles WebOC (tels que le contrôle WebBrowser dans WPF ou l’interface IWebBrowser2) n’activent pas la détection et la prise en charge hdPI. Le résultat est un contrôle incorporé avec du contenu d’affichage trop petit sur un affichage haute résolution. L’exemple suivant explique comment activer la prise en charge haute résolution dans une instance web WebOC spécifique.

Implémentez l’interface IDocHostUIHandler (consultez l’article MSDN sur 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);
    }

Si vous le souhaitez, implémentez l’interface ICustomDoc (consultez l’article MSDN sur ICustomDoc :

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

Associez la classe qui implémente IDocHostUIHandler au document du WebOC. Si vous avez implémenté l’interface ICustomDoc ci-dessus, dès que la propriété de document du WebOC est valide, effectuez un cast vers un ICustomDoc et appelez la méthode SetUIHandler, en passant la classe qui implémente 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 vous n’avez PAS implémenté l’interface ICustomDoc, dès que la propriété de document du WebOC est valide, vous devez la convertir en IOleObject et appeler la SetClientSite méthode, en passant la classe qui implémente IDocHostUIHandler. Définissez l’indicateur DOCHOSTUIFLAG_DPI_AWARE sur le DOCHOSTUIINFO passé à l’appel GetHostInfo de méthode :

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

Il doit s’agir de tout ce que vous devez obtenir votre contrôle WebOC pour prendre en charge HPDI.

Conseils

  1. Si la propriété de document sur le contrôle WebOC change, vous devrez peut-être réassocier le document à la classe IDocHostUIHandler.

  2. Si le problème ci-dessus ne fonctionne pas, il existe un problème connu avec le WebOC qui ne récupère pas la modification apportée à l’indicateur PPP. Le moyen le plus fiable de résoudre ce problème consiste à activer le zoom optique du WebOC, ce qui signifie que deux appels avec deux valeurs différentes pour le pourcentage de zoom. En outre, si cette solution de contournement est requise, il peut être nécessaire de l’effectuer sur chaque appel de navigation.

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