Resource behavior inconsistency for ItemTemplates of list controls in Anniversary Update

WinUI XAML

7 years ago

It appears that the Anniversary Update has a hidden buggy behavior concerning Resources in ItemTemplates of list controls. I have hit this problem while working on an UWP app and I will describe the problem along with a workaround, which you can use to make sure your app will behave correctly on all versions of Windows 10.

The problem

Suppose I have a list of items of the following type:

public class AnimatableItem : INotifyPropertyChanged
{
    private bool _isAnimating = false;

    public event PropertyChangedEventHandler PropertyChanged;

    public bool IsAnimating
    {
        get { return _isAnimating; }
        set
        {
            _isAnimating = value;
            OnPropertyChanged();
        }
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

We will use the IsAnimating property to control when the item of the list animates. Now let's implement a ListView that will display our items:

<ListView ItemsSource="{x:Bind Items}">
     <ListView.ItemTemplate>
         <DataTemplate>
             <Border x:Name="Container">
                 <Border.Background>
                     <SolidColorBrush Color="Transparent" />
                 </Border.Background>
                 <Border.Resources>
                     <Storyboard x:Key="BlinkingAnimation" RepeatBehavior="Forever" AutoReverse="True">
                         <ColorAnimation Duration="0:0:1" To="Red" Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)" Storyboard.TargetName="Container">
                         </ColorAnimation>
                     </Storyboard>
                  </Border.Resources>
                  <StackPanel VerticalAlignment="Center">
                     <TextBlock Text="Is animating:" FontWeight="Bold" HorizontalAlignment="Center" />
                     <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding IsAnimating}" />
                   </StackPanel>
                   <interactivity:Interaction.Behaviors>
                       <core:DataTriggerBehavior 
                                Binding="{Binding IsAnimating, Mode=OneWay}" 
                                ComparisonCondition="Equal"
                                Value="true">
                                <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource BlinkingAnimation}" />
                       </core:DataTriggerBehavior>
                       <core:DataTriggerBehavior 
                                Binding="{Binding IsAnimating, Mode=OneWay}" 
                                ComparisonCondition="Equal"
                                Value="false">
                                <media:ControlStoryboardAction ControlStoryboardOption="Stop" Storyboard="{StaticResource BlinkingAnimation}" />
                        </core:DataTriggerBehavior>
                 </interactivity:Interaction.Behaviors>
             </Border>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

There are two important things to note about the ItemTemplate of the ListView:

  • We added a BlinkingAnimation Storyboard to Resources. This animation causes the item's background to blink with red background

  • Each item uses a DataTriggerBehavior with ControlStoryboardAction , which is part of the XAML Behaviors package for UWP apps (see here on GitHub). This will allow us to start and stop the animation based on the value of the IsAnimating property

    We would expect that when run this app and set Items[2].IsAnimating = true , the third item in the list should start animating. Surprisingly this is not the case in Anniversary Update.

As we can see, on both the November Update (build 10586) and Creators Update (build 15063) the app does exactly what we want. With Anniversary Update however, the third item's IsAnimating property is set correctly, but the animation runs on the first item in the list.

Experiments

I have tried some experiments to find out what exactly is happening. It doesn't matter for which target SDK the app is compiled. The app always behaves the same for any SDK used. The behavior on each version of Windows 10 is the same for Mobile and Desktop SKUs. Also, when debugging the ControlStoryboardAction , I have noticed on Anniversary Update the Storyboard instance that is set to its property with the StaticResource expression is in fact actually always the first item's Storyboard. Even then, each list item creates its own Storyboard instance, which is not used. It is clear, that in the Anniversary Update the system handles the Resources in DataTemplate somewhat differently (presumably in an effort to save system resources). Because the "correct" behavior returns with Creators Update, it is probably safe to assume this was a undocumented bug.

Workaround

Luckily it is quite easy to work around this problem. We can wrap the ItemTemplate contents into a UserControl and use it as the content instead. First we add a new UserControl into our project with the following XAML:

<UserControl
    x:Class="AnimationInDataTemplateProblem.AnimationItemControl"
    ...>
    <Border BorderBrush="Black" BorderThickness="1" Width="100" Height="100" x:Name="Container">
        <Border.Background>
            <SolidColorBrush Color="Transparent" />
        </Border.Background>
        <Border.Resources>
            <Storyboard x:Key="BlinkingAnimation" RepeatBehavior="Forever" AutoReverse="True">
                <ColorAnimation Duration="0:0:1" To="Red" Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)" Storyboard.TargetName="Container">
                </ColorAnimation>
            </Storyboard>
        </Border.Resources>
        <StackPanel VerticalAlignment="Center">
            <TextBlock Text="Is animating:" FontWeight="Bold" HorizontalAlignment="Center" />
            <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding IsAnimating}" />
        </StackPanel>
        <interactivity:Interaction.Behaviors>
            <core:DataTriggerBehavior 
                Binding="{Binding IsAnimating, Mode=OneWay}" 
                ComparisonCondition="Equal"
                Value="true">
                <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource BlinkingAnimation}" />
            </core:DataTriggerBehavior>
            <core:DataTriggerBehavior 
                Binding="{Binding IsAnimating, Mode=OneWay}" 
                ComparisonCondition="Equal"
                Value="false">
                <media:ControlStoryboardAction ControlStoryboardOption="Stop" Storyboard="{StaticResource BlinkingAnimation}" />
            </core:DataTriggerBehavior>
        </interactivity:Interaction.Behaviors>
    </Border>
</UserControl>

And now we use it like this:

<ListView ItemsSource="{x:Bind Items}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:AnimationItemControl />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Note the data binding works without a change with the Binding syntax, as the UserControl uses the current DataContext . If we wanted to use x:Bind instead, we would have to create a new dependency property and bind it manually. This simple change will now help the animation work as expected on all versions of Windows 10. The following images show both implementations at once:

Source code

Example source code for this blog post is available on my GitHub.

Summary

We have shown an odd behavior with resource handling for list items in Anniversary Update. Although AU is currently (April 2017) the most used version of Windows 10, it doesn't seem this problem has been observed nor documented. Luckily for us developers, it is easy to use a simple workaround that will force the expected behavior on all versions including Anniversary Update - wrapping the ItemTemplate contents in a UserControl .