Consider this code:
<UserControl x:Class="MyApp.MyControl" ... xmlns:local="clr-namespace:MyApp" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"><UserControl.Template><ControlTemplate><ControlTemplate.Resources><Storyboard x:Key="MyStory"><ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase"><SplineColorKeyFrame KeyTime="0:0:1" Value="Red"/></ColorAnimationUsingKeyFrames></Storyboard></ControlTemplate.Resources><Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black"> ...</Border><ControlTemplate.Triggers><Trigger SourceName="brdBase" Property="IsMouseOver" Value="True"><Trigger.EnterActions><BeginStoryboard Storyboard="{StaticResource MyStory}"/></Trigger.EnterActions></Trigger></ControlTemplate.Triggers></ControlTemplate></UserControl.Template></UserControl>
The above code works with no problem. Now, I wanna bind key-frame value of
MyStory to a DP (named SpecialColor) of this user-control like so:
<Storyboard x:Key="MyStory"><ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase"><SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/></ColorAnimationUsingKeyFrames></Storyboard>
which makes an error:
Cannot freeze this Storyboard timeline tree for use across threads.
It's possible to do this using code behind. But how can I do it in XAML only?
__________________________
Code-Behind Aided Solution:
►Step 1: Putting the MyStory storyboard into the brdBase resources.
<UserControl.Template><ControlTemplate><Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black"><Border.Resources><Storyboard x:Key="MyStory"><ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase"><SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/></ColorAnimationUsingKeyFrames></Storyboard></Border.Resources> ...</Border><ControlTemplate.Triggers><Trigger SourceName="brdBase" Property="IsMouseOver" Value="True"><Trigger.EnterActions><BeginStoryboard Storyboard="{StaticResource MyStory}"/></Trigger.EnterActions></Trigger></ControlTemplate.Triggers></ControlTemplate></UserControl.Template>
Error:Cannot find resource named 'MyStory'. Resource names are case sensitive.
►Step 2: Eliminating Trigger on IsMouseOver property and begin theMyStory from code behind.
<UserControl.Template><ControlTemplate><Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black" MouseEnter="brdBase_MouseEnter"><Border.Resources><Storyboard x:Key="MyStory"><ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase"><SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/></ColorAnimationUsingKeyFrames></Storyboard></Border.Resources></Border></ControlTemplate></UserControl.Template>
C# Code-Behind:
private void brdBase_MouseEnter(object sender, MouseEventArgs e) { Border grdRoot = (Border)this.Template.FindName("brdBase", this); Storyboard story = grdRoot.Resources["MyStory"] as Storyboard; story.Begin(this, this.Template); }
►Step 3: The solution is already done, but it doesn't work at the first time. Fortunately, there is a workaround for this issue. It's enough to put theControlTemplate in a Style.
(I need other Trigger types than EventTrigger and must wrap theUserControl elements with the ControlTemplate.)
______________________
Using ObjectDataProvider:
I also tried to use ObjectDataProvider, but it failed.
-
An ObjectDataProvider resource cannot be used to provide a storyboard!!! The error report is:
- XamlParseException: Set property 'System.Windows.Media.Animation.BeginStoryboard.Storyboard' threw an exception.
- InnerException: 'System.Windows.Data.ObjectDataProvider' is not a valid value for property 'Storyboard'.
- The AssociatedControl DP is always null.
Here is the code:
<UserControl.Template><ControlTemplate><ControlTemplate.Resources><local:StoryboardFinder x:Key="StoryboardFinder1" AssociatedControl="{Binding ElementName=brdBase}"/><ObjectDataProvider x:Key="dataProvider" ObjectInstance="{StaticResource StoryboardFinder1}" MethodName="Finder"><ObjectDataProvider.MethodParameters><sys:String>MyStory</sys:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ControlTemplate.Resources><Border x:Name="brdBase" BorderThickness="1" BorderBrush="Cyan" Background="Black"><Border.Resources><Storyboard x:Key="MyStory"><ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="brdBase"><SplineColorKeyFrame KeyTime="0:0:1" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}, Path=SpecialColor}"/></ColorAnimationUsingKeyFrames></Storyboard></Border.Resources> ...</Border><ControlTemplate.Triggers><Trigger SourceName="brdBase" Property="IsMouseOver" Value="True"><Trigger.EnterActions><BeginStoryboard Storyboard="{StaticResource dataProvider}"/></Trigger.EnterActions></Trigger></ControlTemplate.Triggers></ControlTemplate></UserControl.Template>
The StoryboardFinder class:
public class StoryboardFinder : DependencyObject { #region ________________________________________ AssociatedControl public Control AssociatedControl { get { return (Control)GetValue(AssociatedControlProperty); } set { SetValue(AssociatedControlProperty, value); } } public static readonly DependencyProperty AssociatedControlProperty = DependencyProperty.Register("AssociatedControl", typeof(Control), typeof(StoryboardFinder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None)); #endregion public Storyboard Finder(string resourceName) { // // Associated control is always null :( // return new Storyboard(); } }