StopStoryboard doesn't stop endless animation - wpf

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.

Related

WPF Storyboard Animation Loops Forever Even After Being Set

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.

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

Storyboard animation not completing when triggering property changes

I am using WPF and MVVM. I have a storyboard with a color animation triggering on some state in the ViewModel. If in the "Increased" state the storyboard will cause the item to flash green once. If in the "Decreased" state the storyboard will flash red once. If in the "Unchanged" state nothing will happen.
The problem I am having is if for any reason the state changes while the animation is running the animation will stop. E.g. I enter the the Increased state and the animation begins to flash green. Then the same property changes to Unchanged and the animation ceases immediately without ending.
Is there a way to have the animation run its course even though the value on which it is triggering has changed?
<Storyboard x:Key="ValueIncreasedStoryboard" AutoReverse="True">
<ColorAnimation
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="{StaticResource ResourceKey=IncreasedColor}"
Duration="0:0:0.4" />
<ColorAnimation
Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
To="{StaticResource ResourceKey=IncreasedColor}"
Duration="0:0:1" />
</Storyboard>
<DataTrigger Binding="{Binding Path=Status}" Value="{x:Static ViewModel:Status.Increased}">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource ValueIncreasedStoryboard}" x:Name="ValueIncreased_Storyboard"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="ValueIncreased_Storyboard"/>
</DataTrigger.ExitActions>
</DataTrigger>
SOLUTION:
A combination of deleting the exit action and setting the fill behavior to Stop.
Remove this lines:
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="ValueIncreased_Storyboard"/>
</DataTrigger.ExitActions>
Then subscribe to Storyboard.Completed event. You may do it from XAML using EventSetter.
In Completed event handler remove storyboard.

DataTrigger don't seems to fire

I want to create a datatrigger that makes my page blink (from transparent to red). So I created a DataTrigger that listens to a boolean flag within my viewmodel. This flag shall indicate whenever the user needs to be reminded. In that case, my page shall blink from transparent to red.
I was pretty sure that I have implemented the data trigger in a correct manner, but my app does nothing - no error, no blinking... So I must have something missed.
<Style x:Key="ReminderPage" TargetType="{x:Type ViewTemplates:TpApplicationBarView}" BasedOn="{StaticResource TpApplicationBarViewStyle}">
<Style.Triggers>
<!-- Reminder animation, when the time comes to remind the user -->
<DataTrigger Binding="{Binding IndicateReminderAnimation}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard x:Name="Blink">
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
AutoReverse="True"
From="Transparent"
To="Red"
Duration="0:0:1"
RepeatBehavior="Forever">
</ColorAnimation >
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding IndicateReminderAnimation}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
AutoReverse="False"
To="Transparent"
Duration="0:0:1">
</ColorAnimation >
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
So, what do I have done wrong?
Update: I can see the following message in the output window:
System.Windows.Media.Animation Warning: 6 : Unable to perform action because
the specified Storyboard was never applied to this object for interactive control.;
Action='Stop'; Storyboard='System.Windows.Media.Animation.Storyboard';
Storyboard.HashCode='61356140'; Storyboard.Type='System.Windows.Media.Animation.Storyboard';
TargetElement='System.Windows.Media.Animation.Storyboard'; TargetElement.HashCode='61356140';
TargetElement.Type='System.Windows.Media.Animation.Storyboard'
Update2: After googling arround I found out, that it is a problem with the UI Thread. So I made a dispatcher call whenever I set the bound property. But even with this trick, there's no color animation. But the error in the output window seems to be vanished. So, I'm searching for further ideas on how to fix the animation.
Update3: It seems to be a general problem setting the background color of the page. But it's really strange. The Page is placed in a NavigationFrame. Setting the background color of the navigation frame will change the color of the application, but setting the background color of the page (even without any animation) won't change anything.
I think you will have to set the animations Target, something like this -
Storyboard.TargetName="yourWindowName"
You may have already checked this, but make sure that correct object is set as your TpApplicationBarView's DataContext(having IndicateReminderAnimation property).
I found the bug - or better the two bugs.
1.) It seems not be possible to change the background color of a page that is placed within a Navigation Frame.
So first was to move the binding and event to the MainWindow itself (wpf window class)
2.) The Style that contains the data trigger did not work. After googling around I found a working solution for what I'm searching for.
<Storyboard x:Key="RemindUser" >
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
AutoReverse="True"
From="Transparent"
To="{StaticResource WinAccentBackgroundColor}"
Duration="0:0:1"
RepeatBehavior="Forever">
</ColorAnimation >
</Storyboard>
<Storyboard x:Key="StopRemindUser">
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
AutoReverse="True"
To="Transparent"
Duration="0:0:1">
</ColorAnimation >
</Storyboard>
<Style x:Key="ReminderWindow" TargetType="{x:Type Metro:SnappedTransparentWindow}" BasedOn="{StaticResource TransparentWindow}">
<Style.Triggers>
<!-- Reminder animation, when the time comes to remind the user -->
<DataTrigger Binding="{Binding IndicateReminderAnimation}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource RemindUser}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource StopRemindUser}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
The key was to split the binding and storyboard into different parts.

Synchronize WPF ColorAnimation across several controls

I have several toggle-like buttons that I want to pulsate in unison when in the pressed state.
I have defined a style with a trigger that kicks-off the glow animation and this works just fine, apart from the fact that each button pulsates asynchronously from the others.
How can I have each button synchronize its pulse to the others?
Here's the style:
<Storyboard x:Key="pulseStory">
<ColorAnimation
Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)"
From="Red"
To="Transparent"
Duration="0:0:1" />
</Storyboard>
<Style x:Key="pulseButton" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag,RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource pulseStory}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Cheers!
OK ... I'll take a stab at this one ...
The WPF framework doesn't have any facility for synchronizing animations that are running concurrently, so you are going to have to come up with a different method. One idea springs to mind ...
Animate some Color property of a hidden UI element within your storyboard, then use UI binding (i.e. ElementName bindings) to connect to the Color of each of your buttons to this hidden UI element.
Actually you should be doing this via a resource, at least using a hidden control is a bit too much of a hack for me personally.
What needs to be met for it to work:
The property you bind to needs to be a DependencyProperty, hence your enveloping object needs to be a DependencyObject.
You have to reference the object as a static resource (as opposed to a dynamic resource) like this:
<DoubleAnimation
Storyboard.Target="{StaticResource AnimationValue}"
Storyboard.TargetProperty="(local:WrappedValue.Value)"
To="0" Duration="0:0:1"/>
Well, admittedly it's a bit hacky as well to have a Wrapper-class for this but it's cleaner than a full control (if you want to use controls you can utilize some unused Tag-property, e.g. of the container that hosts all your controls)

Resources