Ottimizzare il markup XAML

L'analisi del markup XAML per costruire gli oggetti in memoria richiede tempo per un'interfaccia utente complessa. Ecco alcune cose che puoi fare per migliorare i tempi di analisi e caricamento del markup XAML, nonché l'efficienza della memoria della tua app.

All'avvio dell'app, limita il markup XAML caricato ai soli elementi necessari per l'interfaccia utente iniziale. Esamina il markup nella pagina iniziale (incluse le risorse della pagina) e verifica di non stare caricando elementi aggiuntivi non immediatamente necessari. Questi elementi possono provenire da una vasta gamma di origini, ad esempio dizionari di risorse, elementi che sono stati inizialmente compressi ed elementi disegnati su altri elementi.

L'ottimizzazione del codice XAML per migliorare l'efficienza richiede di stabilire dei compromessi; non esiste sempre un'unica soluzione per ogni situazione. In questo caso esaminiamo alcuni problemi comuni e forniamo linee guida che puoi usare per stabilire i compromessi ideali per la tua app.

Ridurre al minimo il numero di elementi

Anche se la piattaforma XAML è in grado di visualizzare un numero elevato di elementi, puoi velocizzare il layout e il rendering dell'app usando il minor numero possibile di elementi per ottenere gli elementi visivi desiderati.

Le scelte effettuate riguardo al layout dei controlli dell'interfaccia utente influiranno sul numero di elementi dell'interfaccia utente creati all'avvio dell'app. Per altre informazioni dettagliate sull'ottimizzazione del layout, vedi Ottimizzare il layout XAML.

Il numero di elementi è estremamente importante nei modelli di dati perché ogni elemento viene nuovamente creato per ogni elemento di dati. Per informazioni sulla riduzione del numero di elementi in un elenco o griglia, vedi Riduzione degli elementi per ogni elemento nell'articolo Ottimizzazione dell'interfaccia utente in ListView e GridView.

In questo caso esaminiamo alcuni altri modi in cui puoi ridurre il numero di elementi che l'app deve caricare all'avvio.

Rinviare la creazione di elementi

Se il markup XAML contiene elementi che non visualizzi immediatamente, puoi posticipare il caricamento di questi elementi fino a quando non vengono visualizzati. Puoi ad esempio posticipare la creazione del contenuto non visibile, ad esempio di una scheda secondaria in un'interfaccia utente a schede. Oppure puoi visualizzare gli elementi in una visualizzazione griglia per impostazione predefinita, ma fornisci invece all'utente un'opzione per visualizzare i dati in un elenco. Puoi ritardare il caricamento dell'elenco fino a quando non è necessario.

Usa l'attributo x:Load anziché la proprietà Visibility per controllare quando viene visualizzato un elemento. Quando la visibilità di un elemento è impostata su Collapsed, verrà ignorato durante il passaggio di rendering, ma comunque paghi i costi dell'istanza dell'oggetto in memoria. Quando invece usi x:Load, il framework non creerà l'istanza dell'oggetto fino a quando non è necessario, pertanto i costi di memoria sono anche inferiori. Lo svantaggio è che paghi un piccolo overhead di memoria (circa 600 byte) quando l'interfaccia utente non è caricata.

Nota

