3

I searched for this Problem on Stackoverflow, but in my opinion the other Posts do not cover this question.

In my Custom Control i am using a Visual State Manager. Inside the Visual State Manager there is an Animation that Animates the Height of an Element. When i try to bind to the Controls Properties i get following Error on StartUp:

Additional information: Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType=MyNameSpace.MyControl, AncestorLevel='1''. BindingExpression:Path=ActualHeight; DataItem=null; target element is 'DoubleAnimation' (HashCode=562002); target property is 'To' (type 'Nullable`1')

My Control looks like this:

<Style TargetType="{x:Type local:MyControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyControl}">
                <Grid x:Name="RootGrid" >
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="someElement"
                                                        Storyboard.TargetProperty="Height"
                                                        From="0"
                                                        To="{Binding RelativeSource={RelativeSource AncestorType=local:MyControl},  Path=CustomControlProperty}"
                                                        Duration="0:0:.7" />
...

I tried all ways of Bindings, but it seems that the Animations always takes itself as Scope.

Thanks for your help again.

Febertson
  • 398
  • 4
  • 23
  • Yes, there won't be a visual tree relationship to the parent control from in there, so that's going to fail. But `RootGrid` will have the same `ActualHeight` as the templated parent, won't it? I'd try `{Binding ActualHeight, ElementName=RootGrid}`. It's kind of a kludge, yeah. If it even works. – 15ee8f99-57ff-4f92-890c-b56153 Feb 27 '17 at 14:16
  • I made a mistake. I dont want to bind to the Height Property. If this was the case you are right. I want to bind to a custom Property defined inside my Custom Control. – Febertson Feb 27 '17 at 14:19

2 Answers2

3

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.

Community
  • 1
  • 1
0

Edit I realized my answer doesn't work after all.

See this related discussion https://social.msdn.microsoft.com/Forums/vstudio/en-US/027c364f-5d75-424f-aafd-7fb76b10b676/templatebinding-on-storyboard?forum=wpf

[...] The To property and From property of ColorAnimation can't be bind with, because of those properties need to be frozen (unchangeable) for the animation to work.

So please use specific color to instead of binding.

This suggests that some animation elements are not supposed to change via dynamic properties. I actually don't know whether this applies to all animations or just to a specific subset.

So considering @EdPlunkett answer, it might be worth to investigate, whether subsequent property changes are actually reflected in the animation without errors.


You should use {TemplateBinding CustomControlProperty} for one way binding or {Binding RelativeSource={RelativeSource TemplatedParent},Path=CustomControlProperty} if you need more binding complexity, instead of trying to find the templated control by other means.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • 1
    *"This suggests that some animation elements are not supposed to change via dynamic properties"* -- I wouldn't suppose that myself; if the designers went to the trouble of making it a writable dependency property, they had no objection to it being a binding target. And having tested the code in my answer and found it to work without weirdness or errors, I wouldn't sweat it. – 15ee8f99-57ff-4f92-890c-b56153 Feb 27 '17 at 18:33
  • I tried your first solution, too. But hadn't enough time to post my result here, sorry. Thanks anyways. I will try @Ed Plunkett answer now. – Febertson Mar 01 '17 at 06:25