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


Optimize loading XAML (XAML)

Parsing XAML and creating the corresponding objects in memory can be time consuming for a complex UI. Here are some things you can do to make your app load XAML quicker when you write your Windows Store app using C++, C#, or Visual Basic for Windows 8.

We recommend that you load only the XAML that you need to get past the startup process, for example, load the pages needed to display the initial UI. Carefully examine the XAML of your first page to make sure it contains everything it needs. If your first page references a control or style that you define in a different file, the framework parses that file too.

Consider this example. It shows the first page that an app displays and part of AppStyles.Xaml. The page uses the resource, TextColor, which is defined in AppStyles.xaml. This means that AppStyles.xaml must be parsed when this page is loaded. Because AppStyles.xaml contains many app resources used throughout the app, it will take time to parse all of those resources which are not necessary to start the app.

Contents of the main page.

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

Contents of AppStyles.xaml.

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

    <!--This ResourceDictionary contains many other resources that
    used in the app, but they aren't used during startup.-->
</ResourceDictionary>

Trim resource dictionaries. If you use a resource throughout an app, then store it in the Application object to avoid duplication. But if you use a resource in a single page that is not the initial page, put the resource in the resource dictionary of that page. This reduces the amount of XAML the framework parses when the app starts. The framework parses the rest of the XAML only when a user navigates to the specific page.

Here is a example of a bad practice. Don’t include XAML that is specific to a page in the app’s resource dictionary. The app incurs the cost of parsing resources that are not immediately necessary at startup instead of on demand.

<Application ...> <!-- BAD CODE DO NOT USE.-->
     <Application.Resources>  <!-- BAD CODE DO NOT USE.-->
        <SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/> <!-- BAD CODE DO NOT USE.-->
        <SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/> <!-- BAD CODE DO NOT USE.-->
        <SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/> <!-- BAD CODE DO NOT USE.-->
        <SolidColorBrush x:Key="ThirdPageTextColor" Color="#FF3F42CC"/> <!-- BAD CODE DO NOT USE.-->
    </Application.Resources> <!-- BAD CODE DO NOT USE.-->
</Application> <!-- BAD CODE DO NOT USE.-->

XAML for the first page of the app.

<Page ...>  <!-- BAD CODE DO NOT USE.-->   
    <StackPanel>  <!-- BAD CODE DO NOT USE.-->
        <TextBox Foreground="{StaticResource HomePageTextColor}"/> <!-- BAD CODE DO NOT USE.-->
    </StackPanel> <!-- BAD CODE DO NOT USE.-->
</Page> <!-- BAD CODE DO NOT USE.-->
    

XAML for the second page of the app.

<Page ...>  <!-- BAD CODE DO NOT USE.-->
    <StackPanel> <!-- BAD CODE DO NOT USE.-->
        <Button Content="Submit" Foreground="{StaticResource SecondPageColor}" /> <!-- BAD CODE DO NOT USE.-->
    </StackPanel> <!-- BAD CODE DO NOT USE.-->
</Page> <!-- BAD CODE DO NOT USE.-->

Move page-specific resources to the resource dictionary for that page. This example defines the SecondPageTextColor resource on the same page that uses it. It keeps the resource, HomePageTextColor, in app.xaml because it must be parsed at start up anyway. You can also move the resource to the resource dictionary of the first page.

<Application ...>
    <Application.Resources>
        <SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
    </Application.Resources>
</Application>

XAML for the second page of the app.

<Page ...>    
    <StackPanel>
        <TextBox Foreground="{StaticResource HomePageTextColor}" />
    </StackPanel>
</Page>

XAML for the first page of the app.

<Page ...>
    <Page.Resources>
        <SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
    </Page.Resources>
    
    <StackPanel>
        <Button Content="Submit" Foreground="{StaticResource SecondPageTextColor}" />
    </StackPanel>
</Page>

Optimize element count

The XAML framework can display thousands of objects, and you can make your app layout and render scenes faster by reducing the number of elements on a page. Here are a few tricks that you can use to reduce a scene’s element count while maintaining the same level of visual complexity.

  • Avoid unnecessary elements. For example, set the Background property on panels to provide a background color instead of placing a colored Rectangle behind the elements in that panel.

    Don't do this.

    <Grid> <!-- BAD CODE DO NOT USE.-->
        <Rectangle Fill="Black"/> <!-- BAD CODE DO NOT USE.-->
    </Grid> <!-- BAD CODE DO NOT USE.-->
    

    Instead, do this.

    <Grid Background="Black" />
    
  • If elements aren't visible because they are transparent or hidden behind other elements, remove them or set the Visibility property to Collapsed if other visual states use them.

  • If you reuse the same vector based element multiple times, make it an image. Vector-based elements are potentially more expensive because the CPU must create each individual element separately, but the image needs to be decoded only once.

