In this post, I’ll show you an easy way to add fade-in / fade-out effects to your user controls, when you change their Visibility
property.
Adding the animation is done with an attached property, so using the code will be extremely simple.
Usage Sample
XAML:
<Window x:Class="WpfDemoVisibilityAnimation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="clr-namespace:WPF.Common"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Button Content="Hide" Click="Hide_Click" />
<Button Content="Show" Click="Show_Click" />
<Image common:VisibilityAnimation.AnimationType="Fade"
x:Name="Image"
Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg" />
</StackPanel>
</Window>
Code Behind:
private void Hide_Click(object sender, RoutedEventArgs e)
{
Image.Visibility = Visibility.Hidden;
}
private void Show_Click(object sender, RoutedEventArgs e)
{
Image.Visibility = Visibility.Visible;
}
Note that the only required addition for the animation effect was setting the attached property: VisibilityAnimation.AnimationType="Fade"
.
You can find here the full source code for the VisibilityAnimation
attached property and here the usage sample application.
Where is the magic? The magic resides in the implementation of the attached property. Before I show you the code, let me explain the basic idea.
- We “register” for getting visibility property “
before change
” event.
- When an element’s visibility property tries to change, somewhere in the application, we get the notification and check if the element has our attached property.
- If it has, we start an animation on the
opacity
property and force the current visibility value to Visibility.Visible
. This will allow the animation to present without interruptions.
- When the animation completes, we set the original requested value.
- Setting the original requested value will invoke (again) our “
before change
” event, so we need to keep a flag that indicates whether we already started the animation, in which case we just set the value.
Credit: The original idea is based on an answer from stackoverflow. I’ve improved the code, added support for the case where the Visibility
was getting the value using data binding and added lots of comments.
That’s it for now,
Arik Poznanski.
Appendix A – Source Code for VisibilityAnimation Class
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Animation;
namespace WPF.Common
{
public class VisibilityAnimation
{
public enum AnimationType
{
None,
Fade
}
private const int AnimationDuration = 300;
private static readonly Dictionary<FrameworkElement, bool> _hookedElements =
new Dictionary<FrameworkElement, bool>();
public static AnimationType GetAnimationType(DependencyObject obj)
{
return (AnimationType)obj.GetValue(AnimationTypeProperty);
}
public static void SetAnimationType(DependencyObject obj, AnimationType value)
{
obj.SetValue(AnimationTypeProperty, value);
}
public static readonly DependencyProperty AnimationTypeProperty =
DependencyProperty.RegisterAttached(
"AnimationType",
typeof(AnimationType),
typeof(VisibilityAnimation),
new FrameworkPropertyMetadata(AnimationType.None,
new PropertyChangedCallback(OnAnimationTypePropertyChanged)));
private static void OnAnimationTypePropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
{
return;
}
if (GetAnimationType(frameworkElement) != AnimationType.None)
{
HookVisibilityChanges(frameworkElement);
}
else
{
UnHookVisibilityChanges(frameworkElement);
}
}
private static void HookVisibilityChanges(FrameworkElement frameworkElement)
{
_hookedElements.Add(frameworkElement, false);
}
private static void UnHookVisibilityChanges(FrameworkElement frameworkElement)
{
if (_hookedElements.ContainsKey(frameworkElement))
{
_hookedElements.Remove(frameworkElement);
}
}
static VisibilityAnimation()
{
UIElement.VisibilityProperty.AddOwner(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
Visibility.Visible,
VisibilityChanged,
CoerceVisibility));
}
private static void VisibilityChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
}
private static object CoerceVisibility(
DependencyObject dependencyObject,
object baseValue)
{
FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
{
return baseValue;
}
Visibility visibility = (Visibility)baseValue;
if (visibility == frameworkElement.Visibility)
{
return baseValue;
}
if (!IsHookedElement(frameworkElement))
{
return baseValue;
}
if (UpdateAnimationStartedFlag(frameworkElement))
{
return baseValue;
}
DoubleAnimation doubleAnimation = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(AnimationDuration))
};
doubleAnimation.Completed += (sender, eventArgs) =>
{
if (visibility == Visibility.Visible)
{
UpdateAnimationStartedFlag(frameworkElement);
}
else
{
if (BindingOperations.IsDataBound(frameworkElement,
UIElement.VisibilityProperty))
{
Binding bindingValue =
BindingOperations.GetBinding(frameworkElement,
UIElement.VisibilityProperty);
BindingOperations.SetBinding(frameworkElement,
UIElement.VisibilityProperty, bindingValue);
}
else
{
frameworkElement.Visibility = visibility;
}
}
};
if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
{
doubleAnimation.From = 1.0;
doubleAnimation.To = 0.0;
}
else
{
doubleAnimation.From = 0.0;
doubleAnimation.To = 1.0;
}
frameworkElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);
return Visibility.Visible;
}
private static bool IsHookedElement(FrameworkElement frameworkElement)
{
return _hookedElements.ContainsKey(frameworkElement);
}
private static bool UpdateAnimationStartedFlag(FrameworkElement frameworkElement)
{
bool animationStarted = (bool)_hookedElements[frameworkElement];
_hookedElements[frameworkElement] = !animationStarted;
return animationStarted;
}
}
}