Synchronize WPF ColorAnimation across several controls - wpf

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)

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.

Crash when animating a DataGridRow during a scroll

I am trying to make a DataGridRow's background colour animate itself when its data changes. I have used the advice in this post and hooked up an animation to the TargetUpdated event. This animation fires when the data is first loaded, when it scrolls and when the data changes.
My biggest issue is that when I try scrolling by dragging the scrollbar with the mouse I get a crash of the following sort:
System.InvalidOperationException: Cannot animate 'Background.Color' on an immutable object
When I scroll using the arrow buttons there's no crash. If I reach the bottom of the list and only then start dragging the scrollbar with the mouse, there's no crash.
Can anyone advise as to why this crash is happening? My best guess based on other reading is that there are several animations running for a single element concurrently because the animation is triggered by scrolling (maybe due to element resuse and contentdata switching?). There's certainly nothing in the code explicitly binding to the background color.
A related issue I have is that I only want the animation to trigger when the data actually changes - not when I scroll.
I apologise that I cannot post a copy of my code. My company's policies make that problematic. Here's a paraphrase (hopefully catching all salient details):
<Style x:Key="RowStyle" TargetType="DataGridRow">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation From="Red" Duration="0:0:1" Storyboard.TargetProperty="Background.Color">
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
The columns with data I am interested in are similar to the following:
<DataGridTextColumn Binding="{Binding Value, Mode=OneWay, NotifyOnTargetUpdated=True}" />

How do I animate a WPF control bound to a viewmodel property?

I have a WPF app, which uses MVVM. When the users edits data, if certain conditions are met, they will need to fill in revision notes for auditing purposes. If they don't need to, I hide the revision notes textbox to keep the UI clear.
At the moment this is done by binding the Visibility property of the Grid that surrounds the textbox (and its label) to a bool property on the viewmodel. When the bool changes, the revsion notes textbox is hidden or shown as necessary.
This works fine, but the textbox just appears. I would like to animate it, so it grows from zero height to its default, or something similar.
Any idea how I would do this? I have done animation before, but this was always when I manually triggered the animation. In this case, I want to declare the animation in XAML, so it happens automatically when the binding changes.
Anyone able to point me in the right direction?
Just use a DataTrigger to kick off an animation:
<Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding MyVMBool}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard >
<Storyboard>
<!-- DoubleAnimation on height or whatever -->
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<!-- you could animate close too if you wanted -->
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
Sounds like you solve it with a DataStateBehavior. Here's the MSDN documentation http://msdn.microsoft.com/en-us/library/vstudio/dn195678(v=vs.110).aspx. You could also take a look at the GoToStateAction.

How to 'Freeze' the UI (main window) when mouse is hovering over

What I want to achieve is that when the mouse is hovering over the main window, all the UI elements should freeze, which I think can be done by setting Window.IsEnabled to false, and after the mouse leaves the main window, everything should be back to normal.
I've tried to define a property trigger in a style targetting Window, but it doesn't work. The code is as lollow,
<Style.Triggers>
<Trigger Property="Window.IsMouseOver" Value="True">
<Setter Property="Window.IsEnabled" Value="false"/>
</Trigger>
</Style.Triggers>
In fact this kind of property trigger wouldn't work on Grid either. Can anyone make some explanations?
I also tried to explicitly use the MouseEnter and MouseLeave events on Window, and set the disable/enable logic in the handlers. This works. I wonder if it's possible to do this in XAML?
Well to be honest I don't know why your code doesn't work, I think it goes in some kind of conflict but I don't know why
Anyway you can do it in XAML using eventsetter, It's not so elegant but it works
<Window.Triggers>
<EventTrigger RoutedEvent="Window.MouseEnter">
<BeginStoryboard>
<Storyboard Name="sb">
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsEnabled" >
<BooleanKeyFrameCollection>
<DiscreteBooleanKeyFrame Value="False" KeyTime="0:0:0:1"></DiscreteBooleanKeyFrame>
</BooleanKeyFrameCollection>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>

WPF Animation "Cannot freeze this Storyboard timeline tree for use across threads"

I currently have a listbox that has its selected item bound to a property on my ViewModel. Whenever the selected item isn't null I want to perform an animation on it. However I keep getting the following error "Cannot freeze this Storyboard timeline tree for use across threads" and from research sort of understand why this is happening. However I am unsure of what approach I need to take to get the behavior I want.
<Storyboard x:Key="ShowItemEdit">
<DoubleAnimation
Storyboard.TargetName="lstItemList"
Storyboard.TargetProperty="ListBox.Width"
To="{Binding ActualWidth, ElementName=UserControl}"
Duration="0:0:0.40" />
...
</Storyboard>
<Style x:Key="ListStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource ShowItemEdit}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
<ListBox x:Name="lstItemList" Style={StaticResource ListStyle}" SelectedItem="{Binding SelectedItem}">
...
</ListBox>
Can you post your Storyboard? It sounds like you have some kind of Binding in the Storyboard definition.
Ok so, as I suspected, it's because you're using a Binding in your Storyboard. You can't do this because WPF attempts to freeze all the resources leveraged by a template for efficiency and when you use a Binding on a Freezable, in this case the Storyboard, it prevents it from being able to be frozen.
There is a technique that you can use to get around the Freezable issue that allows you to use a binding for the "To" value of your animation (rather than hard-coding a value there). Its pretty straightforward and I've outlined it here.
Old question but might be useful for other people.
Sometimes creating the Storyboard in the code-behind can be simpler: https://stackoverflow.com/a/10848781/779521

Resources