Search code examples
c#wpfxamlvisualstatemanagervisualstates

TemplateBinding in Storyboard | ObjectAnimationUsingKeyFrames not working


I have the following setup. the problem is, that if I switch to the VisualState "Alarm", the AlarmBrush is not set. I have tried different "Bindings" (see below), but I only get errors like System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=AlarmBrush; DataItem=null; target element is 'DiscreteObjectKeyFrame' (HashCode=39320280); target property is 'Value' (type 'Object').

If I replace the value with an ResourceKey, then everything is fine. But I want to have this State dynamic, so a static resource is no opionion.

BaseClass

[TemplateVisualState(GroupName = "FooGroup", Name = "Alarm")]
public abstract class BaseClass : Control
{
    public static readonly DependencyProperty AlarmBrushProperty = DependencyProperty.Register("AlarmBrush", typeof(Brush), typeof(BaseClass), null);
    public Brush AlarmBrush
    {
        get { return (Brush)GetValue(AlarmBrushProperty); }
        set { SetValue(AlarmBrushProperty, value); }
    }
}

DerivedClass

public class DerivedClass : BaseClass
{
    public DerivedClass()
    {
        this.DefaultStyleKey = typeof(DerivedClass);
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        VisualStateManager.GoToState(this, "Alarm", true);
    }
}

XAML Style

<Style TargetType="Basic:DerivedClass">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Basic:DerivedClass">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="FooGroup">
                            <VisualState x:Name="Alarm">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background)" Storyboard.TargetName="AlarmBorder">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding AlarmBrush, RelativeSource={RelativeSource TemplatedParent}}"/>
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Basic:DerivedClass}}, Path=AlarmBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="AlarmBorder" BorderBrush="{TemplateBinding BorderBrush}" Padding="{TemplateBinding Padding}"  BorderThickness="{TemplateBinding BorderThickness}">
                        <TextBlock Text="test" Foreground="{TemplateBinding Foreground}" TextWrapping="Wrap" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="BorderThickness" Value="5"/>
</Style>

xaml

<Foo:DerivedClass BorderBrush="#FFDA1F1F" AlarmBrush="#FF00FF0C"/>

Solution

  • You are going to hate this but I remember this is one of the limitations in WPF. The Value property needs to be frozen which means you cannot use bindings to dynamically change it. Same thing goes to the To property of a ColorAnimation.

    Since you are not actually animating the color (animating a Brush will simply change the color instantly), you can always obtain a reference of the AlarmBorder and manually set its Background.