WPF Storyboard Animation Loops Forever Even After Being Set - wpf

I have a a custom user control to animate based on a DependencyProperty which is bound to a DataTrigger. If the DependencyProperty is equal to Failure, it should animate the fill color of a rectangle (named buttonColor) within the user control.
For some reason though, it always loops forever even if I set the RepeatBehavior to any value including 1.
If I remove the RepeatBehavior attribute, it only plays the animation once (as expected). Here is the code which I have the issue:
<DataTrigger Binding="{Binding Path=ButtonAction.Status}" Value="Failure">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="Pulse"/>
<BeginStoryboard>
<Storyboard RepeatBehavior="1">
<ColorAnimation Storyboard.TargetName="buttonColor"
Storyboard.TargetProperty="Fill.Color"
To="{StaticResource FailedColor}"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>

The correct syntax to repeat N times is:
<Storyboard RepeatBehavior="Nx">
for example:
<Storyboard RepeatBehavior="6x">

Setting a duration value will also limit the repeat behavior as it takes precedence. So if you have repeat behavior set on the ColorAnimationUsingKeyFrames tag but on the storyboard you set a Duration="0:0:4" then the animation will only repeat for 4 seconds.

To be clear and add onto the the answer, if one actually wants to repeat continuously one can set the RepeatBehavior to Forever such as here where I am rotating a vector around its center point.
<Storyboard x:Key="ChaseRotate" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="path">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
See How to: Rotate an Object - WPF .NET Framework
Full Example for Chasing Circles in Xaml.

Related

StopStoryboard doesn't stop endless animation

I'm drawing a Polygon on a Canvas as a battery indicator. If the battery percentage drops below a threshold value, the polygon should start a pulsing animation, if the percentage rises back above the threshold, the pulsing should stop. For that I am using a DataTrigger that binds to a bool in the view model that tells me whether or not to run the pulsing animation loop at the moment.
At first, the animation is off, and as soon as the percentage drops, it starts pulsing as intended. But when the percentage levels rise again, I can't find any way to make the pulsing animation stop.
This is my current XAML code:
<Polygon.Style>
<Style TargetType="Polygon">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsLowPercentage}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Pulse">
<Storyboard>
<DoubleAnimation
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetProperty="Opacity"
From="1"
To="0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="Pulse" />
<!--<RemoveStoryboard BeginStoryboardName="Pulse" />-->
<!--<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>-->
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Polygon.Style>
I found two questions (1, 2) about this which have the same use-case, and StopStoryboard seems to do the trick for them, but not for me.
Next to StopStoryboard, I also tried RemoveStoryboard and another BeginStoryboard (because according to this answer a storyboard automatically stops if the same dependency propery is animated by a new storyboard). The regarding codes are shown in the commented part. I tested all combinations, that is Stop, Remove, Begin alone, Stop+Remove, Stop+Begin, Remove+Begin or Stop+Remove+Begin in combination, but nothing seems to work, the pulsing effect just continues forever.
I also considered different naming scopes as the issue, but my storyboards are even in closer naming scopes (same DataTrigger) than this example using StopStoryboard from Microsoft themselves, and my code is also very similar to this other example from Microsoft, but my animation is endless.
I have validated that the bool is in fact set to true or false respectively, and I see that all other values bound to the same view model are displaying the updated values from that view model, so that should be fine.
Everybody seems to be fine using StopStoryboard, but something about my code is wrong so it doesn't work for me.

How to reset a color animation to the color it was before the animation with WPF triggers and animations

I havea control template for a text box with a triggers section like this
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="Border"
Storyboard.TargetProperty="Background.(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0:0:0.20" Value="Yellow"/>
<EasingColorKeyFrame KeyTime="0:0:1" Value="{Binding ElementName=Border, Path=Background.SolidColorBrush.Color}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
The idea being that whenever the binding target is updated the text box will pulse yellow. My UI has complex dependencies between controls and i would like the user to be notified when things change via a simple visual cue.
The problem I have above is resetting the color of the background of textbox to the color it was previously. If I animate it back to white this might not have been the original color. There are several visual states, ie normal disabled enabled.
So I wish to pulse yellow and then return to color it was previously. However if I try to bind the color of the final keyframe I get an error like
Cannot freeze storyboard to be used across multiple threads.
Is there either a way to clear the result of the animation automatically after it finishes or bind the correct color in dynamically?
You could set the animation's FillBehavior to Stop. The animated property will then automatically revert to the value it had before the animation was started.
<ColorAnimationUsingKeyFrames FillBehavior="Stop"
Storyboard.TargetName="Border"
Storyboard.TargetProperty="Background.Color">
...
</ColorAnimationUsingKeyFrames>

WPF Datagrid events DataTriggers vs EventTriggers

