Stop StoryBoard after currently running animation is completed - wpf

I want to stop WPF StoryBoard after property of control IsAnimating is changed to false, but I need to stop animation not instantly, but first complete currently running animation cycle and then stop it (I'm using this XAML now, but it stops my animation instantly):
<UserControl x:Class="App.Controls.ProgressCircle"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<Ellipse Name="Circle" Width="30" Height="30" Fill="Green" >
<Ellipse.Style>
<Style>
<Style.Resources>
<Storyboard x:Key="Pulsing">
<DoubleAnimation From="30.0" To="0.0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
Storyboard.TargetProperty="Width" />
<DoubleAnimation From="30.0" To="0.0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
Storyboard.TargetProperty="Height" />
</Storyboard>
</Style.Resources>
<Style.Triggers>
<DataTrigger Binding="{Binding IsAnimating, ElementName=me}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Pulsing" Storyboard="{StaticResource Pulsing}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="Pulsing" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</UserControl>

A "quick and dirty" solution would be to start a new animation in the DataTrigger.ExitActions that animates the height and width properties to the values they had before the whole animation was started. This could look like this:
<Style.Triggers>
<DataTrigger Binding="{Binding IsAnimating, ElementName=me}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Pulsing" Storyboard="{StaticResource Pulsing}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
Storyboard.TargetProperty="Height" />
<DoubleAnimation Duration="0:0:1"
Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
The important thing is that if you do not set the the To value on an animation, then the target value will be the one that the dependency property previously had (the local value of 30.0 in this case). You do not have to stop the 'Pulsing' storyboard in this case because this is automatically done when you start a new storyboard that animates the same dependency properties.
Obviously, this is not the optimal solution as you do not integrate the current status of the Pulsing animation (i.e. at which point in time it was when IsAnimating is set to false). As far as I know, there is no built-in functionality in WPF to achieve this kind of functionality, but it might be possible to implement a custom ConstrallableStoryboardAction that respects all these information and that can be set in the DataTrigger.ExitActions instead. It might also be worth your while to check some of Animation How-To topics in the MSDN library.

Related

WPF animation does not trigger iff animating Opacity from 0.0

I would like to fade in/out animate FontAwesome.WPF spinner icon when it starts/stops spinning. Luckily, MSDN provides almost identical example how to Trigger an Animation on Property Change which uses IsMouseOver. Chaning the property to icon's Spin and opacity to 0.0 should be straightforward:
<CheckBox x:Name="IsExecuting"/>
<fa:ImageAwesome xmlns:fa="http://schemas.fontawesome.io/icons/"
Icon="Refresh" Height="8" Width="8"
Spin="{Binding IsChecked, ElementName=IsExecuting}">
<fa:ImageAwesome.Style>
<Style TargetType="{x:Type fa:ImageAwesome}">
<Setter Property="Opacity" Value="0.0" />
<Style.Triggers>
<Trigger Property="Spin" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.0" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</fa:ImageAwesome.Style>
</fa:ImageAwesome>
But it does not work - nothing happens at all, spinner is invisible regardless of Spin property value. Curiously, the fade in/out works flawlessly, as long as the 'hidden' Opacity is not 0, i.e. if I rolled back to 0.25 as in MSDN example, it would work.
What is the problem here and how can I fix it?

WPF animation that is always running when the control is visible

I have a (lame) user requirement to make a control super visible.
Sadly that means a flashing background (Ug).
So, the control is a Border that holds a TextBlock is only visible in fairly rare scenarios.
I have looked at a few animation examples and they all have a "Trigger" on them. Most commonly when the user clicks on something.
Is there a way to just have the animation running all the time (if the control is visible of course)?
here you go, RepeatBehavior="Forever" will keep the animation running until stopped or removed
you can trigger a color animation with auto reverse enabled on the control load and let it run forever
<Border Background="Transparent">
<TextBlock Text="some text" />
<Border.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="SkyBlue"
Storyboard.TargetProperty="Background.Color"
RepeatBehavior="Forever"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
if you need the animation to be triggered on visibility change then here is a way, note that the animation is applied when IsVisible property become true and stopped when it become false.
<Border Background="Transparent">
<TextBlock Text="some text" />
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<Trigger Property="IsVisible"
Value="true">
<Trigger.EnterActions>
<BeginStoryboard x:Name="startFlashing">
<Storyboard>
<ColorAnimation To="SkyBlue"
Storyboard.TargetProperty="Background.Color"
RepeatBehavior="Forever"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="startFlashing" />
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
typically after visibility is set to false there is no visible difference if animation is still running or stopped.

The ellipse can't rotate in datatrigger

I want to rotate the ellipse when ProgressBar is set to true in DataTrigger. But It isn't work.
I test the ellipse is work OK with Opacity. But it isn't work with RotateTransform. And the ellipse is also work with EventTrigger.
Which is my mistake and how I fix it.
Thanks for reading my issue.
<Ellipse Name="ProgressEllipse" Width="40" Height="40" StrokeThickness="7" Stroke="{StaticResource ProcessEllipseGradient}" Margin="5,0,0,0">
<Ellipse.RenderTransform>
<RotateTransform CenterX="20" CenterY="20" Angle="0"/>
</Ellipse.RenderTransform>
<!--<Ellipse.Triggers>
<EventTrigger RoutedEvent="Ellipse.Loaded" SourceName="ProgressEllipse">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ProgressEllipse"
Storyboard.TargetProperty="(Ellipse.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:1"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>-->
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=ProgressBar}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Ellipse.RenderTransform).(RotateTransform.Angle)"
From="0"
To="1"
Duration="0:0:5"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
Addition information
Sorry WPF-it. I make my question not clear.
The Ellipse is work OK when It located in form directly. But It not work when located in a Template.
Follow is my code.
https://www.mediafire.com/?7rb8gvthx94rfiy
If I omit "RelativeSource={RelativeSource self}" in the Trigger. It will throw exception(Cannot animate '(0).(1)' on an immutable object instance)
In the sample code. I have three ellipse. The left ellipse is not work when I locate it in the template(I try to make it work, but It don't work). The center ellipse is the same code with the ellipse in template. But It work OK when I locate it directly in the form. And I test ellipse with Opacity property in the template. And It work OK. I don't know which is my mistake.
Can you give me a hand.
Thanks for reading my question
Ellipse.ProgressBar property does not exist in WPF. Your are probably referring some property from the underlying DataContext of Ellipse.
So change Binding="{Binding RelativeSource={RelativeSource Self}, Path=ProgressBar}" to simply Binding="{Binding Path=ProgressBar}"
Also make sure that this ProgressBar property generates property change notifications so that trigger will execute the action ...
Plus you should also have an EndStoryBoard also.
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ProgressBar}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="MyStoryboard">
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Ellipse.RenderTransform).(RotateTransform.Angle)"
From="0"
To="1"
Duration="0:0:5"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="MyStoryboard"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>

