Click here to Skip to main content
Click here to Skip to main content

WPF: Animate Visibility Property - Update

, 13 Apr 2010
Rate this:
Please Sign up or sign in to vote.
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. Sin

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.

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
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// Supplies attached properties that provides visibility of animations
    /// <span class="code-SummaryComment"></summary>
</span>    public class VisibilityAnimation
    {
        public enum AnimationType
        {
            /// <span class="code-SummaryComment"><summary>
</span>            /// No animation
            /// <span class="code-SummaryComment"></summary>
</span>            None,
            /// <span class="code-SummaryComment"><summary>
</span>            /// Fade in / Fade out
            /// <span class="code-SummaryComment"></summary>
</span>            Fade
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Animation duration
        /// <span class="code-SummaryComment"></summary>
</span>        private const int ANIMATION_DURATION = 200;

        /// <span class="code-SummaryComment"><summary>
</span>        /// List of hooked objects
        /// <span class="code-SummaryComment"></summary>
</span>        private static readonly Dictionary<FrameworkElement, bool> _hookedElements = 
            new Dictionary<FrameworkElement, bool>();

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

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

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

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

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

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


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

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

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

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

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

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

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

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

            return animationStarted;
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Arik Poznanski
Software Developer (Senior) Verint
Israel Israel
Arik Poznanski is a senior software developer at Verint. He completed two B.Sc. degrees in Mathematics & Computer Science, summa cum laude, from the Technion in Israel.
 
Arik has extensive knowledge and experience in many Microsoft technologies, including .NET with C#, WPF, Silverlight, WinForms, Interop, COM/ATL programming, C++ Win32 programming and reverse engineering (assembly, IL).
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMemory leak Pinmemberschlaup2-Jun-11 4:27 
GeneralRe: Memory leak PinmvpArik Poznanski3-Jun-11 11:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140814.1 | Last Updated 13 Apr 2010
Article Copyright 2010 by Arik Poznanski
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid