Question

I have defined a storyboard in the XAML source of a UserControl. It is played whenever this function is called:

/// <summary>
/// Plays highlight animation.
/// </summary>
public void Highlight()
{
    Storyboard highlighter = FindResource("Highlight") as Storyboard;
    highlighter.Begin(this, true);
}

This works well as long as the animation isn't already playing when this function is called. When I call the function before the storyboard finishes playing, the animation gets stuck indefinitely. Why does this happen? Here is the source of the animation:

<Storyboard x:Key="Highlight" AutoReverse="True">
    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="Border">
        <EasingColorKeyFrame KeyTime="0:0:0.15" Value="LightGray">
            <EasingColorKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
            </EasingColorKeyFrame.EasingFunction>
        </EasingColorKeyFrame>
    </ColorAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="LayoutRoot">
        <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0.6">
            <EasingDoubleKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="LayoutRoot">
        <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0.6">
            <EasingDoubleKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

How do I make sure that the animation doesn't jam when the storyboard gets a new Begin call before it finishes playing? I'm fine with either the animation restarting at each function call or no new animation being triggered while it is still playing. For whatever reason, this yields the exception Cannot perform action because the specified Storyboard was not applied to this object for interactive control:

Storyboard highlighter = FindResource("Highlight") as Storyboard;
if (highlighter.GetCurrentState(this) == ClockState.Stopped)
    highlighter.Begin(this, true);

Update: I tried this XAML-based solution based on XAMIMAX's answer, but when I use this no animation is played whatsoever.

<UserControl.Triggers>
    <EventTrigger RoutedEvent="local:StatusIcon.HighlightRequested">
        <EventTrigger.EnterActions>
            <BeginStoryboard x:Name="bidHighlight" Storyboard="{StaticResource Highlight}" />
        </EventTrigger.EnterActions>
        <EventTrigger.ExitActions>
            <StopStoryboard BeginStoryboardName="bidHighlight" />
        </EventTrigger.ExitActions>
    </EventTrigger>
</UserControl.Triggers>
Was it helpful?

Solution

My problem went away when I explicitly defined a starting value for each animated property at keyframe 0. Here is my code:

<Storyboard x:Key="Highlight" AutoReverse="True">
    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="Border">
        <EasingColorKeyFrame KeyTime="0" Value="White"/>
        <EasingColorKeyFrame KeyTime="0:0:0.15" Value="LightGray">
            <EasingColorKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
            </EasingColorKeyFrame.EasingFunction>
        </EasingColorKeyFrame>
    </ColorAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="LayoutRoot">
        <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0.6">
            <EasingDoubleKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="LayoutRoot">
        <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0.6">
            <EasingDoubleKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[1].(SkewTransform.AngleX)" Storyboard.TargetName="LayoutRoot">
        <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[1].(SkewTransform.AngleY)" Storyboard.TargetName="LayoutRoot">
        <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

OTHER TIPS

If you want to do it through xaml:

Assumption - Models or View Models Implement INotfifyPropertyChanged

Lets say that you have a list of movies

<ListView 
            ScrollViewer.CanContentScroll="False" 
            x:Name="lsvMovies" 
            ItemsSource="{Binding Movies}"
            SelectedItem ="{Binding SelectedMovie}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Width="Auto" Header="Code"          DisplayMemberBinding="{Binding ID}"></GridViewColumn>
                    <GridViewColumn Width="Auto" Header="Description"   DisplayMemberBinding="{Binding Desc}"></GridViewColumn>
                    <GridViewColumn Width="Auto" Header="Qty"           DisplayMemberBinding="{Binding Qty}"></GridViewColumn>
                    <GridViewColumn Width="Auto" Header="Gross"         DisplayMemberBinding="{Binding Price}"></GridViewColumn>
                    <GridViewColumn Width="Auto" Header="Discount %"    DisplayMemberBinding="{Binding Discount}"></GridViewColumn>
                    <GridViewColumn Width="Auto" Header="Net"           DisplayMemberBinding="{Binding Gross}"></GridViewColumn>
                    <GridViewColumn Width="Auto" Header="Title"         DisplayMemberBinding="{Binding Title}"></GridViewColumn>
                </GridView>
            </ListView.View>
            <ListView.GroupStyle>
                <GroupStyle ContainerStyle="{StaticResource GroupedView}"/>
            </ListView.GroupStyle>
        </ListView>

Ignore the GroupStyle it's there for expander.

<Style x:Key="{x:Type ListViewItem}" TargetType="ListViewItem">
    <Style.Triggers>
        <DataTrigger Binding="{Binding BidChangeDirectionIndicator}"
                     Value="-1">
            <DataTrigger.EnterActions>
                <BeginStoryboard x:Name="bidDownStory">
                    <Storyboard>
                        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="Border">
                            <EasingColorKeyFrame KeyTime="0:0:0.15" Value="LightGray">
                                <EasingColorKeyFrame.EasingFunction>
                                    <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
                                </EasingColorKeyFrame.EasingFunction>
                            </EasingColorKeyFrame>
                        </ColorAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="LayoutRoot">
                            <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0.6">
                                <EasingDoubleKeyFrame.EasingFunction>
                                    <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
                                </EasingDoubleKeyFrame.EasingFunction>
                            </EasingDoubleKeyFrame>
                        </DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="LayoutRoot">
                            <EasingDoubleKeyFrame KeyTime="0:0:0.15" Value="0.6">
                                <EasingDoubleKeyFrame.EasingFunction>
                                    <ElasticEase EasingMode="EaseIn" Oscillations="1"/>
                                </EasingDoubleKeyFrame.EasingFunction>
                            </EasingDoubleKeyFrame>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>
                <StopStoryboard BeginStoryboardName="bidDownStory" />
            </DataTrigger.ExitActions>
        </DataTrigger>
    </Style.Triggers>
</Style>

Place the Style above wherever you need it to be. HTH
This Style was based on this Question WPF DataTriggers

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top