Style.Trigger vs. Grid.Trigger

In my Code I have defined two Triggers, a Label and a Canvas.
The description of my problem:
When the cursor goes straight across the Label, the Style.Trigger gets activated and the background colour changes (to orange). When the cursor runs across the canvas-area the Grid.Trigger gets activated and changes the background color(to violet). So far, so good.Is the cursor now, running (after the Grid.Trigger was active) across the label-area the background does not change at all.
It seems to me that the Grid.Trigger gets priority once it was active.
<Window x:Class="Sample01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<!-- Defined Style starts here -->
<Style x:Key="{x:Type Label}" TargetType="{x:Type Label}" >
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.Setters>
</Trigger.Setters>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="DarkOrange" Duration="0:0:0.5"
Storyboard.TargetProperty="Background.Color"
/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="White" Duration="0:0:1"
Storyboard.TargetProperty="Background.Color" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
<!-- End defined Style-->
</Grid.Resources>
<!-- Define Trigger -->
<Grid.Triggers>
<EventTrigger RoutedEvent="MouseEnter"
SourceName="canvas">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="BlueViolet" Duration="0:0:1"
Storyboard.TargetProperty="Background.Color"
Storyboard.TargetName="label" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave"
SourceName="canvas">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="White" Duration="0:0:1"
Storyboard.TargetProperty="Background.Color"
Storyboard.TargetName="label" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Label x:Name="label" VerticalAlignment="Top" Height="100" BorderBrush="Black" BorderThickness="2" Content="LABEL"/>
<Canvas x:Name="canvas" Height="100" VerticalAlignment="Bottom"
IsHitTestVisible="True"
Background="AntiqueWhite"
/>
</Grid>
Can someone explain this behavior ?
You're running into the order of precedence of value sources for Dependency Properties. A common case of this is when you set a local value directly on an element, a value set in a style is overridden. In this case, you're applying an animation to the property, which takes precedence over anything set in the Style (or even a local value).
To allow the Style to take over again you need to make the animation no longer apply to the Label. You can do this by explicitly removing the initial animation, which will reset back to the original state, like a Property Trigger does:
<EventTrigger RoutedEvent="FrameworkElement.MouseEnter"
SourceName="canvas">
<BeginStoryboard x:Name="GridMouseover">
<Storyboard>
<ColorAnimation To="BlueViolet" Duration="0:0:1"
Storyboard.TargetProperty="Background.Color"
Storyboard.TargetName="label" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.MouseLeave"
SourceName="canvas">
<RemoveStoryboard BeginStoryboardName="GridMouseover"/>
</EventTrigger>
The disadvantage of this is that you lose the smooth animation back to White. VisualStateManager is a much better choice for this kind of thing in many cases because it handles that for you automatically.
The other thing you can do is tell the Storyboard to stop applying itself after finishing by changing the FillBehavior:
<EventTrigger RoutedEvent="FrameworkElement.MouseEnter"
SourceName="canvas">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="BlueViolet" Duration="0:0:1"
Storyboard.TargetProperty="Background.Color"
Storyboard.TargetName="label" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.MouseLeave"
SourceName="canvas">
<BeginStoryboard>
<Storyboard FillBehavior="Stop">
<ColorAnimation To="White" Duration="0:0:1"
Storyboard.TargetProperty="Background.Color"
Storyboard.TargetName="label" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>