Reuse identical brushes

The XAML framework tries to cache commonly used objects so that they can be reused as often as possible. But XAML cannot easily tell if a brush declared in one template is the same as a brush declared in a different template. Create commonly used brushes as root ResourceDictionary elements and then refer to that object in templates as needed. XAML will be able to use the same object across the different templates and memory consumption will decrease. This trick is especially important for GradientBrush objects, which are more expensive than SolidColorBrush objects.

Avoid defining the same brush in multiple controls like this.

 <!-- BAD CODE DO NOT USE.-->
<Page ...>  <!-- BAD CODE DO NOT USE.-->
    <StackPanel>  <!-- BAD CODE DO NOT USE.-->
        <TextBox>  <!-- BAD CODE DO NOT USE.-->
            <TextBox.Foreground>  <!-- BAD CODE DO NOT USE.-->
                <SolidColorBrush Color="#FF3F42CC"/> <!-- BAD CODE DO NOT USE.-->
            </TextBox.Foreground> <!-- BAD CODE DO NOT USE.-->
        </TextBox> <!-- BAD CODE DO NOT USE.-->
        <Button Content="Submit"> <!-- BAD CODE DO NOT USE.-->
            <Button.Foreground> <!-- BAD CODE DO NOT USE.-->
                <SolidColorBrush Color="#FF3F42CC"/> <!-- BAD CODE DO NOT USE.-->
            </Button.Foreground> <!-- BAD CODE DO NOT USE.-->
        </Button> <!-- BAD CODE DO NOT USE.-->
    </StackPanel> <!-- BAD CODE DO NOT USE.-->
</Page> <!-- BAD CODE DO NOT USE.-->

Instead, define the common brush once and reference it as needed, like this. If controls in other pages use the same brush, move it to app.xaml so the app doesn't duplicate it.

<Page ...>
    <Page.Resources>
        <SolidColorBrush x:Key="TextColor" Color="#FF3F42CC"/>
    </Page.Resources>
    
    <StackPanel>
        <TextBox Foreground="{StaticResource TextColor}" />
        <Button Content="Submit" Foreground="{StaticResource TextColor}" />
    </StackPanel>
</Page>

Minimize redrawing to the same place on the screen

