I'm trying to animate the Background Color of a Border in a DataTemplate for a DataObject when a Child Property of the DataObject changes. The DataObject is a Class called Test with two Properties, Number and Text. I have an ObservableCollection of DataObjects called Numbers. In a Task I update the Number Property at a regular Interval.
<Window
x:Class="WpfAnimationTest.MainWindow"
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:local="clr-namespace:WpfAnimationTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
DataContext="{Binding Main, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="NumberTemplate">
<TextBlock Text="{Binding NotifyOnTargetUpdated=True}" />
</DataTemplate>
<local:ValueTemplateSelector x:Key="TemplateSelector">
<local:ValueTemplateSelector.NumberTemplate>
<DataTemplate>
<ContentControl Content="{Binding NotifyOnTargetUpdated=True}" ContentTemplate="{StaticResource NumberTemplate}" />
</DataTemplate>
</local:ValueTemplateSelector.NumberTemplate>
</local:ValueTemplateSelector>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder" Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock
Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl
Width="50"
Margin="10"
Content="{Binding Number}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
<!--
ContentTemplate="{StaticResource NumberTemplate}"
-->
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation
FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace WpfAnimationTest
{
public class Locator
{
public Locator()
{
Main = new Main();
}
public Main Main { get; set; }
}
public class Main
{
public ObservableCollection<Test> Numbers { get; set; } = new ObservableCollection<Test>();
public Main()
{
var rnd = new Random(42);
for (int i = 0; i < 10; i++)
{
Numbers.Add(new Test(){Number = i, Text = $"#: {i}"});
}
Task.Run(() =>
{
while (true)
{
try
{
Application.Current?.Dispatcher.Invoke(() =>
{
Numbers[rnd.Next(9)] = new Test
{
Number = rnd.Next(30),
Text = $"# {rnd.Next(30) + 30}"
};
});
Thread.Sleep(1000);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
});
}
}
public class Test
{
public int Number { get; set; }
public string Text { get; set; }
}
public class ValueTemplateSelector : DataTemplateSelector
{
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <param name="container"></param>
/// <returns></returns>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return !(item is Test value)
? null
: NumberTemplate;
}
/// <summary>
///
/// </summary>
public DataTemplate NumberTemplate { get; set; }
/// <summary>
///
/// </summary>
public DataTemplate DefaultTemplate { get; set; }
}
}
When using a ContentTemplate for the Number Property in the ContentControl the animation is working. But when I use a ContentTemplateSelector the Animation is not triggered anymore.
What am I missing here?
Your data bindings are wrong, resulting in unhandled or unexpected data types.
Currently, your
ContentControl.Content
property (inside theDataTemplate
for the typeTest
) binds to theTest.Number
property of typeint
. Therefore, the object that is passed to theDataTemplateSelector
is of typeint
and notTest
. The condition in theDataTemplateSelector.SelectTemplate
method will return false (asint is not Test
istrue
) and make theDataTemplateSelector.SelectTemplate
returnnull
- no template. The XAML engine will callToString
on theint
instance and is therefore able to display the value correctly (despite the lack of aDataTemplate
).Solution
You must fix the binding on the
ContentControl.Content
property to bind to theTest
instance instead of binding to theTest.Number
property:Now that that the
DataTemplateSelector
can work properly, you must also adjust theNumberTemplate
, so that it can display theTest.Number
property properly. Since we have modified the binding source for theContentControl.Content
property, the new data type is nowTest
(instead ofint
):Remarks
There is a lot of redundant code here. You will achieve the same results by dropping all the templates and the
DataTemplateSelector
by defining theItemTemplate
for theTest
items properly.The following drastically simplified version should also work: