0

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>
Pieter
  • 31,619
  • 76
  • 167
  • 242
  • Have you tried this? [MSDN](http://msdn.microsoft.com/en-us/library/cc663151(v=vs.110).aspx) – XAMlMAX Apr 07 '14 at 13:30
  • Alas, I got the same exception when I tested for *highlighter.GetCurrentProgress(this) == null*. – Pieter Apr 07 '14 at 15:53
  • How do you feel about using triggers in your `xaml` rather than in code highlighting? – XAMlMAX Apr 09 '14 at 11:19
  • **UPDATE** The way I have animation in my xaml is like this ``. – XAMlMAX Apr 09 '14 at 11:27
  • I'm not sure if XAML is an option because my storyboard isn't really tied to a specific input event. Classes can call `RenderHighlight()` to trigger the highlight animation at any time, which is why I'm working in the codebehind. Is there a way to make this work in XAML? – Pieter Apr 12 '14 at 17:19
  • Yes, it's possible by binding the highlight function to a property and then use `DataTriggers`. – XAMlMAX Apr 14 '14 at 06:57

2 Answers2

1

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>
Pieter
  • 31,619
  • 76
  • 167
  • 242
0

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

Community
  • 1
  • 1
XAMlMAX
  • 2,268
  • 1
  • 14
  • 23
  • I defined a `DataTrigger` in `UserControl.Triggers`, but now I get a run-time exception saying *Triggers collection members must be of type EventTrigger*. I'll try implementing a custom event and get back to you. – Pieter Apr 14 '14 at 08:07
  • The program runs when I use event triggers, but the `StopStoryboard` action seems to cancel out the entire animation. When I only use the `BeginStoryboard` action under `EventTrigger.Actions`, the same animation jamming problem I reported earlier pops back up, even when I leave the `EventTrigger.ExitActions` section in there. I added a code snippet of my trigger to the top of the post. – Pieter Apr 14 '14 at 08:38
  • What if you try to add the same `BeginSoryBoard` to your `ExitAction`? – XAMlMAX Apr 14 '14 at 09:07
  • BTW I tried to replicate the solution in my WPF test app and I have no problems of setting the `StoryBoard` in the `DataTrigger`. here is the link to another solution, maybe this will give you information you need [WPF DataTriggers to play animation](http://blog.smoura.com/playing-storyboards-on-datatriggers-to-animate-a-path/). HTH – XAMlMAX Apr 14 '14 at 09:15
  • The triggers turned out to be a red herring. I've posted my solution below. Thanks for your help, though! – Pieter Apr 14 '14 at 14:03