#UWPXAML – Compiled Binding – Incremental rendering with x:Phase

Coming with Windows 10 are a lot of new features especially for XAML when creating a Universal Windows Platform application aka. UWP app. We can mention a few of them: new controls (as the RelativePanel, the SplitView and more); new tools for building responsive and adaptive UI (AdaptiveTrigger, Extension SDKs, etc.); and a new way to bind data to the UI, Compiled Binding.

This series on Compiled Binding will be composed of several parts:

 

Dealing with long list of data

You often have to work on apps that have a great deal of data. Those data are best displayed on list when they’re of same type, and you generally want to let your users scroll through it without noticeable performance issues.

To enable this, virtualized lists have been created.

Only a handful of items, that are visible to the user, are rendered at the same time while other items are simply not rendered at all. When the user scrolls through the list, rendered items are recycled to match where the user is in the list, thus keeping your memory usage low while still giving to your user what he needs.

This gives your users the illusion that your app is fast.

But sometimes, your items’ DataTemplate can be complex and when the user scrolls through the list, the CPU might not keep up and fully render new items in time. In that case, the list will display placeholder items until rendered items are ready. A quirky user experience may result from that.

To help you have more control on how to optimize the rendering of your items in those circumstances, Windows 8.1 added the event ContainerContentChanging allowing you to progressively render your items in a ListViewBase-derived control.
 

ContainerContentChanging event

The idea behind ContainerContentChanging is quite simple. By handling that event, you can progressively render the items that are visible to the user. This is done by using phases.

Let’s say that you have a list of books, each with a title, a subtitle and a description.
The most important information of your books is the Title property, so it must be displayed whenever possible. The Subtitle property is important but not crucial for the user experience while the Description property is not important.

With the ContainerContentChanging event, you can delay the rendering of the Subtitle and Description properties to later, when the CPU will be more available. The event is called multiple times if necessary, for each pair of container / data that need to be rendered.

Each time the event is raised for the same container / data pair is called a phase. The phase number is provided through the ContainerContentChangingArgs parameter, Phase property.

You can then check the phase number to decide which information should be loaded according to your importance scale.

There we can decide to let the Title property on Phase 0, the Subtitle property on Phase 1 and the Description property on Phase 2.

Phase 0 will be part of the first rendering of the item, while Phase 1 will be done once all current items’ Phase 0 have been rendered, and so on until all phases of all items are done.

Here’s an example that can be found on MSDN:
ListView and GridView UI optimization

XAML

<Page
    x:Class="LotsOfItems.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:lotsOfItems="using:LotsOfItems"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <GridView ItemsSource="{x:Bind ViewModel.ExampleItems}" ContainerContentChanging="GridView_ContainerContentChanging">
            <GridView.ItemTemplate>
                <DataTemplate x:DataType="lotsOfItems:ExampleItem">
                    <StackPanel Height="100" Width="100" Background="OrangeRed">
                        <TextBlock Text="{x:Bind Title}"/>
                        <TextBlock Opacity="0"/>
                        <TextBlock Opacity="0"/>
                    </StackPanel>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>
</Page>

C#

namespace LotsOfItems
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.ViewModel = new ExampleItemViewModel();
        }

        public ExampleItemViewModel ViewModel { get; set; }

        // Display each item incrementally to improve performance.
        private void GridView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            if (args.Phase != 0)
            {
                throw new System.Exception("We should be in phase 0, but we are not.");
            }

            // It's phase 0, so this item's title will already be bound and displayed.

            args.RegisterUpdateCallback(this.ShowSubtitle);

            args.Handled = true;
        }

        private void ShowSubtitle(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            if (args.Phase != 1)
            {
                throw new System.Exception("We should be in phase 1, but we are not.");
            }

            // It's phase 1, so show this item's subtitle.
            var templateRoot = args.ItemContainer.ContentTemplateRoot as StackPanel;
            var textBlock = templateRoot.Children[1] as TextBlock;
            textBlock.Text = (args.Item as ExampleItem).Subtitle;
            textBlock.Opacity = 1;

            args.RegisterUpdateCallback(this.ShowDescription);
        }

        private void ShowDescription(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            if (args.Phase != 2)
            {
                throw new System.Exception("We should be in phase 2, but we are not.");
            }

            // It's phase 2, so show this item's description.
            var templateRoot = args.ItemContainer.ContentTemplateRoot as StackPanel;
            var textBlock = templateRoot.Children[2] as TextBlock;
            textBlock.Text = (args.Item as ExampleItem).Description;
            textBlock.Opacity = 1;
        }
    }
}

