How would I make a control fade in/out when it becomes Visible.
Below is my failed attempt:
You may want to try AutoReverse property ... though I am not sure if it works the way you want it to. This is what I found on MSDN :
When a timeline's AutoReverse property is set to true and its RepeatBehavior property causes it to repeat, each forward iteration is followed by a backward iteration. This makes one repetition. For example, a timeline with an AutoReverse value of true with an iteration Count of 2 would play forward once, then backwards, then forwards again, and then backwards again.
I've been coming at this a slightly different way - I've got an extended version of Ray's answer to this question which adds a FadeIn() and FadeOut() extension method to everything that collapses or shows the element as appropriate, then instead of making objects visible I can just call FadeIn() and FadeOut() on them - and it will work an any element without any specific animation code.
public static T FadeFromTo(this UIElement uiElement, double fromOpacity,
double toOpacity, int durationInMilliseconds, bool loopAnimation,
bool showOnStart, bool collapseOnFinish)
{
var timeSpan = TimeSpan.FromMilliseconds(durationInMilliseconds);
var doubleAnimation =
new DoubleAnimation(fromOpacity, toOpacity,
new Duration(timeSpan));
if (loopAnimation)
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
uiElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);
if (showOnStart)
{
uiElement.ApplyAnimationClock(UIElement.VisibilityProperty, null);
uiElement.Visibility = Visibility.Visible;
}
if (collapseOnFinish)
{
var keyAnimation = new ObjectAnimationUsingKeyFrames{Duration = new Duration(timeSpan) };
keyAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame(Visibility.Collapsed, KeyTime.FromTimeSpan(timeSpan)));
uiElement.BeginAnimation(UIElement.VisibilityProperty, keyAnimation);
}
return uiElement;
}
public static T FadeIn(this UIElement uiElement, int durationInMilliseconds)
{
return uiElement.FadeFromTo(0, 1, durationInMilliseconds, false, true, false);
}
public static T FadeOut(this UIElement uiElement, int durationInMilliseconds)
{
return uiElement.FadeFromTo(1, 0, durationInMilliseconds, false, false, true);
}
I don't know how to do both animations (fade in and fade out) in pure XAML. But simple fade out can be achieved relatively simple. Replace DataTriggers with Triggers, and remove ExitActions since they makes no sense in Fade out scenario. This is what you will have:
<Style TargetType="FrameworkElement" x:Key="animatedList">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
But hey, don't give up. If you want to support both animations I can suggest small coding behind the XAML. After we do a trick, we will get what you want by adding one line of code in XAML:
<Button Content="Fading button"
x:Name="btn"
loc:VisibilityAnimation.IsActive="True"/>
Every time we change btn.Visibility from Visible to Hidden/Collapsed button will fade out. And every time we change Visibility back the button will fade in. This trick will work with any FrameworkElement (including ListView :) ).
Here is the code of VisibilityAnimation.IsActive attached property:
public class VisibilityAnimation : DependencyObject
{
private const int DURATION_MS = 200;
private static readonly Hashtable _hookedElements = new Hashtable();
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive",
typeof(bool),
typeof(VisibilityAnimation),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
public static bool GetIsActive(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsActiveProperty);
}
public static void SetIsActive(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsActiveProperty, value);
}
static VisibilityAnimation()
{
UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement),
new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// So what? Ignore.
}
private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return;
}
if (GetIsActive(fe))
{
HookVisibilityChanges(fe);
}
else
{
UnHookVisibilityChanges(fe);
}
}
private static void UnHookVisibilityChanges(FrameworkElement fe)
{
if (_hookedElements.Contains(fe))
{
_hookedElements.Remove(fe);
}
}
private static void HookVisibilityChanges(FrameworkElement fe)
{
_hookedElements.Add(fe, false);
}
private static object CoerceVisibility(DependencyObject d, object baseValue)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return baseValue;
}
if (CheckAndUpdateAnimationStartedFlag(fe))
{
return baseValue;
}
// If we get here, it means we have to start fade in or fade out
// animation. In any case return value of this method will be
// Visibility.Visible.
var visibility = (Visibility)baseValue;
var da = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS))
};
da.Completed += (o, e) =>
{
// This will trigger value coercion again
// but CheckAndUpdateAnimationStartedFlag() function will reture true
// this time, and animation will not be triggered.
fe.Visibility = visibility;
// NB: Small problem here. This may and probably will brake
// binding to visibility property.
};
if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
{
da.From = 1.0;
da.To = 0.0;
}
else
{
da.From = 0.0;
da.To = 1.0;
}
fe.BeginAnimation(UIElement.OpacityProperty, da);
return Visibility.Visible;
}
private static bool CheckAndUpdateAnimationStartedFlag(FrameworkElement fe)
{
var hookedElement = _hookedElements.Contains(fe);
if (!hookedElement)
{
return true; // don't need to animate unhooked elements.
}
var animationStarted = (bool) _hookedElements[fe];
_hookedElements[fe] = !animationStarted;
return animationStarted;
}
}
The most important thing here is CoerceVisibility() method. As you can see we do not allow changing this property until fading animation is completed.
This code is neither thread safe nor bug free. Its only intention is to show the direction :). So feel free to improve, edit and get reputation ;).
You can't directly use the Visibility property for a fade out because setting a trigger on it will first Hide/Collapse the control, THEN animate it. So basically you'll got an animation on a collapsed control => nothing.
One "reliable" way would be to introduce a new Dependency Property (attached or not), say IsOpen
and setting a property trigger IsOpen=True
on it with:
Here's an example:
<Style TargetType="{x:Type local:TCMenu}">
<Style.Resources>
<Storyboard x:Key="FadeInMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOutMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsOpen" Value="true">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
This is best done using a behavior
class AnimatedVisibilityFadeBehavior : Behavior<Border>
{
public Duration AnimationDuration { get; set; }
public Visibility InitialState { get; set; }
DoubleAnimation m_animationOut;
DoubleAnimation m_animationIn;
protected override void OnAttached()
{
base.OnAttached();
m_animationIn = new DoubleAnimation(1, AnimationDuration, FillBehavior.HoldEnd);
m_animationOut = new DoubleAnimation(0, AnimationDuration, FillBehavior.HoldEnd);
m_animationOut.Completed += (sender, args) =>
{
AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Collapsed);
};
AssociatedObject.SetCurrentValue(Border.VisibilityProperty,
InitialState == Visibility.Collapsed
? Visibility.Collapsed
: Visibility.Visible);
Binding.AddTargetUpdatedHandler(AssociatedObject, Updated);
}
private void Updated(object sender, DataTransferEventArgs e)
{
var value = (Visibility)AssociatedObject.GetValue(Border.VisibilityProperty);
switch (value)
{
case Visibility.Collapsed:
AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Visible);
AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationOut);
break;
case Visibility.Visible:
AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationIn);
break;
}
}
}
This is specifically being applied to a border - I haven't tried a user control but I expect the same applies.
To use it, you need the Blend Interactivity namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And use this markup on the Border that you want the behavior on:
<i:Interaction.Behaviors>
<Interactivity:AnimatedVisibilityFadeBehavior AnimationDuration="0:0:0.3" InitialState="Collapsed" />
</i:Interaction.Behaviors>
You'll need to add in the namespace for the behavior class too..
Quite old now, but could you not just chain the DoubleAnimations?
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:5"
/>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:5"
/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>