using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Animation;
namespace WPF.Common
{
///
/// Supplies attached properties that provides visibility of animations
///
public class VisibilityAnimation
{
public enum AnimationType
{
///
/// No animation
///
None,
///
/// Fade in / Fade out
///
Fade
}
///
/// Animation duration
///
private const int ANIMATION_DURATION = 200;
///
/// List of hooked objects
///
private static readonly Dictionary _hookedElements =
new Dictionary();
///
/// Get AnimationType attached property
///
/// Dependency object
/// AnimationType value
public static AnimationType GetAnimationType(DependencyObject obj)
{
return (AnimationType)obj.GetValue(AnimationTypeProperty);
}
///
/// Set AnimationType attached property
///
/// Dependency object
/// New value for AnimationType
public static void SetAnimationType(DependencyObject obj, AnimationType value)
{
obj.SetValue(AnimationTypeProperty, value);
}
///
/// Using a DependencyProperty as the backing store for AnimationType.
/// This enables animation, styling, binding, etc...
///
public static readonly DependencyProperty AnimationTypeProperty =
DependencyProperty.RegisterAttached(
"AnimationType",
typeof(AnimationType),
typeof(VisibilityAnimation),
new FrameworkPropertyMetadata(AnimationType.None,
new PropertyChangedCallback(OnAnimationTypePropertyChanged)));
///
/// Get IgnoreFirstTime attached property
///
/// Dependency object
/// IgnoreFirstTime value
public static bool GetIgnoreFirstTime(DependencyObject obj)
{
return (bool)obj.GetValue(IgnoreFirstTimeProperty);
}
///
/// Set IgnoreFirstTime attached property
///
/// Dependency object
/// New value for IgnoreFirstTime
public static void SetIgnoreFirstTime(DependencyObject obj, bool value)
{
obj.SetValue(IgnoreFirstTimeProperty, value);
}
///
/// Using a DependencyProperty as the backing store for IgnoreFirstTime.
/// This enables animation, styling, binding, etc...
///
public static readonly DependencyProperty IgnoreFirstTimeProperty =
DependencyProperty.RegisterAttached(
"IgnoreFirstTime",
typeof(bool),
typeof(VisibilityAnimation),
new UIPropertyMetadata(false));
///
/// AnimationType property changed
///
/// Dependency object
/// e
private static void OnAnimationTypePropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
var frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
{
return;
}
// If AnimationType is set to True on this framework element,
if (GetAnimationType(frameworkElement) != AnimationType.None)
{
// Add this framework element to hooked list
HookVisibilityChanges(frameworkElement);
}
else
{
// Otherwise, remove it from the hooked list
UnHookVisibilityChanges(frameworkElement);
}
}
///
/// Add framework element to list of hooked objects
///
/// Framework element
private static void HookVisibilityChanges(FrameworkElement frameworkElement)
{
_hookedElements.Add(frameworkElement, false);
}
///
/// Remove framework element from list of hooked objects
///
/// Framework element
private static void UnHookVisibilityChanges(FrameworkElement frameworkElement)
{
if (_hookedElements.ContainsKey(frameworkElement))
{
_hookedElements.Remove(frameworkElement);
}
}
///
/// VisibilityAnimation static ctor
///
static VisibilityAnimation()
{
// Here we "register" on Visibility property "before change" event
UIElement.VisibilityProperty.AddOwner(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
Visibility.Visible,
VisibilityChanged,
CoerceVisibility));
}
///
/// Visibility changed
///
/// Dependency object
/// e
private static void VisibilityChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
// Ignore
}
///
/// Coerce visibility
///
/// Dependency object
/// Base value
/// Coerced value
private static object CoerceVisibility(
DependencyObject dependencyObject,
object baseValue)
{
// Make sure object is a framework element
var frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
{
return baseValue;
}
// Cast to type safe value
var visibility = (Visibility)baseValue;
// If Visibility value hasn't change, do nothing.
// This can happen if the Visibility property is set using data
// binding and the binding source has changed
// but the new visibility value hasn't changed.
if (visibility == frameworkElement.Visibility)
{
return baseValue;
}
// If element is not hooked by our attached property, stop here
if (!IsHookedElement(frameworkElement))
{
return baseValue;
}
// if element has IgnoreFirstTime flag set, then ignore the first time
// the property is coerced.
if (GetIgnoreFirstTime(frameworkElement))
{
SetIgnoreFirstTime(frameworkElement, false);
return baseValue;
}
// Update animation flag
// If animation already started - don't restart it (otherwise, infinite loop)
if (UpdateAnimationStartedFlag(frameworkElement))
{
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,
// to allow the animation.
var doubleAnimation = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(ANIMATION_DURATION))
};
// When animation completes, set the visibility value to the requested
// value (baseValue)
doubleAnimation.Completed += (sender, eventArgs) =>
{
if (visibility == Visibility.Visible)
{
// In case we change into Visibility.Visible, the correct value
// is already set
// So just update the animation started flag
UpdateAnimationStartedFlag(frameworkElement);
}
else
{
// This will trigger value coercion again
// but UpdateAnimationStartedFlag() function will reture true this time,
// thus animation will not be triggered.
if (BindingOperations.IsDataBound(frameworkElement,
UIElement.VisibilityProperty))
{
// Set visiblity using bounded value
Binding bindingValue = BindingOperations.GetBinding(frameworkElement,
UIElement.VisibilityProperty);
BindingOperations.SetBinding(frameworkElement,
UIElement.VisibilityProperty, bindingValue);
}
else
{
// No binding, just assign the value
frameworkElement.Visibility = visibility;
}
}
};
if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
{
// Fade out by animating opacity
doubleAnimation.From = (double)frameworkElement.GetValue(
UIElement.OpacityProperty);
doubleAnimation.To = 0.0;
}
else
{
// Fade in by animating opacity
doubleAnimation.From = (double)frameworkElement.GetValue(
UIElement.OpacityProperty);
doubleAnimation.To = 1.0;
}
// Start animation
frameworkElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);
// Make sure the element remains visible during the animation
// The original requested value will be set in the completed event of
// the animation
return Visibility.Visible;
}
///
/// Check if framework element is hooked with AnimationType property
///
/// Framework element to check
/// Is the framework element hooked?
private static bool IsHookedElement(FrameworkElement frameworkElement)
{
return _hookedElements.ContainsKey(frameworkElement);
}
///
/// Update animation started flag or a given framework element
///
/// Given framework element
/// Old value of animation started flag
private static bool UpdateAnimationStartedFlag(FrameworkElement frameworkElement)
{
var animationStarted = _hookedElements[frameworkElement];
_hookedElements[frameworkElement] = !animationStarted;
return animationStarted;
}
}
}