WPF: Animate Visibility Property – Update

13 באפריל 2010

תגיות: , , , ,
אין תגובות

Back in this post I showed you how you can easily add a fade-in / fade-out effect to a UIElement that changes its Visibility property, using a simple attached property.


Some people encountered a problem using this property when they bind the UIElement to a model which initially hides the control. Since the default value of the Visibility property is Visible, using the attached property created an unwanted fade-out animation when the application started.


To fix this issue I added another attached property that allows the user to skip the first animation.


Also, I’ve fixed a minor issue with the double animation of the opacity.


For completion, I bring here the full updated source.
For more details on how the animation works, check the original post.


That’s it for now,
Arik Poznanski.


kick it on DotNetKicks.com Shout it


Appendix A – Updated 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
{
    /// <summary>
    /// Supplies attached properties that provides visibility of animations
    /// </summary>
    public class VisibilityAnimation
    {
        public enum AnimationType
        {
            /// <summary>
            /// No animation
            /// </summary>
            None,
            /// <summary>
            /// Fade in / Fade out
            /// </summary>
            Fade
        }

        /// <summary>
        /// Animation duration
        /// </summary>
        private const int ANIMATION_DURATION = 200;

        /// <summary>
        /// List of hooked objects
        /// </summary>
        private static readonly Dictionary<FrameworkElement, bool> _hookedElements =
            new Dictionary<FrameworkElement, bool>();

        /// <summary>
        /// Get AnimationType attached property
        /// </summary>
        /// <param name="obj">Dependency object</param>
        /// <returns>AnimationType value</returns>
        public static AnimationType GetAnimationType(DependencyObject obj)
        {
            return (AnimationType)obj.GetValue(AnimationTypeProperty);
        }

        /// <summary>
        /// Set AnimationType attached property
        /// </summary>
        /// <param name="obj">Dependency object</param>
        /// <param name="value">New value for AnimationType</param>
        public static void SetAnimationType(DependencyObject obj, AnimationType value)
        {
            obj.SetValue(AnimationTypeProperty, value);
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for AnimationType.  
        /// This enables animation, styling, binding, etc…
        /// </summary>
        public static readonly DependencyProperty AnimationTypeProperty =
            DependencyProperty.RegisterAttached(
                "AnimationType",
                typeof(AnimationType),
                typeof(VisibilityAnimation),
                new FrameworkPropertyMetadata(AnimationType.None,
                    new PropertyChangedCallback(OnAnimationTypePropertyChanged)));

        /// <summary>
        /// Get IgnoreFirstTime attached property
        /// </summary>
        /// <param name="obj">Dependency object</param>
        /// <returns>IgnoreFirstTime value</returns>
        public static bool GetIgnoreFirstTime(DependencyObject obj)
        {
            return (bool)obj.GetValue(IgnoreFirstTimeProperty);
        }

        /// <summary>
        /// Set IgnoreFirstTime attached property
        /// </summary>
        /// <param name="obj">Dependency object</param>
        /// <param name="value">New value for IgnoreFirstTime</param>
        public static void SetIgnoreFirstTime(DependencyObject obj, bool value)
        {
            obj.SetValue(IgnoreFirstTimeProperty, value);
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for IgnoreFirstTime.  
        /// This enables animation, styling, binding, etc…
        /// </summary>
        public static readonly DependencyProperty IgnoreFirstTimeProperty =
            DependencyProperty.RegisterAttached(
                "IgnoreFirstTime",
                typeof(bool),
                typeof(VisibilityAnimation),
                new UIPropertyMetadata(false));


        /// <summary>
        /// AnimationType property changed
        /// </summary>
        /// <param name="dependencyObject">Dependency object</param>
        /// <param name="e">e</param>
        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);
            }
        }

        /// <summary>
        /// Add framework element to list of hooked objects
        /// </summary>
        /// <param name="frameworkElement">Framework element</param>
        private static void HookVisibilityChanges(FrameworkElement frameworkElement)
        {
            _hookedElements.Add(frameworkElement, false);
        }

        /// <summary>
        /// Remove framework element from list of hooked objects
        /// </summary>
        /// <param name="frameworkElement">Framework element</param>
        private static void UnHookVisibilityChanges(FrameworkElement frameworkElement)
        {
            if (_hookedElements.ContainsKey(frameworkElement))
            {
                _hookedElements.Remove(frameworkElement);
            }
        }

        /// <summary>
        /// VisibilityAnimation static ctor
        /// </summary>
        static VisibilityAnimation()
        {
            // Here we "register" on Visibility property "before change" event
            UIElement.VisibilityProperty.AddOwner(
                typeof(FrameworkElement),
                new FrameworkPropertyMetadata(
                    Visibility.Visible,
                    VisibilityChanged,
                    CoerceVisibility));
        }

        /// <summary>
        /// Visibility changed
        /// </summary>
        /// <param name="dependencyObject">Dependency object</param>
        /// <param name="e">e</param>
        private static void VisibilityChanged(
            DependencyObject dependencyObject,
            DependencyPropertyChangedEventArgs e)
        {
            // Ignore
        }

        /// <summary>
        /// Coerce visibility
        /// </summary>
        /// <param name="dependencyObject">Dependency object</param>
        /// <param name="baseValue">Base value</param>
        /// <returns>Coerced value</returns>
        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;
        }

        /// <summary>
        /// Check if framework element is hooked with AnimationType property
        /// </summary>
        /// <param name="frameworkElement">Framework element to check</param>
        /// <returns>Is the framework element hooked?</returns>
        private static bool IsHookedElement(FrameworkElement frameworkElement)
        {
            return _hookedElements.ContainsKey(frameworkElement);
        }

        /// <summary>
        /// Update animation started flag or a given framework element
        /// </summary>
        /// <param name="frameworkElement">Given framework element</param>
        /// <returns>Old value of animation started flag</returns>
        private static bool UpdateAnimationStartedFlag(FrameworkElement frameworkElement)
        {
            var animationStarted = _hookedElements[frameworkElement];
            _hookedElements[frameworkElement] = !animationStarted;

            return animationStarted;
        }
    }
}

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים