Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind to Control's Property inside VisualStateManager

Tags:

c#

wpf

xaml

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.

like image 551
Febertson Avatar asked Jun 20 '26 18:06

Febertson


1 Answers

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.

like image 131
15ee8f99-57ff-4f92-890c-b56153 Avatar answered Jun 23 '26 10:06

15ee8f99-57ff-4f92-890c-b56153



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!