I was able to do this with a BindingProxy. I find binding proxies to be nonintuitive. Sometimes they work on the first shot; this one took a little trial and error. Also, they're a little bit of a hail-mary workaround.
XAML:
<Grid x:Name="RootGrid" > <Grid.Resources> <!-- When defined in ControlTemplate.Resources, this failed. TemplateBinding failed too. --> <local:BindingProxy x:Key="CustomControlPropertyProxy" Data="{Binding CustomControlProperty, RelativeSource={RelativeSource TemplatedParent}}" /> </Grid.Resources> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CheckStates"> <VisualState x:Name="Checked"> <Storyboard> <DoubleAnimation Storyboard.TargetName="someElement" Storyboard.TargetProperty="Height" From="0" To="{Binding Data, Source={StaticResource CustomControlPropertyProxy}}" Duration="0:0:5" /> </Storyboard>
C# (stolen, not for the first time, from this answer):
public class BindingProxy : Freezable { #region Overrides of Freezable protected override Freezable CreateInstanceCore() { return new BindingProxy(); } #endregion public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy)); }
Here's another variant of the XAML, in case you end up binding animation properties to more than one property of the templated parent:
<Grid x:Name="RootGrid" > <Grid.Resources> <local:BindingProxy x:Key="TemplatedParentProxy" Data="{Binding ., RelativeSource={RelativeSource TemplatedParent}}" /> </Grid.Resources> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CheckStates"> <VisualState x:Name="Checked"> <Storyboard> <DoubleAnimation Storyboard.TargetName="someElement" Storyboard.TargetProperty="Height" From="0" To="{Binding Data.CustomControlProperty, Source={StaticResource TemplatedParentProxy}}" Duration="0:0:5" /> </Storyboard>
Blind Alleys
After ruling out TemplateBinding and {RelativeSource TemplatedParent}, my next guess was to bind RootGrid.Tag to CustomControlProperty and use To="{Binding Tag, ElementName=RootGrid}". That did not work. While intellisense knew about RootGrid in the XAML designer, the Binding couldn't find RootGrid at runtime:
<DoubleAnimation Storyboard.TargetName="someElement" Storyboard.TargetProperty="Height" From="0" To="{Binding Tag, ElementName=RootGrid, PresentationTraceSources.TraceLevel=High}" Duration="0:0:1" />
Debug trace:
System.Windows.Data Warning: 67 : BindingExpression (hash=15221148): Resolving source System.Windows.Data Warning: 69 : BindingExpression (hash=15221148): Framework mentor not found System.Windows.Data Warning: 67 : BindingExpression (hash=15221148): Resolving source (last chance) System.Windows.Data Warning: 69 : BindingExpression (hash=15221148): Framework mentor not found System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Tag; DataItem=null; target element is 'DoubleAnimation' (HashCode=44950942); target property is 'To' (type 'Nullable`1')
That "governing FrameworkElement or FrameworkContentElement" jazz is the essential problem with all of the other approaches as well. That's where binding proxies come in: Resource lookup isn't limited by that visual tree parent chain stuff.
RootGridwill have the sameActualHeightas the templated parent, won't it? I'd try{Binding ActualHeight, ElementName=RootGrid}. It's kind of a kludge, yeah. If it even works.