Fade out animation keeps repeating, possibly triggers itself? - wpf

I'm trying to create a very simple fade out effect that should happen whenever the user control's visibility changes to hidden.
To do this I created a storyboard that is triggered whenever the visibility of the user control changes to Hidden. It then first changes the visibility to visible again so I can animate it. It then changes the opacity over time, and finally changes the visibility to hidden. However this causes the animation to repeat forever.
When changing the control from visible to collapsed in the final frame it does work once, but then the user control will never become visible again (I listen for the IsVisibleChanged event and it never gets triggered after it has collapsed once, even though I literally have the code control.Visibility = Visibility.Visible; at the end of that event handler.
So how do I make sure the animation is only triggered once, preferably without code behind so I can just add this fadeout control in a nice resource dictionary and it will always work. I've seen a few questions related to this on SO especially this one but I cant get it to work.
The code I have so far:
<UserControl.Style>
<Style TargetType="{x:Type UserControl}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Hidden">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:3" FillBehavior="Stop"/>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:3.5" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Hidden</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Style>

Would it work fine if you just removed the ObjectAnimation that sets the Visibility to Hidden? So the control is technically visible, but with opacity 0?

Related

WPF routedevent storyboard begin only if height is zero

I have the following XAML for a border trigger that uses a routed event
<Border.Triggers>
<EventTrigger RoutedEvent="MouseLeftButtonUp" EnterActions="">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="0" Duration="0:0:0.4" Storyboard.Target="{Binding ElementName=messageWriterDefinition}" Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame >
<DiscreteObjectKeyFrame.Value>
<GridLength>20</GridLength>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
...
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
This trigger fires when the border and containing elements are clicked and animates cause the target to animate open from height of Zero to 200
The trigger works really well but each time the border receives the event the animation runs and the target animates open again (even if already open)
How can one add a condition to the trigger that effectively ignores the animation is the target already has a height greater than Zero?
You may use a DoubleAnimation instead of an ObjectAnimationUsingKeyFrames. By only setting its To property, but not From, the animation starts from the current property value. It requires that you also set an initial value of the Height of the Border:
<Border Height="20" ...>
<Border.Triggers>
<EventTrigger RoutedEvent="MouseLeftButtonUp">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
To="200" Duration="0:0:0.4"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>

How to reuse interactions and interactivity attached to a grid

I am trying to make use of interactivity and interactions libraries in wpf app. I need it to work on a grid, and it works well, but now I need this stuff in multiple grids and I cant find a way of reusing it. Here is the xaml
<Grid>
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding KeepAlive}"
FalseState="InactiveState"
TrueState="ActiveState"
Value="false" />
</i:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="ActiveState" />
<VisualState x:Name="InactiveState">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="ActiveContainer"
Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>False</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="InactiveContainer"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border>
<Grid>
Content comes here, Texboxes, labels when active or inactive etc.
</Grid>
</Border>
</Grid>
It works great, but how can I refactor the above code so I can reuse the exact same behaviour on multiple grids?
Thank you
A few approach immediately come up: style or resource. But Blend behavior cannot work when declared in resources. You can use attached property. See:
How to add a Blend Behavior in a Style Setter
I created generic behaviour, from which you could simply inherit and add to your style
see my answer below
https://stackoverflow.com/a/31292989/4711853

Set VerticalAlignment in an EventTrigger

How would i go about setting a property that doesn't seem to have an animation type associated with it? Specifically, I'd like to change a control's VerticalAlignment whenever an EventTrigger is activated. Here's my current status/failed attempt:
<EventTrigger RoutedEvent="my:MenuHelper.MenuIsReversed">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="VerticalAlignment" Storyboard.TargetName="Bouncy_Bar">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Top"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Which yields this exception:
Cannot animate the 'VerticalAlignment' property on a
'System.Windows.Controls.Border' using a
'System.Windows.Media.Animation.ObjectAnimationUsingKeyFrames'. For
details see the inner exception.
Inner exception:
The animation(s) applied to the 'VerticalAlignment' property calculate
a current value of 'Top', which is not a valid value for the property.
I'm not sure if i'm improperly qualifying the VerticalAlignment type or if this is simply the wrong way to go about setting an atypical animation property.
Sorry, for the quick answer of my own question. I found that I wasn't laying out the XAML to specify the target type well enough. Here was the working result:
<EventTrigger RoutedEvent="pill:PillMenuHelper.MenuIsReversed">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Bouncy_Bar" Storyboard.TargetProperty="RenderTransform.Children[1].ScaleY" To="-1" Duration="0:0:.002"/>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="VerticalAlignment" Storyboard.TargetName="Bouncy_Bar">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<VerticalAlignment>Top</VerticalAlignment>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>

Is there a way to use setters instead of animations in a storyboard?

Animations are nice, but sometimes I just want to change the value of a property instantly, without any animation. Apparently, the only way to do that is to use a <Something>AnimationUsingKeyFrames:
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtStatus" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="Red" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
The syntax is a mess... 7 lines of code just to set a property to a different value?!
Is there a simpler way?
I'd like to be able to do something like this:
<Storyboard>
<Setter TargetName="txtStatus" Property="Foreground" Value="Red" />
</Storyboard>
(I know this code isn't valid, it's just to give an example of what I'd like)
EDIT: just to make things clear: I know about Triggers and DataTriggers, but they're not what I need, because they can only be used in styles and control templates. I'm using the VisualStateManager in a UserControl, so I'm forced to use a Storyboard.
If there exists a non-keyframe animation for the type of the property that you're setting (and there are a whole bunch of non-keyframe animation types), you can use an animation with a Duration of 0. This has the effect of acting like a setter and in one line of xaml. For example:
<Storyboard>
<ColorAnimation Storyboard.TargetName="txtStatus" Storyboard.TargetProperty="Foreground" Value="Red" Duration="0" />
</Storyboard>
This works because the default FillBehavior of the animation is HoldEnd.
It's not quite the same as having the word "Setter" appear -- browsing the xaml requires some interpretation to realize that the animation has the effect of an instantaneous set. Also, the value will revert when the storyboard ends, but this does fit the bill for changing a value instantly.
You'd have to set up triggers (Which you probably already have to call the storyboards. Rather than filling them with BeginStoryboard and Storyboard items you fill them with setters.
<Trigger Property="<SomeProperty>" Value="<SomeValue>">
<Setter TargetName="txtStatus" Property="Foreground" Value="Red" />
</Trigger>

changing tooltip in visualstatemanager

i want to be able to change the tooltip on Checked and Unchecked, i tried:
<VisualState x:Name="Checked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="ToolTipService.ToolTip" Storyboard.TargetName="btn">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:String>Button is checked</System:String>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
but it doesnt seem to work, what am i doing wrong?
Once before I also, ran into the same problem. So, I left the VSM away. I made the ToolTip as Resource and applied it for the control. Using the internal state change properties I updated the tooltip value using a converter.
HTH.

Resources