relatively simple question that I am struggling to find a nice elegant solution to. I have a grid with a column that displays values that update every n seconds. I wish to show an animation when the value changes and flash the cell in different colours based on if the number is negative or positive. Found a whole host of methods that do almost what I want, but nothing that exactly matches my needs.
Using an EventTrigger I can make a cell flash every time an update occurs. Using a relatively simple animation below. But I am unable to make the colour the animation uses conditional as the storyboard freezes the UI elements, so I cannot use binding in the storyboard to define the colour.
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard HandoffBehavior="Compose" Name="GreenCell">
<Storyboard TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)">
<ColorAnimation Duration="0:0:1.50" From="Green" To="Transparent" AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Using a DataTrigger I can make the cell colour conditional by creating a Converter that converts the updates to this Value and if the value is negative and then binding a data trigger to this and setting the background colour when this changes. But if the number is already negative, and remains negative (but changes) the trigger is not fired.
<DataTrigger Binding="{Binding Value, Converter={StaticResource cellBackGroundConverter}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard HandoffBehavior="Compose" Name="GreenCell">
<Storyboard TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)">
<ColorAnimation Duration="0:0:1.50" From="Green" To="Transparent" AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Value, Converter={StaticResource cellBackGroundConverter}}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard HandoffBehavior="Compose" Name="RedCell">
<Storyboard TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)">
<ColorAnimation Duration="0:0:1.50" From="Red" To="Transparent" AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
What I require seems to be an event trigger, to capture every update and play the animation with the conditional aspect of the data trigger.
Surely I am missing something simple here, would love for someone to set me straight!
Many thanks
Matt

How can I hold a WPF storyboard animation for a second, before continuing?

We have a color animation that blends from red to white. Currently, this is a linear fade. We know we can play with the Storyboard class's BeginTime and such, but that simply delays the start of the entire animation. We've also looked at the ease-in/ease-out side of things, but they don't seem to work either.
Specifically, we'd like to hold a value of red for one second, then fade from red to white over the next. Can that be done in pure XAML? If not, can it be done in code-behind manually setting up a storyboard? ...or do we have to use two separate storyboards and play them in sequence?
Several ways to do this.
with key frames:
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Background.Color">
<DiscreteColorKeyFrame Value="Red" KeyTime="0:0:0" />
<DiscreteColorKeyFrame Value="Red" KeyTime="0:0:1" />
<LinearColorKeyFrame Value="White" KeyTime="0:0:2" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
with two animations in sequence:
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color"
From="Red" To="Red" Duration="0:0:1" />
<ColorAnimation Storyboard.TargetProperty="Background.Color"
To="White" BeginTime="0:0:1" Duration="0:0:1" />
</Storyboard>
with a custom easing function:
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color"
From="Red" To="White" Duration="0:0:2">
<ColorAnimation.EasingFunction>
<local:CustomEasingFunction />
</ColorAnimation.EasingFunction>
</ColorAnimation>
</Storyboard>
In this case a function that shows the transition in the first half of duration and holds the value in the second half. Because the default EasingMode is EaseOut this function will then 'play' backwards.
public class CustomEasingFunction : EasingFunctionBase
{
public CustomEasingFunction() : base()
{ }
protected override double EaseInCore(double normalizedTime)
{
return (normalizedTime < 0.5)
? normalizedTime * 2
: 1;
}
protected override Freezable CreateInstanceCore()
{
return new CustomEasingFunction();
}
}
Depending on how you have your animations written, you could just add a transition "from" Red "to" Red that takes a second at the beginning. So technically the animation is running, but its not doing anything. That can be done in pure XAML.

WPF Animation of border width

I've got a dockpanel on my ui as follows;
<DockPanel>
<Border DockPanel.Dock="Top">Header</Border>
<Border DockPanel.Dock="Bottom">My footer</Border>
<Border DockPanel.Dock="Left">Menu</Border>
<Border>Content</Border>
</DockPanel>
What I want to do is to have a storyboard animation to show / hide the Menu on the left-hand side. I've got my border to increase the width when it has loaded but I want a way to close / reopen it. I need a button somewhere but I want this to trigger the animation in the border control rather than itself. Ideally I was thinking of something like the Toolbox / Server Explorer in visual studio.
Does anyone have any pointers / examples for getting started?
Thanks
I'm not sure I understand what you mean, but if you want to animate it in/out then you probably want to update its width? If you have a property on your ViewModel/PresentationModel that you can bind to then you can do something like:
<DataTrigger Binding="{Binding IShouldBeVisible}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard AccelerationRatio="0.4" DecelerationRatio="0.4">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Width)">
<SplineDoubleKeyFrame KeyTime="00:00:0.13" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard AccelerationRatio="0.4" DecelerationRatio="0.4">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Width)">
<SplineDoubleKeyFrame KeyTime="00:00:0.1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
If you are doing complicated animations that change multiple properties, different timings etc then it's much easier to put together in Blend, even if you do it in a test project then cut+paste the resulting StoryBoard :-)

Resources