As you can see, our DataTemplate contains three TextBlocks: one for the Title property, one for the Subtitle property and one for the Description property.

Title being the most important, the control is already databound in the DataTemplate while the others are simply not set and are even hidden from users with Opacity at 0.

Then we add an event handler to the ContainerContentChanging event of the GridView.

When the event is raised in GridView_ContainerContentChanging method, it is Phase 0, the book’s title is rendered.

x:Phase
 

To notify that we expect another phase to occur, we register an update callback through the ContainerContentChangingArgs.RegisterUpdateCallback method. In that callback, that will be called once all items’ Phase 0 are done, we retrieve the second TextBlock control to load its content, the book’s Subtitle, and display it by setting the Opacity to 1.

x:Phase
 

Once it’s done, we register for a third phase to display the book’s description.

x:Phase
 

If the user has scrolled past too many items and the list control wants to recycle containers before we have rendered all the phases, ContainerContentChanging is no longer called for these container / data pairs and the containers are recycled for other data, thus ending the rendering of those previous items.

While ContainerContentChanging is simple to use, its implementation can be quite tedious. Even more when you need to change the DataTemplate over the course of development, you found yourself re-implementing it over and over.

There comes to the rescue {x:Bind} and its x:Phase attribute.
 

Simplifying ContainerContentChanging with x:Phase

Based on the ContainerContentChanging event, x:Bind introduces a shortcut notation to use those rendering phases: x:Phase.

It uses the exact same principle that we’ve seen on the last example, except you don’t have to implement ContainerContentChanging. It’s done for you by code-generation when using {x:Bind}.

To use it, it’s really simple. Just set the x:Phase attribute on an {x:Bind} databound control.

XAML

<GridView ItemsSource="{x:Bind ViewModel.ExampleItems}">
    <GridView.ItemTemplate>
        <DataTemplate x:DataType="lotsOfItems:ExampleItem">
            <StackPanel Height="100" Width="100" Background="OrangeRed">
                <TextBlock Text="{x:Bind Title}"/>
                <TextBlock Text="{x:Bind Subtitle}" x:Phase="1"/>
                <TextBlock Text="{x:Bind Description}" x:Phase="2"/>
            </StackPanel>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

As we’ve seen with ContainerContentChanging, the higher the phase number, the later the control will be rendered. If no phase is set, it’ll be 0 by default.

Note: Those phases don’t have to be contiguous with x:Phase.

In this case, the CPU will try to render the book’s title, then the book’s subtitle and finally the book’s description instead of trying to render them all at once and failing to keep up with the scroll applied by the user.

When the control’s phase is not yet reached, they are set to Opacity=0 thus hiding them. When their phase is reached, the compiled binding is resolved and the controls are displayed.

The result is the same but it is less tedious to implement. You just have to set an attribute on the databound control as opposed to doing a whole implementation of the ContainerContentChanging event.

Later, if you need to change the DataTemplate, it will be way easier.

Just one thing, x:Phase needs x:Bind in order to work. If not present, x:Phase will simply be ignored. So if you need to delay the rendering of a not-databound control, you still have to use the ContainerContentChanging event, that also applies if you need more than just hiding a control for a time.
 

Going further

MSDN
« x:Phase attribute »
https://msdn.microsoft.com/en-us/library/windows/apps/mt204790.aspx

MSDN
« ListView and GridView UI optimization »
https://msdn.microsoft.com/en-us/library/windows/apps/mt204776.aspx

Channel 9
« Improving XAML performance »
https://channel9.msdn.com/Events/Windows/Developers-Guide-to-Windows-10-RTM/Improving-XAML-Performance

Build 2015
« Data Binding: Boost Your Apps’ Performance Through New Enhancements to XAML Data Binding » https://channel9.msdn.com/Events/Build/2015/3-635

Une réflexion sur “#UWPXAML – Compiled Binding – Incremental rendering with x:Phase

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s