It’s much faster to let graphics hardware draw each shape to the screen buffer than cull occluded parts of objects. Because each object is drawn, the total amount of work the graphics hardware does can be reduced by not drawing elements that are completely obscured. Of course you are not expected to keep track of the visibility of all objects, but you can do a few easy things to prevent the same pixel from being drawn multiple times unnecessarily.

  • Collapse elements that are entirely obscured by others in the foreground.

  • Create a composite element instead of layering objects to create an effect. This example of un-optimized XAML creates a two toned background for the Grid element. The top half is black and the bottom half is gray. The example places a semi transparent white Rectangle in the bottom half of the grid and let the black background of the Grid blend with the with Rectangle. This design causes the graphics card to write all pixels on the screen black when it draws the Grid and then write all of the pixels for the white Rectangle (half the size of the screen). The total number of pixels filled is 1.5 times the screen resolution.

    <Grid Background="Black"> <!-- BAD CODE DO NOT USE.-->
        <Grid.RowDefinitions> <!-- BAD CODE DO NOT USE.-->
            <RowDefinition Height="*"/> <!-- BAD CODE DO NOT USE.-->
            <RowDefinition Height="*"/> <!-- BAD CODE DO NOT USE.-->
        </Grid.RowDefinitions> <!-- BAD CODE DO NOT USE.-->
        <Rectangle Grid.Row="1" Fill="White" Opacity=".5"/> <!-- BAD CODE DO NOT USE.-->
    </Grid> <!-- BAD CODE DO NOT USE.-->
    

    This example of optimized XAML creates the same two toned background for the Grid element, but with 33% fewer pixel fills. The only things that are drawn are the top Rectangle (half the screen) and the bottom Rectangle (half the screen). The total number of pixels filled is equal to the screen resolution. The background for the grid is never drawn because it’s transparent

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Rectangle Grid.Row="0" Fill="Black"/>
        <Rectangle Grid.Row="1" Fill="#FF7F7F7F"/>
    </Grid>
    
  • Don't use the same color for foreground and background elements. Instead don't set color of the foreground elements and let the background color bleed through.

    This XAML creates a GridView with a blue background and sets the background of the DataTemplate for each item to be blue too. Setting the background (to anything except Transparent) on the DataTemplate causes pixels to be filled for the background of each item. In this case it is unnecessary because the background of the GridView and the background of the items is the same color.

    Don't do this.

    <!-- BAD CODE DO NOT USE.-->
    <GridView Background="Blue">  <!-- BAD CODE DO NOT USE.-->
        <GridView.ItemTemplate> <!-- BAD CODE DO NOT USE.-->
            <DataTemplate> <!-- BAD CODE DO NOT USE.-->
                <!--This background is unnecessary and can be removed-->
                <Grid Background="Blue"/> <!-- BAD CODE DO NOT USE.-->
            </DataTemplate> <!-- BAD CODE DO NOT USE.-->
        </GridView.ItemTemplate> <!-- BAD CODE DO NOT USE.-->
    </GridView> <!-- BAD CODE DO NOT USE.-->
    

    Instead, do this.

    <GridView Background="Blue">  
        <GridView.ItemTemplate> 
            <DataTemplate> 
                <Grid/> 
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView> 
    
  • Prefer a Border element to draw a border around an object instead of using other objects to impersonate a border. In this example, both the Grid and the TextBox use many of the same pixels. Where overlap occurs, the pixels are filled twice.

    <!-- BAD CODE DO NOT USE.-->
    <Grid Background="Blue" Width="300" Height="45"> <!-- BAD CODE DO NOT USE.-->
        <Grid.RowDefinitions> <!-- BAD CODE DO NOT USE.-->
            <RowDefinition Height="5"/> <!-- BAD CODE DO NOT USE.-->
            <RowDefinition/> <!-- BAD CODE DO NOT USE.-->
            <RowDefinition Height="5"/> <!-- BAD CODE DO NOT USE.-->
        </Grid.RowDefinitions> <!-- BAD CODE DO NOT USE.-->
        <Grid.ColumnDefinitions> <!-- BAD CODE DO NOT USE.-->
            <ColumnDefinition Width="5"/> <!-- BAD CODE DO NOT USE.-->
            <ColumnDefinition/> <!-- BAD CODE DO NOT USE.-->
            <ColumnDefinition Width="5"/> <!-- BAD CODE DO NOT USE.-->
        </Grid.ColumnDefinitions> <!-- BAD CODE DO NOT USE.-->
        <TextBox Grid.Row="1" Grid.Column="1"></TextBox> <!-- BAD CODE DO NOT USE.-->
    </Grid> <!-- BAD CODE DO NOT USE.-->
    

    But this example draws a border around the TextBox without creating overlapping areas of pixels to fill.

    <Border BorderBrush="Blue" BorderThickness="5" Width="300" Height="45">
        <TextBox/>
    </Border>
    
  • Be aware of your margin sizes. Two neighboring elements might overlap accidentally if their margins extend into another’s render bounds, which causes the same pixels to be filled twice.

The DebugSettings.IsOverdrawHeatMapEnabled property helps you determine where and when an app draws objects on top of one another. It’s not uncommon to find objects being drawn that you may not have known were even in the scene!

Cache static content

You might create a conceptually discrete element by using many overlapping subshapes. The framework redraws each place that the elements overlap. You can eliminate this by setting CacheMode to BitmapCache on the UIElement that contains the entire conceptual element. Doing so allows the framework to render the element to a bitmap once and then use that bitmap instead of rerendering the subobjects every frame.

Suppose that you create a logo that looks like a three circle Venn diagram. You can create this object quickly with some simple XAML.

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

It’s clear that the subelements of the logo overlap. Every time the graphics hardware draws this, it spends time filling the pixels for each circle. The next image shows the overdrawn areas. Darker red indicates higher amounts of overdraw.

Because the logo is one complete object that always looks the same, you can eliminate the extra overdraw by setting the CacheMode property on the parent UIElement. In this example the app creates the element once, caches it, and then uses the cached version instead of drawing each circle every time it redraws the logo.

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

Don't use this technique if any of the subshapes animate, because every time one of these elements changes, the bitmap cache needs to be regenerated.