Risolvere i problemi relativi ai valori DPI

Un numero crescente di dispositivi viene distribuito con schermi ad alta risoluzione. Queste schermate hanno in genere più di 200 pixel per pollice (ppi). L'uso di un'applicazione in questi computer richiede che il contenuto venga ridimensionato per soddisfare le esigenze di visualizzazione del contenuto a una normale distanza di visualizzazione per il dispositivo. A partire dal 2014, la destinazione principale per gli schermi ad alta densità è costituita dai dispositivi di mobile computing (tablet, portatili a clamshell e telefoni).

Windows 8.1 e versioni successive contiene diverse funzionalità per consentire a questi computer di lavorare con schermi e ambienti in cui il computer è collegato a schermi ad alta densità e densità standard contemporaneamente.

  • Windows consente di ridimensionare il contenuto nel dispositivo usando l'impostazione "Rendi testo e altri elementi più grandi o più piccoli" (disponibile a partire da Windows XP).

  • Windows 8.1 e versioni successive ridimensionano automaticamente il contenuto per la maggior parte delle applicazioni in modo che siano coerenti quando vengono spostati tra schermi di densità di pixel diverse. Quando la visualizzazione primaria è ad alta densità (ridimensionamento del 200%) e la visualizzazione secondaria è densità standard (100%), Windows ridimensiona automaticamente il contenuto della finestra dell'applicazione sullo schermo secondario (1 pixel visualizzato per ogni 4 pixel di cui viene eseguito il rendering dall'applicazione).

  • Per impostazione predefinita, Windows avrà il ridimensionamento corretto per la densità di pixel e la distanza di visualizzazione per lo schermo (Windows 7 e versioni successive configurabili dall'OEM).

  • Windows può aumentare automaticamente il contenuto fino al 250% nei nuovi dispositivi che superano i 280 ppi (a partire da Windows 8.1 S14).

    Windows ha un modo per gestire l'aumento dell'interfaccia utente per sfruttare i vantaggi di un aumento del numero di pixel. Un'applicazione acconsente esplicitamente a questo sistema dichiarando di essere "compatibile con dpi di sistema". Le applicazioni che non eseguono questa operazione vengono ridimensionate dal sistema. Ciò può comportare un'esperienza utente "fuzzy" in cui l'intera applicazione è estesa in modo uniforme in pixel. Ad esempio:

    DPI Issues Fuzzy

    Visual Studio acconsente esplicitamente a essere compatibile con il ridimensionamento DPI e pertanto non è "virtualizzato".

    Windows (e Visual Studio) sfruttano diverse tecnologie dell'interfaccia utente, che hanno modi diversi per gestire i fattori di ridimensionamento impostati dal sistema. Ad esempio:

  • WPF misura i controlli in modo indipendente dal dispositivo (unità, non pixel). L'interfaccia utente WPF aumenta automaticamente le prestazioni per il valore DPI corrente.

  • Tutte le dimensioni del testo indipendentemente dal framework dell'interfaccia utente sono espresse in punti e quindi vengono considerate dal sistema come indipendenti dal DPI. Il testo in Win32, WinForms e WPF aumenta già correttamente quando viene disegnato nel dispositivo di visualizzazione.

  • Finestre e finestre di dialogo Win32/WinForms consentono di abilitare il layout che viene ridimensionato con testo(ad esempio, tramite griglia, flusso e pannelli di layout tabella). Questi consentono di evitare posizioni pixel hardcoded che non vengono ridimensionate quando vengono aumentate le dimensioni del carattere.

  • Le icone fornite dal sistema o dalle risorse in base alle metriche di sistema (ad esempio, SM_CXICON e SM_CXSMICON) sono già state ridimensionate.

Interfaccia utente basata su Win32 (GDI, GDI+) e WinForms

Anche se WPF è già compatibile con dpi elevati, gran parte del codice basato su Win32/GDI non è stato originariamente scritto con consapevolezza DPI. Windows ha fornito API di ridimensionamento DPI. Le correzioni ai problemi di Win32 devono essere usate in modo coerente nel prodotto. Visual Studio ha fornito una libreria di classi helper per evitare la duplicazione delle funzionalità e garantire la coerenza tra il prodotto.

Immagini ad alta risoluzione

Questa sezione è destinata principalmente agli sviluppatori che estendono Visual Studio 2013. Per Visual Studio 2015, usare il servizio immagini integrato in Visual Studio. È anche possibile che sia necessario supportare o impostare come destinazione molte versioni di Visual Studio e pertanto l'uso del servizio immagini nel 2015 non è un'opzione perché non esiste nelle versioni precedenti. Questa sezione è disponibile anche per te.

Ridimensionamento delle immagini troppo piccole

Le immagini troppo piccole possono essere ridimensionate e sottoposte a rendering su GDI e WPF usando alcuni metodi comuni. Le classi helper DPI gestite sono disponibili per gli integratori interni ed esterni di Visual Studio per gestire icone di ridimensionamento, bitmap, immaginitrip e elenchi di immagini. Gli helper C/C++nativi basati su Win32 sono disponibili per il ridimensionamento di HICON, HBITMAP, HIMAGELIST e VsUI::GdiplusImage. Il ridimensionamento di una bitmap richiede in genere solo una modifica di una riga dopo aver incluso un riferimento alla libreria helper. Ad esempio:

(WinForms) DpiHelper.LogicalToDeviceUnits(ref image);

Il ridimensionamento di un elenco di immagini dipende dal fatto che l'elenco di immagini sia stato completato in fase di caricamento o venga accodato in fase di esecuzione. Se viene completato in fase di caricamento, chiamare LogicalToDeviceUnits() con l'elenco di immagini come si farebbe con una bitmap. Quando il codice deve caricare una singola bitmap prima di comporre l'elenco di immagini, assicurarsi di ridimensionare le dimensioni dell'immagine dell'elenco di immagini:

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

Nel codice nativo, le dimensioni possono essere ridimensionate durante la creazione dell'elenco di immagini come indicato di seguito:

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

Le funzioni nella libreria consentono di specificare l'algoritmo di ridimensionamento. Quando si ridimensionano le immagini da inserire negli elenchi di immagini, assicurarsi di specificare il colore di sfondo usato per la trasparenza o di usare il ridimensionamento NearestNeighbor (che causerà distorsioni al 125% e al 150%).

Consultare la DpiHelper documentazione su MSDN.

La tabella seguente mostra esempi di come le immagini devono essere ridimensionate in base ai fattori di ridimensionamento DPI corrispondenti. Le immagini descritte in arancione indicano la procedura consigliata a partire da Visual Studio 2013 (ridimensionamento DPI del 100%-200%):

DPI Issues Scaling

Problemi di layout

I problemi di layout comuni possono essere evitati principalmente mantenendo i punti nell'interfaccia utente ridimensionati e rispetto l'uno all'altro anziché usando posizioni assolute (in particolare, in unità pixel). Ad esempio:

  • Le posizioni layout/testo devono essere modificate per tenere conto delle immagini con scalabilità orizzontale.

  • Le colonne nelle griglie devono avere larghezze regolate per il testo con scalabilità verticale.

  • Anche le dimensioni hardcoded o lo spazio tra gli elementi dovranno essere ridimensionati. Le dimensioni basate solo sulle dimensioni del testo sono in genere corrette, perché i tipi di carattere vengono ridimensionati automaticamente.

    Le funzioni helper sono disponibili nella DpiHelper classe per consentire il ridimensionamento sull'asse X e Y:

  • LogicalToDeviceUnitsX/LogicalToDeviceUnitsY (le funzioni consentono il ridimensionamento sull'asse X/Y)

  • int space = DpiHelper.LogicalToDeviceUnitsX (10);

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

    Esistono overload LogicalToDeviceUnits per consentire il ridimensionamento di oggetti come Rect, Point e Size.

Uso della libreria/classe DPIHelper per ridimensionare immagini e layout

La libreria helper DPI di Visual Studio è disponibile in moduli nativi e gestiti e può essere usata all'esterno della shell di Visual Studio da altre applicazioni.

Per usare la libreria, passare agli esempi di estendibilità di Visual Studio VSSDK e clonare l'esempio high-DPI_Images_Icons.

Nei file di origine includere VsUIDpiHelper.h e chiamare le funzioni statiche della VsUI::DpiHelper classe:

#include "VsUIDpiHelper.h"

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

Nota

Non usare le funzioni helper nelle variabili statiche a livello di modulo o a livello di classe. La libreria usa anche statiche per la sincronizzazione dei thread ed è possibile che si verifichino problemi di inizializzazione degli ordini. Convertire tali statici in variabili membro non statiche o eseguirne il wrapping in una funzione (in modo che vengano costruite al primo accesso).

Per accedere alle funzioni helper DPI dal codice gestito che verrà eseguito all'interno dell'ambiente di Visual Studio:

  • Il progetto di utilizzo deve fare riferimento alla versione più recente di SHELL MPF. Ad esempio:

    <Reference Include="Microsoft.VisualStudio.Shell.14.0.dll" />
    
  • Verificare che il progetto disponga di riferimenti a System.Windows.Forms, PresentationCore e PresentationUI.

  • Nel codice usare lo spazio dei nomi Microsoft.VisualStudio.PlatformUI e chiamare funzioni statiche della classe DpiHelper. Per i tipi supportati (punti, dimensioni, rettangoli e così via), sono disponibili funzioni di estensione che restituiscono nuovi oggetti ridimensionati. Ad esempio:

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

Gestione della fuzza dell'immagine WPF nell'interfaccia utente ingrandita

In WPF le bitmap vengono ridimensionate automaticamente da WPF per il livello di zoom DPI corrente usando un algoritmo bicubico di alta qualità (impostazione predefinita), che funziona bene per le immagini o gli screenshot di grandi dimensioni, ma è inappropriato per le icone delle voci di menu perché introduce una fuzzità percepita.

Raccomandazioni:

  • Per le immagini del logo e le immagini banner, è possibile usare la modalità di ridimensionamento predefinita BitmapScalingMode .

  • Per le voci di menu e le immagini iconografiche, è BitmapScalingMode consigliabile usare quando non provoca altri artefatti di distorsione per eliminare la fuzza (al 200% e al 300%).

  • Per livelli di zoom di grandi dimensioni non multipli del 100% (ad esempio, 250% o 350%), ridimensionare immagini iconografiche con risultati bicubici in un'interfaccia utente fuzzy e lavata. Un risultato migliore viene ottenuto ridimensionando prima l'immagine con NearestNeighbor al multiplo più grande del 100% (ad esempio, 200% o 300%) e ridimensionando con bicubica da lì. Per altre informazioni, vedere Caso speciale: prescallamento delle immagini WPF per livelli DPI di grandi dimensioni.

    La classe DpiHelper nello spazio dei nomi Microsoft.VisualStudio.PlatformUI fornisce un membro BitmapScalingMode che può essere usato per l'associazione. Consentirà alla shell di Visual Studio di controllare in modo uniforme la modalità di ridimensionamento bitmap nel prodotto, a seconda del fattore di ridimensionamento DPI.

    Per usarlo in XAML, aggiungere:

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

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

La shell di Visual Studio imposta già questa proprietà nelle finestre e nei dialoghi di primo livello. L'interfaccia utente basata su WPF in esecuzione in Visual Studio lo erediterà già. Se l'impostazione non viene propagata alle parti dell'interfaccia utente specifiche, può essere impostata sull'elemento radice dell'interfaccia utente XAML/WPF. Le posizioni in cui questo accade includono popup, sugli elementi con elementi padre Win32 e finestre di progettazione che esauriscono il processo, ad esempio Blend.

Alcune interfacce utente possono essere ridimensionate indipendentemente dal livello di zoom DPI impostato dal sistema, ad esempio l'editor di testo di Visual Studio e le finestre di progettazione basate su WPF (Desktop WPF e Windows Store). In questi casi, DpiHelper.BitmapScalingMode non deve essere usato. Per risolvere questo problema nell'editor, il team dell'IDE ha creato una proprietà personalizzata denominata RenderOptions.BitmapScalingMode. Impostare il valore della proprietà su HighQuality o NearestNeighbor a seconda del livello di zoom combinato del sistema e dell'interfaccia utente.

Caso speciale: prescallamento delle immagini WPF per livelli DPI di grandi dimensioni

Per livelli di zoom molto grandi che non sono multipli del 100% (ad esempio, 250%, 350%e così via), ridimensionare le immagini iconografia con risultati bicubici in un'interfaccia utente fuzzy e lavata. L'impressione di queste immagini insieme a testo nitido è quasi come quella di un'illusione ottica. Le immagini sembrano essere più vicine all'occhio e allo stato attivo in relazione al testo. Il risultato della scalabilità a questa dimensione di ingrandimento può essere migliorato ridimensionando prima l'immagine con NearestNeighbor al multiplo più grande del 100% (ad esempio, 200% o 300%) e ridimensionando con bicubica al resto (un ulteriore 50%).

Di seguito è riportato un esempio delle differenze nei risultati, in cui la prima immagine viene ridimensionata con l'algoritmo di ridimensionamento doppio migliorato 100%->200%->250%, e il secondo solo con bicubica 100%->250%.

DPI Issues Double Scaling Example

Per consentire all'interfaccia utente di usare questo doppio ridimensionamento, è necessario modificare il markup XAML per la visualizzazione di ogni elemento Image. Gli esempi seguenti illustrano come usare il doppio ridimensionamento in WPF in Visual Studio usando la libreria DpiHelper e Shell.12/14.

Passaggio 1: Prescale the image to 200%, 300%, and so on using NearestNeighbor.Step 1: Prescale the image to 200%, 300%, and so on using NearestNeighbor.

Prescalare l'immagine usando un convertitore applicato a un'associazione o con un'estensione di markup XAML. Ad esempio:

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

Se l'immagine deve anche essere a tema (la maggior parte, se non tutte, dovrebbe), il markup può usare un convertitore diverso che esegue prima il tema dell'immagine e quindi la pre-ridimensionamento. Il markup può usare DpiPrescaleThemedImageConverter o DpiPrescaleThemedImageSourceConverter, a seconda dell'output di conversione desiderato.

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

Passaggio 2: Verificare che le dimensioni finali siano corrette per il dpi corrente.

Poiché WPF ridimensiona l'interfaccia utente per il valore DPI corrente usando la proprietà BitmapScalingMode impostata su UIElement, un controllo Image che usa un'immagine con scalabilità predefinita perché l'origine avrà un aspetto due o tre volte maggiore di quanto dovrebbe. Di seguito sono riportati alcuni modi per contrastare questo effetto:

  • Se si conosce la dimensione dell'immagine originale al 100%, è possibile specificare le dimensioni esatte del controllo Immagine. Queste dimensioni rifletteranno le dimensioni dell'interfaccia utente prima dell'applicazione del ridimensionamento.

    <Image Source="{Binding Path=SelectedImage, Converter={StaticResource DpiPrescaleImageSourceConverter}}" Width="16" Height="16" />
    
  • Se le dimensioni dell'immagine originale non sono note, è possibile usare layoutTransform per ridurre l'oggetto Image finale. Ad esempio:

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

Abilitazione del supporto HDPI per WebOC

Per impostazione predefinita, i controlli WebOC (ad esempio il controllo WebBrowser in WPF o l'interfaccia IWebBrowser2) non abilitano il rilevamento e il supporto di HDPI. Il risultato sarà un controllo incorporato con contenuto visualizzato troppo piccolo su uno schermo ad alta risoluzione. Di seguito viene descritto come abilitare il supporto dpi elevato in un'istanza WebOC specifica.

Implementare l'interfaccia IDocHostUIHandler (vedere l'articolo MSDN su 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);
    }

Facoltativamente, implementare l'interfaccia ICustomDoc (vedere l'articolo MSDN su ICustomDoc:

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

Associare la classe che implementa IDocHostUIHandler al documento del WebOC. Se è stata implementata l'interfaccia ICustomDoc precedente, non appena la proprietà del documento webOC è valida, eseguirne il cast a un ICustomDoc e chiamare il metodo SetUIHandler, passando la classe che 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);

Se non è stata implementata l'interfaccia ICustomDoc, non appena la proprietà del documento webOC è valida, sarà necessario eseguirne il cast a un IOleObject e chiamare il SetClientSite metodo , passando la classe che implementa IDocHostUIHandler. Impostare il flag DOCHOSTUIFLAG_DPI_AWARE in DOCHOSTUIINFO passato alla chiamata al GetHostInfo metodo:

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

Questo dovrebbe essere tutto ciò che è necessario ottenere il controllo WebOC per supportare HPDI.

Suggerimenti

  1. Se la proprietà del documento nel controllo WebOC cambia, potrebbe essere necessario riassociare il documento con la classe IDocHostUIHandler.

  2. Se il codice precedente non funziona, si verifica un problema noto con il WebOC che non rileva la modifica al flag DPI. Il modo più affidabile per risolvere questo problema consiste nell'attivare o disattivare lo zoom ottico del WebOC, ovvero due chiamate con due valori diversi per la percentuale di zoom. Inoltre, se questa soluzione alternativa è necessaria, potrebbe essere necessario eseguirla in ogni chiamata di navigazione.

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