Click here to Skip to main content
15,886,362 members
Articles / Desktop Programming / WPF

WPFSpark: 6 of n: FluidProgressBar

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
19 Jan 2012Ms-PL4 min read 45.2K   1.5K   20  
A Windows Phone style indeterminate ProgressBar in WPF.
#region File Header

// -------------------------------------------------------------------------------
// 
// This file is part of the WPFSpark project: http://wpfspark.codeplex.com/
//
// Author: Ratish Philip
// 
// WPFSpark v1.1
//
// -------------------------------------------------------------------------------

#endregion

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace WPFSpark
{
    /// <summary>
    /// Interaction logic for FluidProgressBar.xaml
    /// </summary>
    public partial class FluidProgressBar : UserControl, IDisposable
    {
        #region Internal class

        private class KeyFrameDetails
        {
            public KeyTime KeyFrameTime { get; set; }
            public List<DoubleKeyFrame> KeyFrames { get; set; }
        }

        #endregion

        #region Fields

        Dictionary<int, KeyFrameDetails> keyFrameMap = null;
        Dictionary<int, KeyFrameDetails> opKeyFrameMap = null;
        //KeyTime keyA = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0));
        //KeyTime keyB = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.5));
        //KeyTime keyC = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2.0));
        //KeyTime keyD = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2.5));
        Storyboard sb;
        bool isStoryboardRunning;

        #endregion

        #region Dependency Properties

        #region Delay

        /// <summary>
        /// Delay Dependency Property
        /// </summary>
        public static readonly DependencyProperty DelayProperty =
            DependencyProperty.Register("Delay", typeof(Duration), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(new Duration(TimeSpan.FromMilliseconds(100)), new PropertyChangedCallback(OnDelayChanged)));

        /// <summary>
        /// Gets or sets the Delay property. This dependency property 
        /// indicates the delay between adjacent animation timelines.
        /// </summary>
        public Duration Delay
        {
            get { return (Duration)GetValue(DelayProperty); }
            set { SetValue(DelayProperty, value); }
        }

        /// <summary>
        /// Handles changes to the Delay property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDelayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            Duration oldDelay = (Duration)e.OldValue;
            Duration newDelay = pBar.Delay;
            pBar.OnDelayChanged(oldDelay, newDelay);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the Delay property.
        /// </summary>
        /// <param name="oldDelay">Old Value</param>
        /// <param name="newDelay">New Value</param>
        protected virtual void OnDelayChanged(Duration oldDelay, Duration newDelay)
        {
            bool isActive = isStoryboardRunning;
            if (isActive)
                StopFluidAnimation();

            UpdateTimelineDelay(newDelay);

            if (isActive)
                StartFluidAnimation();
        }

        #endregion

        #region DotWidth

        /// <summary>
        /// DotWidth Dependency Property
        /// </summary>
        public static readonly DependencyProperty DotWidthProperty =
            DependencyProperty.Register("DotWidth", typeof(double), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(4.0, new PropertyChangedCallback(OnDotWidthChanged)));

        /// <summary>
        /// Gets or sets the DotWidth property. This dependency property 
        /// indicates the width of each of the dots.
        /// </summary>
        public double DotWidth
        {
            get { return (double)GetValue(DotWidthProperty); }
            set { SetValue(DotWidthProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DotWidth property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDotWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            double oldDotWidth = (double)e.OldValue;
            double newDotWidth = pBar.DotWidth;
            pBar.OnDotWidthChanged(oldDotWidth, newDotWidth);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DotWidth property.
        /// </summary>
        /// <param name="oldDotWidth">Old Value</param>
        /// <param name="newDotWidth">New Value</param>
        protected virtual void OnDotWidthChanged(double oldDotWidth, double newDotWidth)
        {
            if (isStoryboardRunning)
                RestartStoryboardAnimation();
        }

        #endregion

        #region DotHeight

        /// <summary>
        /// DotHeight Dependency Property
        /// </summary>
        public static readonly DependencyProperty DotHeightProperty =
            DependencyProperty.Register("DotHeight", typeof(double), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(4.0, new PropertyChangedCallback(OnDotHeightChanged)));

        /// <summary>
        /// Gets or sets the DotHeight property. This dependency property 
        /// indicates the height of each of the dots.
        /// </summary>
        public double DotHeight
        {
            get { return (double)GetValue(DotHeightProperty); }
            set { SetValue(DotHeightProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DotHeight property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDotHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            double oldDotHeight = (double)e.OldValue;
            double newDotHeight = pBar.DotHeight;
            pBar.OnDotHeightChanged(oldDotHeight, newDotHeight);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DotHeight property.
        /// </summary>
        /// <param name="oldDotHeight">Old Value</param>
        /// <param name="newDotHeight">New Value</param>
        protected virtual void OnDotHeightChanged(double oldDotHeight, double newDotHeight)
        {
            if (isStoryboardRunning)
                RestartStoryboardAnimation();
        }

        #endregion

        #region DotRadiusX

        /// <summary>
        /// DotRadiusX Dependency Property
        /// </summary>
        public static readonly DependencyProperty DotRadiusXProperty =
            DependencyProperty.Register("DotRadiusX", typeof(double), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnDotRadiusXChanged)));

        /// <summary>
        /// Gets or sets the DotRadiusX property. This dependency property 
        /// indicates the corner radius width of each of the dot.
        /// </summary>
        public double DotRadiusX
        {
            get { return (double)GetValue(DotRadiusXProperty); }
            set { SetValue(DotRadiusXProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DotRadiusX property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDotRadiusXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            double oldDotRadiusX = (double)e.OldValue;
            double newDotRadiusX = pBar.DotRadiusX;
            pBar.OnDotRadiusXChanged(oldDotRadiusX, newDotRadiusX);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DotRadiusX property.
        /// </summary>
        /// <param name="oldDotRadiusX">Old Value</param>
        /// <param name="newDotRadiusX">New Value</param>
        protected virtual void OnDotRadiusXChanged(double oldDotRadiusX, double newDotRadiusX)
        {
            if (isStoryboardRunning)
                RestartStoryboardAnimation();
        }

        #endregion

        #region DotRadiusY

        /// <summary>
        /// DotRadiusY Dependency Property
        /// </summary>
        public static readonly DependencyProperty DotRadiusYProperty =
            DependencyProperty.Register("DotRadiusY", typeof(double), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnDotRadiusYChanged)));

        /// <summary>
        /// Gets or sets the DotRadiusY property. This dependency property 
        /// indicates the corner height of each of the dots.
        /// </summary>
        public double DotRadiusY
        {
            get { return (double)GetValue(DotRadiusYProperty); }
            set { SetValue(DotRadiusYProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DotRadiusY property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDotRadiusYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            double oldDotRadiusY = (double)e.OldValue;
            double newDotRadiusY = pBar.DotRadiusY;
            pBar.OnDotRadiusYChanged(oldDotRadiusY, newDotRadiusY);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DotRadiusY property.
        /// </summary>
        /// <param name="oldDotRadiusY">Old Value</param>
        /// <param name="newDotRadiusY">New Value</param>
        protected virtual void OnDotRadiusYChanged(double oldDotRadiusY, double newDotRadiusY)
        {
            if (isStoryboardRunning)
                RestartStoryboardAnimation();
        }

        #endregion

        #region DurationA

        /// <summary>
        /// DurationA Dependency Property
        /// </summary>
        public static readonly DependencyProperty DurationAProperty =
            DependencyProperty.Register("DurationA", typeof(Duration), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5)), new PropertyChangedCallback(OnDurationAChanged)));

        /// <summary>
        /// Gets or sets the DurationA property. This dependency property 
        /// indicates the duration of the animation from the start point till KeyFrameA.
        /// </summary>
        public Duration DurationA
        {
            get { return (Duration)GetValue(DurationAProperty); }
            set { SetValue(DurationAProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DurationA property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDurationAChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            Duration oldDurationA = (Duration)e.OldValue;
            Duration newDurationA = pBar.DurationA;
            pBar.OnDurationAChanged(oldDurationA, newDurationA);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DurationA property.
        /// </summary>
        /// <param name="oldDurationA">Old Value</param>
        /// <param name="newDurationA">New Value</param>
        protected virtual void OnDurationAChanged(Duration oldDurationA, Duration newDurationA)
        {
            bool isActive = isStoryboardRunning;
            if (isActive)
                StopFluidAnimation();

            UpdateKeyTimes(1, newDurationA);

            if (isActive)
                StartFluidAnimation();
        }

        #endregion

        #region DurationB

        /// <summary>
        /// DurationB Dependency Property
        /// </summary>
        public static readonly DependencyProperty DurationBProperty =
            DependencyProperty.Register("DurationB", typeof(Duration), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(1.5)), new PropertyChangedCallback(OnDurationBChanged)));

        /// <summary>
        /// Gets or sets the DurationB property. This dependency property 
        /// indicates the duration of the animation from the KeyFrameA till KeyFrameB.
        /// </summary>
        public Duration DurationB
        {
            get { return (Duration)GetValue(DurationBProperty); }
            set { SetValue(DurationBProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DurationB property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDurationBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            Duration oldDurationB = (Duration)e.OldValue;
            Duration newDurationB = pBar.DurationB;
            pBar.OnDurationBChanged(oldDurationB, newDurationB);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DurationB property.
        /// </summary>
        /// <param name="oldDurationB">Old Value</param>
        /// <param name="newDurationB">New Value</param>
        protected virtual void OnDurationBChanged(Duration oldDurationB, Duration newDurationB)
        {
            bool isActive = isStoryboardRunning;
            if (isActive)
                StopFluidAnimation();

            UpdateKeyTimes(2, newDurationB);

            if (isActive)
                StartFluidAnimation();
        }

        #endregion

        #region DurationC

        /// <summary>
        /// DurationC Dependency Property
        /// </summary>
        public static readonly DependencyProperty DurationCProperty =
            DependencyProperty.Register("DurationC", typeof(Duration), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5)), new PropertyChangedCallback(OnDurationCChanged)));

        /// <summary>
        /// Gets or sets the DurationC property. This dependency property 
        /// indicates the duration of the animation from KeyFrameB till the end point.
        /// </summary>
        public Duration DurationC
        {
            get { return (Duration)GetValue(DurationCProperty); }
            set { SetValue(DurationCProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DurationC property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnDurationCChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            Duration oldDurationC = (Duration)e.OldValue;
            Duration newDurationC = pBar.DurationC;
            pBar.OnDurationCChanged(oldDurationC, newDurationC);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DurationC property.
        /// </summary>
        /// <param name="oldDurationC">Old Value</param>
        /// <param name="newDurationC">New Value</param>
        protected virtual void OnDurationCChanged(Duration oldDurationC, Duration newDurationC)
        {
            bool isActive = isStoryboardRunning;
            if (isActive)
                StopFluidAnimation();

            UpdateKeyTimes(3, newDurationC);

            if (isActive)
                StartFluidAnimation();
        }

        #endregion

        #region KeyFrameA

        /// <summary>
        /// KeyFrameA Dependency Property
        /// </summary>
        public static readonly DependencyProperty KeyFrameAProperty =
            DependencyProperty.Register("KeyFrameA", typeof(double), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(0.33, new PropertyChangedCallback(OnKeyFrameAChanged)));

        /// <summary>
        /// Gets or sets the KeyFrameA property. This dependency property 
        /// indicates the first KeyFrame position after the initial keyframe.
        /// </summary>
        public double KeyFrameA
        {
            get { return (double)GetValue(KeyFrameAProperty); }
            set { SetValue(KeyFrameAProperty, value); }
        }

        /// <summary>
        /// Handles changes to the KeyFrameA property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnKeyFrameAChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            double oldKeyFrameA = (double)e.OldValue;
            double newKeyFrameA = pBar.KeyFrameA;
            pBar.OnKeyFrameAChanged(oldKeyFrameA, newKeyFrameA);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the KeyFrameA property.
        /// </summary>
        /// <param name="oldKeyFrameA">Old Value</param>
        /// <param name="newKeyFrameA">New Value</param>
        protected virtual void OnKeyFrameAChanged(double oldKeyFrameA, double newKeyFrameA)
        {
            RestartStoryboardAnimation();
        }

        #endregion

        #region KeyFrameB

        /// <summary>
        /// KeyFrameB Dependency Property
        /// </summary>
        public static readonly DependencyProperty KeyFrameBProperty =
            DependencyProperty.Register("KeyFrameB", typeof(double), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(0.63, new PropertyChangedCallback(OnKeyFrameBChanged)));

        /// <summary>
        /// Gets or sets the KeyFrameB property. This dependency property 
        /// indicates the second KeyFrame position after the initial keyframe.
        /// </summary>
        public double KeyFrameB
        {
            get { return (double)GetValue(KeyFrameBProperty); }
            set { SetValue(KeyFrameBProperty, value); }
        }

        /// <summary>
        /// Handles changes to the KeyFrameB property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnKeyFrameBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            double oldKeyFrameB = (double)e.OldValue;
            double newKeyFrameB = pBar.KeyFrameB;
            pBar.OnKeyFrameBChanged(oldKeyFrameB, newKeyFrameB);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the KeyFrameB property.
        /// </summary>
        /// <param name="oldKeyFrameB">Old Value</param>
        /// <param name="newKeyFrameB">New Value</param>
        protected virtual void OnKeyFrameBChanged(double oldKeyFrameB, double newKeyFrameB)
        {
            RestartStoryboardAnimation();
        }

        #endregion

        #region Oscillate

        /// <summary>
        /// Oscillate Dependency Property
        /// </summary>
        public static readonly DependencyProperty OscillateProperty =
            DependencyProperty.Register("Oscillate", typeof(bool), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnOscillateChanged)));

        /// <summary>
        /// Gets or sets the Oscillate property. This dependency property 
        /// indicates whether the animation should oscillate.
        /// </summary>
        public bool Oscillate
        {
            get { return (bool)GetValue(OscillateProperty); }
            set { SetValue(OscillateProperty, value); }
        }

        /// <summary>
        /// Handles changes to the Oscillate property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnOscillateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            bool oldOscillate = (bool)e.OldValue;
            bool newOscillate = pBar.Oscillate;
            pBar.OnOscillateChanged(oldOscillate, newOscillate);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the Oscillate property.
        /// </summary>
        /// <param name="oldOscillate">Old Value</param>
        /// <param name="newOscillate">New Value</param>
        protected virtual void OnOscillateChanged(bool oldOscillate, bool newOscillate)
        {
            if (sb != null)
            {
                StopFluidAnimation();
                sb.AutoReverse = newOscillate;
                sb.Duration = newOscillate ? ReverseDuration : TotalDuration;
                StartFluidAnimation();
            }
        }

        #endregion

        #region ReverseDuration

        /// <summary>
        /// ReverseDuration Dependency Property
        /// </summary>
        public static readonly DependencyProperty ReverseDurationProperty =
            DependencyProperty.Register("ReverseDuration", typeof(Duration), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(2.9)), new PropertyChangedCallback(OnReverseDurationChanged)));

        /// <summary>
        /// Gets or sets the ReverseDuration property. This dependency property 
        /// indicates the duration of the total animation in reverse.
        /// </summary>
        public Duration ReverseDuration
        {
            get { return (Duration)GetValue(ReverseDurationProperty); }
            set { SetValue(ReverseDurationProperty, value); }
        }

        /// <summary>
        /// Handles changes to the ReverseDuration property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnReverseDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            Duration oldReverseDuration = (Duration)e.OldValue;
            Duration newReverseDuration = pBar.ReverseDuration;
            pBar.OnReverseDurationChanged(oldReverseDuration, newReverseDuration);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ReverseDuration property.
        /// </summary>
        /// <param name="oldReverseDuration">Old Value</param>
        /// <param name="newReverseDuration">New Value</param>
        protected virtual void OnReverseDurationChanged(Duration oldReverseDuration, Duration newReverseDuration)
        {
            if ((sb != null) && (Oscillate))
            {
                sb.Duration = newReverseDuration;
                RestartStoryboardAnimation();
            }
        }

        #endregion

        #region TotalDuration

        /// <summary>
        /// TotalDuration Dependency Property
        /// </summary>
        public static readonly DependencyProperty TotalDurationProperty =
            DependencyProperty.Register("TotalDuration", typeof(Duration), typeof(FluidProgressBar),
                new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(4.4)), new PropertyChangedCallback(OnTotalDurationChanged)));

        /// <summary>
        /// Gets or sets the TotalDuration property. This dependency property 
        /// indicates the duration of the complete animation.
        /// </summary>
        public Duration TotalDuration
        {
            get { return (Duration)GetValue(TotalDurationProperty); }
            set { SetValue(TotalDurationProperty, value); }
        }

        /// <summary>
        /// Handles changes to the TotalDuration property.
        /// </summary>
        /// <param name="d">FluidProgressBar</param>
        /// <param name="e">DependencyProperty changed event arguments</param>
        private static void OnTotalDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FluidProgressBar pBar = (FluidProgressBar)d;
            Duration oldTotalDuration = (Duration)e.OldValue;
            Duration newTotalDuration = pBar.TotalDuration;
            pBar.OnTotalDurationChanged(oldTotalDuration, newTotalDuration);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the TotalDuration property.
        /// </summary>
        /// <param name="oldTotalDuration">Old Value</param>
        /// <param name="newTotalDuration">New Value</param>
        protected virtual void OnTotalDurationChanged(Duration oldTotalDuration, Duration newTotalDuration)
        {
            if ((sb != null) && (!Oscillate))
            {
                sb.Duration = newTotalDuration;
                RestartStoryboardAnimation();
            }
        }

        #endregion

        #endregion

        #region Construction / Initialization

        /// <summary>
        /// Ctor
        /// </summary>
        public FluidProgressBar()
        {
            InitializeComponent();

            keyFrameMap = new Dictionary<int, KeyFrameDetails>();
            opKeyFrameMap = new Dictionary<int, KeyFrameDetails>();

            GetKeyFramesFromStoryboard();

            this.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
            this.Loaded += new RoutedEventHandler(OnLoaded);
            this.IsVisibleChanged += new DependencyPropertyChangedEventHandler(OnIsVisibleChanged);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the Loaded event
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">EventArgs</param>
        void OnLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            // Update the key frames
            UpdateKeyFrames();
            // Start the animation
            StartFluidAnimation();
        }

        /// <summary>
        /// Handles the SizeChanged event
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">EventArgs</param>
        void OnSizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
        {
            // Restart the animation
            RestartStoryboardAnimation();
        }

        /// <summary>
        /// Handles the IsVisibleChanged event
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">EventArgs</param>
        void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (this.Visibility == Visibility.Visible)
            {
                UpdateKeyFrames();
                StartFluidAnimation();
            }
            else
            {
                StopFluidAnimation();
            }
        }

        #endregion

        #region Helpers

        /// <summary>
        /// Starts the animation
        /// </summary>
        private void StartFluidAnimation()
        {
            if ((sb != null) && (!isStoryboardRunning))
            {
                sb.Begin();
                isStoryboardRunning = true;
            }
        }

        /// <summary>
        /// Stops the animation
        /// </summary>
        private void StopFluidAnimation()
        {
            if ((sb != null) && (isStoryboardRunning))
            {
                // Move the timeline to the end and stop the animation
                sb.SeekAlignedToLastTick(TimeSpan.FromSeconds(0));
                sb.Stop();
                isStoryboardRunning = false;
            }
        }

        /// <summary>
        /// Stops the animation, updates the keyframes and starts the animation
        /// </summary>
        private void RestartStoryboardAnimation()
        {
            StopFluidAnimation();
            UpdateKeyFrames();
            StartFluidAnimation();
        }

        /// <summary>
        /// Obtains the keyframes for each animation in the storyboard so that
        /// they can be updated when required.
        /// </summary>
        private void GetKeyFramesFromStoryboard()
        {
            sb = (Storyboard)this.Resources["FluidStoryboard"];
            if (sb != null)
            {
                foreach (Timeline timeline in sb.Children)
                {
                    DoubleAnimationUsingKeyFrames dakeys = timeline as DoubleAnimationUsingKeyFrames;
                    if (dakeys != null)
                    {
                        string targetName = Storyboard.GetTargetName(dakeys);
                        ProcessDoubleAnimationWithKeys(dakeys, !targetName.StartsWith("Trans"));
                    }
                }
            }
        }

        /// <summary>
        /// Gets the keyframes in the given animation and stores them in a map
        /// </summary>
        /// <param name="dakeys">Animation containg keyframes</param>
        /// <param name="isOpacityAnim">Flag to indicate whether the animation targets the opacity or the translate transform</param>
        private void ProcessDoubleAnimationWithKeys(DoubleAnimationUsingKeyFrames dakeys, bool isOpacityAnim = false)
        {
            // Get all the keyframes in the instance.
            for (int i = 0; i < dakeys.KeyFrames.Count; i++)
            {
                DoubleKeyFrame frame = dakeys.KeyFrames[i];

                Dictionary<int, KeyFrameDetails> targetMap = null;

                if (isOpacityAnim)
                {
                    targetMap = opKeyFrameMap;
                }
                else
                {
                    targetMap = keyFrameMap;
                }

                if (!targetMap.ContainsKey(i))
                {
                    targetMap[i] = new KeyFrameDetails() { KeyFrames = new List<DoubleKeyFrame>() };
                }

                // Update the keyframe time and add it to the map
                targetMap[i].KeyFrameTime = frame.KeyTime;
                targetMap[i].KeyFrames.Add(frame);
            }
        }

        /// <summary>
        /// Update the key value of each keyframe based on the current width of the FluidProgressBar
        /// </summary>
        private void UpdateKeyFrames()
        {
            // Get the current width of the FluidProgressBar
            double width = this.ActualWidth;
            // Update the values only if the current width is greater than Zero and is visible
            if ((width > 0.0) && (this.Visibility == System.Windows.Visibility.Visible))
            {
                double Point0 = -10;
                double PointA = width * KeyFrameA;
                double PointB = width * KeyFrameB;
                double PointC = width + 10;
                // Update the keyframes stored in the map
                UpdateKeyFrame(0, Point0);
                UpdateKeyFrame(1, PointA);
                UpdateKeyFrame(2, PointB);
                UpdateKeyFrame(3, PointC);
            }
        }

        /// <summary>
        /// Update the key value of the keyframes stored in the map
        /// </summary>
        /// <param name="key">Key of the dictionary</param>
        /// <param name="newValue">New value to be given to the key value of the keyframes</param>
        private void UpdateKeyFrame(int key, double newValue)
        {
            if (keyFrameMap.ContainsKey(key))
            {
                foreach (var frame in keyFrameMap[key].KeyFrames)
                {
                    if (frame is LinearDoubleKeyFrame)
                    {
                        frame.SetValue(LinearDoubleKeyFrame.ValueProperty, newValue);
                    }
                    else if (frame is EasingDoubleKeyFrame)
                    {
                        frame.SetValue(EasingDoubleKeyFrame.ValueProperty, newValue);
                    }
                }
            }
        }

        /// <summary>
        /// Updates the duration of each of the keyframes stored in the map
        /// </summary>
        /// <param name="key">Key of the dictionary</param>
        /// <param name="newValue">New value to be given to the duration value of the keyframes</param>
        private void UpdateKeyTimes(int key, Duration newDuration)
        {
            switch (key)
            {
                case 1:
                    UpdateKeyTime(1, newDuration);
                    UpdateKeyTime(2, newDuration + DurationB);
                    UpdateKeyTime(3, newDuration + DurationB + DurationC);
                    break;

                case 2:
                    UpdateKeyTime(2, DurationA + newDuration);
                    UpdateKeyTime(3, DurationA + newDuration + DurationC);
                    break;

                case 3:
                    UpdateKeyTime(3, DurationA + DurationB + newDuration);
                    break;

                default:
                    break;
            }

            // Update the opacity animation duration based on the complete duration
            // of the animation
            UpdateOpacityKeyTime(1, DurationA + DurationB + DurationC);
        }

        /// <summary>
        /// Updates the duration of each of the keyframes stored in the map
        /// </summary>
        /// <param name="key">Key of the dictionary</param>
        /// <param name="newDuration">New value to be given to the duration value of the keyframes</param>
        private void UpdateKeyTime(int key, Duration newDuration)
        {
            if (keyFrameMap.ContainsKey(key))
            {
                KeyTime newKeyTime = KeyTime.FromTimeSpan(newDuration.TimeSpan);
                keyFrameMap[key].KeyFrameTime = newKeyTime;

                foreach (var frame in keyFrameMap[key].KeyFrames)
                {
                    if (frame is LinearDoubleKeyFrame)
                    {
                        frame.SetValue(LinearDoubleKeyFrame.KeyTimeProperty, newKeyTime);
                    }
                    else if (frame is EasingDoubleKeyFrame)
                    {
                        frame.SetValue(EasingDoubleKeyFrame.KeyTimeProperty, newKeyTime);
                    }
                }
            }
        }

        /// <summary>
        /// Updates the duration of the second keyframe of all the opacity animations
        /// </summary>
        /// <param name="key">Key of the dictionary</param>
        /// <param name="newDuration">New value to be given to the duration value of the keyframes</param>
        private void UpdateOpacityKeyTime(int key, Duration newDuration)
        {
            if (opKeyFrameMap.ContainsKey(key))
            {
                KeyTime newKeyTime = KeyTime.FromTimeSpan(newDuration.TimeSpan);
                opKeyFrameMap[key].KeyFrameTime = newKeyTime;

                foreach (var frame in opKeyFrameMap[key].KeyFrames)
                {
                    if (frame is DiscreteDoubleKeyFrame)
                    {
                        frame.SetValue(DiscreteDoubleKeyFrame.KeyTimeProperty, newKeyTime);
                    }
                }
            }
        }

        /// <summary>
        /// Updates the delay between consecutive timelines
        /// </summary>
        /// <param name="newDelay">Delay duration</param>
        private void UpdateTimelineDelay(Duration newDelay)
        {
            Duration nextDelay = new Duration(TimeSpan.FromSeconds(0));

            if (sb != null)
            {
                for (int i = 0; i < sb.Children.Count; i++)
                {
                    // The first five animations are for translation
                    // The next five animations are for opacity
                    if (i == 5)
                        nextDelay = newDelay;
                    else
                        nextDelay += newDelay;


                    DoubleAnimationUsingKeyFrames timeline = sb.Children[i] as DoubleAnimationUsingKeyFrames;
                    if (timeline != null)
                    {
                        timeline.SetValue(DoubleAnimationUsingKeyFrames.BeginTimeProperty, nextDelay.TimeSpan);
                    }
                }
            }
        }

        #endregion

        #region IDisposable Implementation

        /// <summary>
        /// Releases all resources used by an instance of the FluidProgressBar class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual Dispose(bool) method, passing in 'true', and then suppresses 
        /// finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged resources before an instance of the FluidProgressBar class is reclaimed by garbage collection.
        /// </summary>
        /// <remarks>
        /// NOTE: Leave out the finalizer altogether if this class doesn't own unmanaged resources itself, 
        /// but leave the other methods exactly as they are.
        /// This method releases unmanaged resources by calling the virtual Dispose(bool), passing in 'false'.
        /// </remarks>
        ~FluidProgressBar()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the FluidProgressBar class and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">'true' to release both managed and unmanaged resources; 'false' to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources here
                this.SizeChanged -= OnSizeChanged;
                this.Loaded -= OnLoaded;
                this.IsVisibleChanged -= OnIsVisibleChanged;
            }

            // free native resources if there are any.			
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer
United States United States
An individual with more than a decade of experience in desktop computing and mobile app development primarily on the Microsoft platform. He loves programming in C#, WPF & XAML related technologies.
Current interests include web application development, developing rich user experiences across various platforms and exploring his creative side.

Ratish's personal blog: wpfspark.wordpress.com

Comments and Discussions