Puoi ritardare il caricamento degli elementi usando l'attributo x:Load o x:DeferLoadStrategy. L'attributo x:Load è disponibile a partire da Windows 10 Creators Update (versione 1703, build dell'SDK 15063). La versione minima di destinazione del progetto Visual Studio deve essere Windows 10 Creators Update (10.0, Build 15063) per poter usare x:Load. Per gestire le versioni precedenti, usa x:DeferLoadStrategy.

Gli esempi seguenti mostrano la differenza nel numero di elementi e nell'uso della memoria quando vengono usate tecniche diverse per nascondere gli elementi dell'interfaccia utente. Un controllo ListView e un controllo GridView contenenti elementi identici vengono inseriti nella griglia radice di una pagina. ListView non è visibile, ma viene visualizzato il controllo GridView. Il codice XAML in ognuno di questi esempi genera la stessa interfaccia utente sullo schermo. Usiamo gli strumenti per profilatura e prestazioni di Visual Studio per controllare l'uso della memoria e il numero di elementi.

Opzione 1: inefficiente

In questo caso viene caricato il controllo ListView, ma non è visibile perché la sua larghezza è 0. Il controllo ListView e ognuno dei suoi elementi figlio viene creato nella struttura ad albero visuale e caricato in memoria.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE.-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <ListView x:Name="List1" Width="0">
        <ListViewItem>Item 1</ListViewItem>
        <ListViewItem>Item 2</ListViewItem>
        <ListViewItem>Item 3</ListViewItem>
        <ListViewItem>Item 4</ListViewItem>
        <ListViewItem>Item 5</ListViewItem>
        <ListViewItem>Item 6</ListViewItem>
        <ListViewItem>Item 7</ListViewItem>
        <ListViewItem>Item 8</ListViewItem>
        <ListViewItem>Item 9</ListViewItem>
        <ListViewItem>Item 10</ListViewItem>
    </ListView>

    <GridView x:Name="Grid1">
        <GridViewItem>Item 1</GridViewItem>
        <GridViewItem>Item 2</GridViewItem>
        <GridViewItem>Item 3</GridViewItem>
        <GridViewItem>Item 4</GridViewItem>
        <GridViewItem>Item 5</GridViewItem>
        <GridViewItem>Item 6</GridViewItem>
        <GridViewItem>Item 7</GridViewItem>
        <GridViewItem>Item 8</GridViewItem>
        <GridViewItem>Item 9</GridViewItem>
        <GridViewItem>Item 10</GridViewItem>
    </GridView>
</Grid>

Struttura ad albero visuale con il controllo ListView caricato. Il numero totale di elementi per la pagina è 89.

Screenshot of the visual tree with list view.

ListView e i suoi elementi figlio vengono caricati in memoria.

Screenshot of the Managed Memory Test App 1 dot E X E table showing ListView and its children are loaded into memory.

Opzione 2: migliore

In questo caso la visibilità del controllo ListView è impostata su Collapsed (l'altro XAML è identico all'oggetto originale). Il controllo ListView viene creato nella struttura ad albero visuale, ma non i suoi elementi figlio. Tuttavia, tali elementi figlio vengono caricati in memoria, quindi l'uso della memoria è identico all'esempio precedente.

<ListView x:Name="List1" Visibility="Collapsed">

Struttura ad albero visuale con il controllo ListView compresso. Il numero totale di elementi per la pagina è 46.

Screenshot of the visual tree with collapsed list view.

ListView e i suoi elementi figlio vengono caricati in memoria.

An updated screenshot of the Managed Memory Test App 1 dot E X E table showing ListView and its children are loaded into memory.

Opzione 3: massimamente efficiente

In questo caso il controllo ListView ha l'attributo x:Load impostato su False (l'altro XAML è identico all'oggetto originale). Il controllo ListView non viene creato nella struttura ad albero visuale o caricato in memoria all'avvio.

<ListView x:Name="List1" Visibility="Collapsed" x:Load="False">

Struttura ad albero visuale con il controllo ListView non caricato. Il numero totale di elementi per la pagina è 45.

Visual tree with list view not loaded

ListView e i suoi elementi figlio non vengono caricati in memoria.

Visual tree with list view

Nota

Il numero di elementi e l'uso della memoria in questi esempi sono molto ridotti e vengono visualizzati solo per illustrare il concetto. In questi esempi l'overhead dell'uso di x:Load è maggiore dei risparmi a livello di memoria, quindi l'app non trarrà vantaggio. Devi usare gli strumenti di profilatura dell'app per determinare se l'app trarrà vantaggio dal caricamento posticipato.

Usare le proprietà dei pannelli di layout

I pannelli di layout hanno una proprietà Background, quindi non occorre anteporre Rectangle a un pannello solo per applicarvi un colore.

Inefficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid>
    <Rectangle Fill="Black"/>
</Grid>

Efficiente

<Grid Background="Black"/>

I pannelli di layout hanno anche proprietà predefinite del bordo, pertanto non devi inserire un elemento Border attorno a un pannello di layout. Per altre info ed esempi, vedi Ottimizzare il layout XAML.

Usare le immagini al posto degli elementi basati su vettore

Se riutilizzi lo stesso elemento basato su vettori abbastanza volte, risulta più efficiente usare invece un elemento Image. Gli elementi basati su vettore possono essere più onerosi perché la CPU deve creare ogni singolo elemento separatamente. Il file di immagine deve essere decodificato una sola volta.

Ottimizzare le risorse e i dizionari di risorse

In genere usi i dizionari di risorse per archiviare, a livello globale, le risorse a cui vuoi fare riferimento in più posizioni dell'app. Ad esempio, stili, pennelli, modelli e così via.

In generale, abbiamo ottimizzato ResourceDictionary per evitare la creazione di istanze di risorse a meno che non vengano richieste. Ma ci sono casi in cui devi evitarlo in modo che non vengano create istanze delle risorse senza motivo.

Risorse con x:Name

Usa l'attributo x:Key per fare riferimento alle risorse. Qualsiasi risorsa con l'attributo x:Name non trarrà vantaggio dall'ottimizzazione della piattaforma, ma ne verrà invece creata un'istanza non appena si crea l'oggetto ResourceDictionary. Ciò si verifica perché X:Name indica alla piattaforma che l'app richiede l'accesso ai campi per questa risorsa, pertanto la piattaforma deve creare qualcosa per creare un riferimento.

ResourceDictionary in un UserControl

Esiste una penalità per la definizione di ResourceDictionary all'interno di un oggetto UserControl. La piattaforma crea una copia di tale oggetto ResourceDictionary per ogni istanza di UserControl. Nel caso di un UserControl che viene usato molto, sposta il ResourceDictionary fuori dall'oggetto UserControl e inseriscilo a livello di pagina.

Ambito di risorse e ResourceDictionary

Se una pagina fa riferimento a un controllo utente o a una risorsa definiti in un altro file, il framework analizza anche quel file.

In questo esempio, poiché InitialPage.xaml usa una risorsa di ExampleResourceDictionary.xaml, l'intero file ExampleResourceDictionary.xaml deve essere analizzato all'avvio.

InitialPage.xaml

<Page x:Class="ExampleNamespace.InitialPage" ...>
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ExampleResourceDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>

    <Grid>
        <TextBox Foreground="{StaticResource TextBrush}"/>
    </Grid>
</Page>

ExampleResourceDictionary.xaml

<ResourceDictionary>
    <SolidColorBrush x:Key="TextBrush" Color="#FF3F42CC"/>

    <!--This ResourceDictionary contains many other resources that
        are used in the app, but are not needed during startup.-->
</ResourceDictionary>

Se usi una risorsa in molte pagine in tutta l'app, la sua archiviazione in App.xaml è una procedura consigliata ed evita duplicati. Ma il file App.xaml viene analizzato all'avvio dell'app in modo che qualsiasi risorsa usata in una sola pagina (a meno che la pagina non sia la pagina iniziale) venga inserita tra le risorse locali della pagina. L'esempio illustra un file App.xaml contenente le risorse usate da una sola pagina, diversa dalla pagina iniziale. Ciò comporta un inutile aumento dei tempi di avvio.

App.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Application ...>
     <Application.Resources>
        <SolidColorBrush x:Key="DefaultAppTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="InitialPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="SecondPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="ThirdPageTextBrush" Color="#FF3F42CC"/>
    </Application.Resources>
</Application>

InitialPage.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.InitialPage" ...>
    <StackPanel>
        <TextBox Foreground="{StaticResource InitialPageTextBrush}"/>
    </StackPanel>
</Page>

SecondPage.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.SecondPage" ...>
    <StackPanel>
        <Button Content="Submit" Foreground="{StaticResource SecondPageTextBrush}"/>
    </StackPanel>
</Page>

Per rendere più efficiente questo esempio, sposta SecondPageTextBrush in SecondPage.xaml e quindi sposta ThirdPageTextBrush in ThirdPage.xaml. InitialPageTextBrush può rimanere in App.xaml perché le risorse dell'applicazione devono essere analizzate all'avvio dell'app in ogni caso.

Consolidare più pennelli simili in una singola risorsa

Il framework XAML cerca di memorizzare nella cache gli oggetti di uso comune in modo che possano essere riutilizzati il più spesso possibile. Tuttavia XAML non è in grado di stabilire facilmente se un pennello dichiarato in una parte del markup corrisponde al pennello dichiarato in un'altra parte. Questo esempio usa SolidColorBrush a titolo dimostrativo, ma è più probabile e più significativo il caso con GradientBrush. Controlla anche se sono presenti pennelli che usano i colori predefiniti, ad esempio "Orange" e "#FFFFA500" sono lo stesso colore.

Inefficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page ... >
    <StackPanel>
        <TextBlock>
            <TextBlock.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </TextBlock.Foreground>
        </TextBlock>
        <Button Content="Submit">
            <Button.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </Button.Foreground>
        </Button>
    </StackPanel>
</Page>

Per risolvere la duplicazione, definisci il pennello come risorsa. Se i controlli in altre pagine usano lo stesso pennello, spostalo in App.xaml.

Efficiente

<Page ... >
    <Page.Resources>
        <SolidColorBrush x:Key="BrandBrush" Color="#FFFFA500"/>
    </Page.Resources>

    <StackPanel>
        <TextBlock Foreground="{StaticResource BrandBrush}" />
        <Button Content="Submit" Foreground="{StaticResource BrandBrush}" />
    </StackPanel>
</Page>

Ridurre al minimo la sovrapposizione

La sovrapposizione, nota anche come overdrawing, si verifica quando più oggetti vengono disegnati negli stessi pixel dello schermo. Tieni presente che a volte è necessario un compromesso tra questa indicazione e la volontà di ridurre al minimo numero di elementi.

Usa DebugSettings.IsOverdrawHeatMapEnabled come strumento di diagnostica visiva. Potresti trovare oggetti che vengono disegnati, ma dei quali non sospettavi l'esistenza nella scena.

Elementi trasparenti o nascosti

Se un elemento non è visibile perché è trasparente o nascosto dietro altri elementi, e non contribuisce al layout, allora eliminalo. Se l'elemento non è visibile nello stato di visualizzazione iniziale, ma è visibile in altri stati di visualizzazione, allora usa x:Load per controllarne lo stato o imposta Visibility su Collapsed per l'elemento stesso e modifica il valore in Visible negli stati appropriati. Ci saranno eccezioni per questo approccio euristico. In generale, è preferibile impostare in locale nell'elemento il valore che ha una proprietà nella maggior parte degli stati di visualizzazione.

Elementi compositi

Usa un elemento composito invece di disporre gli oggetti su più livelli per creare un effetto. In questo esempio il risultato è una forma con due toni, in cui la metà superiore è nera (dallo sfondo del controllo Grid) e quella inferiore è grigia (dal Rectangle bianco semitrasparente con fusione alfa sullo sfondo nero di Grid). In questo caso, viene riempito il 150% dei pixel necessari per ottenere il risultato.

Inefficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Black">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Grid.Row="1" Fill="White" Opacity=".5"/>
</Grid>

Efficiente

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Fill="Black"/>
    <Rectangle Grid.Row="1" Fill="#FF7F7F7F"/>
</Grid>

Pannelli di layout

Un pannello di layout può avere due scopi: colorare un'area e definire il layout degli elementi figlio. Se un elemento in secondo piano nell'ordine z sta già colorando un'area, non occorre che un pannello di layout in primo piano colori la stessa area, ma può invece concentrarsi sul layout dei rispettivi elementi figlio. Ecco un esempio.

Inefficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Background="Blue"/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Efficiente

<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Se il controllo Grid deve supportare hit test, imposta un valore di sfondo trasparente.

Bordi

Usa un elemento Border per disegnare un bordo attorno a un oggetto. In questo esempio, viene usato un controllo Grid come bordo improvvisato attorno a un controllo TextBox. Ma questo significa una sovrapposizione per tutti i pixel nella cella centrale.

Inefficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Blue" Width="300" Height="45">
    <Grid.RowDefinitions>
        <RowDefinition Height="5"/>
        <RowDefinition/>
        <RowDefinition Height="5"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition/>
        <ColumnDefinition Width="5"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Row="1" Grid.Column="1"></TextBox>
</Grid>

Efficiente

<Border BorderBrush="Blue" BorderThickness="5" Width="300" Height="45">
    <TextBox/>
</Border>

Margini

Ricordati dei margini. Due elementi adiacenti potrebbero sovrapporsi (forse accidentalmente) se i margini negativi si estendono nei limiti di rendering reciproci causando la sovrapposizione.

Memorizzare nella cache il contenuto statico

Un'altra origine di sovrapposizione è una forma composta da molti elementi sovrapposti. Se imposti CacheMode su BitmapCache nell'oggetto UIElement che contiene la forma composita, la piattaforma esegue il rendering dell'elemento in una bitmap una volta e quindi usa tale bitmap per ogni fotogramma invece della sovrapposizione.

Inefficiente

<Canvas Background="White">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Venn diagram with three solid circles

L'immagine precedente è il risultato, ma ecco una mappa delle aree con sovrapposizione. Il rosso più scuro indica le sovrapposizioni maggiori.

Venn diagram that shows overlapping areas

Efficiente

<Canvas Background="White" CacheMode="BitmapCache">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Nota l’uso di CacheMode. Non usare questa tecnica se sono presenti forme secondarie animate, perché è probabile che sia necessario rigenerare la cache delle bitmap per ogni fotogramma, vanificando lo scopo.

Usare XBF2

XBF2 è una rappresentazione binaria del markup XAML che consente di evitare tutti i costi di analisi del testo in fase di esecuzione. Ottimizza inoltre i file binari per il caricamento e la creazione della struttura ad albero e consente "fast-path" per i tipi XAML per migliorare i costi di creazione di heap e oggetti, ad esempio VSM ResourceDictionary, stili e così via. È completamente mappato alla memoria, quindi non esiste alcun footprint dell'heap per il caricamento e la lettura di una pagina XAML. Inoltre, consente di ridurre il footprint del disco delle pagine XAML memorizzate in un pacchetto appx. XBF2 è una rappresentazione più compatta e può ridurre il footprint del disco rispetto ai file XAML/XBF1 fino al 50%. Ad esempio, per l'app Foto predefinita è stata possibile una riduzione di circa il 60% dopo la conversione in XBF2, con una conseguente diminuzione delle dimensioni da circa 1 MB per gli asset XBF1 a circa 400 KB per gli asset XBF2. Abbiamo anche riscontrato vantaggi per le app ovunque dal 15 al 20% nella CPU e dal 10 al 15% nell'heap Win32.

I dizionari e i controlli predefiniti XAML forniti dal framework sono già completamente abilitati per XBF2. Per la tua app, assicurati che il file del progetto dichiari TargetPlatformVersion 8.2 o versione successiva.

Per verificare se hai a disposizione XBF2, apri l'app in un editor binario. Se hai XBF2, i byte 12 e 13 sono 00 02.