Start animation that begins with current value (handoff).

I have a WPF Datatemplate that contains a some DataTriggers that start animating the color of a visual. How can I start the animation beginning with the actual value the color property currently has?
Since there might be another animation currently active I can not start a new one but when I remove the animation using DataTriggers ExitAction and RemoveStoryboard the position property is set back to its default value.
Instead I would like handoff one to the other.
Is this a limitation of WPF that simply can not be done?
<DataTrigger Binding="{Binding Path=State}" Value="Active">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="activeStoryboard" HandoffBehavior="SnapshotAndReplace">
<Storyboard>
<ColorAnimation To="Green" FillBehavior="HoldEnd" Duration="00:00:0.25"
Storyboard.TargetName="stateBrush"
Storyboard.TargetProperty="Color" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="activeStoryboard" />
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding Path=State}" Value="Error">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="errorStoryboard" HandoffBehavior="SnapshotAndReplace">
<Storyboard>
<ColorAnimation To="Red" FillBehavior="HoldEnd" Duration="00:00:0.25"
Storyboard.TargetName="stateBrush"
Storyboard.TargetProperty="Color" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="errorStoryboard" />
</DataTrigger.ExitActions>
</DataTrigger>
It should just work if you start the second animation, even if FillBehavior is set to Stop!
Check the following section on msdn: FillBehavior="Stop" and HandoffBehavior with Multiple Animations

Resources