Click here to Skip to main content
15,895,011 members
Articles / Desktop Programming / WPF

XPlorerBar: A WPF Windows XP Style Explorer Bar Control

Rate me:
Please Sign up or sign in to vote.
4.95/5 (168 votes)
9 Dec 2008CPOL11 min read 345.1K   9.3K   408  
A fully customizable WPF implementation of the left side pane that was introduced in Windows XP's Explorer.
#region [       Copyright © 2008, Zona-Tools, all rights reserved.       ]
/*
 * 
    This source code is licensed under the Code Project Open License (CPOL).
    Check out http://www.codeproject.com/info/cpol10.aspx for further details.
 * 
*/
#endregion


#region [       Using namespaces       ]

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

#endregion


namespace ZonaTools.XPlorerBar
{
    /// <summary>
    /// Represents a Decorator element that can provide expandable/collapsible 
    /// effects to a set of UI elements.
    /// </summary>
    public class XPandableDecorator : Decorator
    {
        #region [       Fields       ]

        //Storyboard used to provide expandable/collapsible effects
        private Storyboard _stBoard = null;
        
        #endregion


        #region [       Constructor       ]

        //===========================================================================
        /// <summary>
        /// Static constructor used to override the dependency properties (if
        /// needed) and define the default style for the control.
        /// </summary>
        //===========================================================================
        static XPandableDecorator()
        { 
            //Sets the default value of the 'ClipToBounds' dependency property to 'true'
            ClipToBoundsProperty.OverrideMetadata(
                typeof(XPandableDecorator), new FrameworkPropertyMetadata(true));

            //Sets the default value of the 'Opacity' dependency property to '0.0'
            OpacityProperty.OverrideMetadata(
                typeof(XPandableDecorator), new FrameworkPropertyMetadata(0.0));

            //Sets the default value of the 'Focusable' dependency property to 'false'
            FocusableProperty.OverrideMetadata(
                typeof(XPandableDecorator), new FrameworkPropertyMetadata(false));
        }

        #endregion


        #region [       Dependency properties       ]

        #region IsExpanded property

        //===========================================================================
        /// <summary>
        /// Gets or sets a value indicating whether the decorator is expanded.
        /// This is a dependency property.
        /// </summary>
        /// <remarks>
        /// The default value is <c>false</c>.
        /// </remarks>
        //===========================================================================
        [Category(XPlorerBar.CATEGORYNAME), Browsable(true)]
        public bool IsExpanded
        {
            get { return (bool)GetValue(XPandableDecorator.IsExpandedProperty); }
            set { SetValue(XPandableDecorator.IsExpandedProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="IsExpanded"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsExpandedProperty =
            DependencyProperty.Register("IsExpanded",
            typeof(bool), typeof(XPandableDecorator),
            new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));

        /// <summary>
        /// Invoked whenever the <c>IsExpanded</c> dependency property value
        /// has been updated.
        /// </summary>
        /// <param name="sender">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by any event that tracks changes 
        /// to the effective value of this property. </param>
        private static void OnIsExpandedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            //Gets the XPandableDecorator instance who sent the event
            XPandableDecorator expDecorator = (XPandableDecorator)sender;

            //Gets the new value of the 'IsExpanded' DP
            bool IsExpandedNewValue = (bool)e.NewValue;

            //Creates a new storyboard or stops it if it already exists
            if (expDecorator._stBoard == null)
                expDecorator._stBoard = new Storyboard();
            else
                expDecorator._stBoard.Stop(expDecorator);

            //Creates a new animation for a double value
            DoubleAnimation animation = new DoubleAnimation();
            animation.To = (IsExpandedNewValue) ? 1.0 : 0.0;
            animation.Duration = 
                (expDecorator.IsLoaded) ?
                TimeSpan.FromMilliseconds(expDecorator.ExpandOrCollapseDuration) : 
                TimeSpan.FromMilliseconds(0.0);
            animation.AccelerationRatio = (IsExpandedNewValue) ? 0.0 : 0.33;
            animation.DecelerationRatio = (IsExpandedNewValue) ? 0.33 : 0.0;
            animation.FillBehavior = FillBehavior.HoldEnd;

            //Links it to the 'AnimationProgress' dependency property
            Storyboard.SetTargetProperty(animation, new PropertyPath(AnimationProgressProperty));

            //Clears previous animations and adds the new one to the storyboard
            expDecorator._stBoard.Children.Clear();
            expDecorator._stBoard.Children.Add(animation);

            //Starts the storyboard and sets it as controllable
            expDecorator._stBoard.Begin(expDecorator, true);
        }

        #endregion

        #region AnimationProgress property

        //===========================================================================
        /// <summary>
        /// Gets or sets a value indicating the progression of the animation (the 
        /// value goes from 0 to 1). This is a dependency property.
        /// </summary>
        /// <remarks>
        /// The default value is 0 (decorator collapsed).
        /// </remarks>
        //===========================================================================
        [Browsable(false)]
        public double AnimationProgress
        {
            get { return (double)GetValue(XPandableDecorator.AnimationProgressProperty); }
            set { SetValue(XPandableDecorator.AnimationProgressProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="AnimationProgress"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty AnimationProgressProperty =
            DependencyProperty.Register("AnimationProgress",
            typeof(double), typeof(XPandableDecorator),
            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure, 
                new PropertyChangedCallback(OnAnimationProgressChanged),
                new CoerceValueCallback(CoerceAnimationProgress)));

        /// <summary>
        /// Invoked whenever the <c>AnimationProgress</c> dependency property value
        /// has been updated.
        /// </summary>
        /// <param name="sender">The <c>DependencyObject</c> on which the property
        /// has changed value.</param>
        /// <param name="e">Event data that is issued by any event that tracks changes 
        /// to the effective value of this property. </param>
        private static void OnAnimationProgressChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            //Gets the XPandableDecorator instance who sent the event
            XPandableDecorator expDecorator = (XPandableDecorator)sender;

            //Updates the instance Visibility
            expDecorator.Visibility =
                (expDecorator.AnimationProgress > 0.0) ? Visibility.Visible : Visibility.Hidden;

            //Updates the instance Opacity
            expDecorator.Opacity = expDecorator.AnimationProgress;
        }

        /// <summary>
        /// Invoked whenever the <c>AnimationProgress</c> dependency property value is 
        /// being re-evaluated, or coercion is specifically requested.
        /// </summary>
        /// <param name="sender">Not used here.</param>
        /// <param name="value">The new value of the property, prior to any coercion 
        /// attempt.</param>
        /// <returns>The coerced value.</returns>
        private static object CoerceAnimationProgress(DependencyObject sender, object value)
        {
            //Gets the value to coerce
            double animationProgress = (double)value;

            //Keeps the value between 0 and 1
            if (animationProgress < 0.0)
                animationProgress = 0.0;
            else if (animationProgress > 1.0)
                animationProgress = 1.0;

            return animationProgress;
        }

        #endregion

        #region ExpandCollapseDuration property

        //===========================================================================
        /// <summary>
        /// Gets or sets the duration (in milliseconds) of the expanding 
        /// (or collapsing) animation. This is a dependency property.
        /// </summary>
        /// <remarks>
        /// The default value is 1000 milliseconds.
        /// </remarks>
        //===========================================================================
        [Category(XPlorerBar.CATEGORYNAME), Browsable(true)]
        public Double ExpandOrCollapseDuration
        {
            get { return (Double)GetValue(XPandableDecorator.ExpandOrCollapseDurationProperty); }
            set { SetValue(XPandableDecorator.ExpandOrCollapseDurationProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="ExpandOrCollapseDuration"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ExpandOrCollapseDurationProperty =
            DependencyProperty.Register("ExpandOrCollapseDuration",
            typeof(Double), typeof(XPandableDecorator),
            new FrameworkPropertyMetadata(1000.0));

        #endregion

        #endregion


        #region [       Layout process       ]

        //===========================================================================
        /// <summary>
        /// Measures the size in layout required for its child element and determines 
        /// the size for this element.
        /// </summary>
        /// <param name="availableSize">The available size that this element can give 
        /// to its child element. Infinity can be specified as a value to indicate 
        /// that the element will size to whatever content is available.</param>
        /// <returns>The size that this element determines it needs during layout, 
        /// based on its calculations of child element sizes.</returns>
        //===========================================================================
        protected override Size MeasureOverride(Size availableSize)
        {
            //Gets the single child of the Border
            UIElement singleChild = Child;

            //Checks if it exists
            if (singleChild != null)
            {
                //Asks the child how big it wants to be within the available area
                singleChild.Measure(availableSize);

                //Evaluates the height of the element according to child item desired 
                //height and the animation progress
                double height = singleChild.DesiredSize.Height * AnimationProgress;

                //Returns the original width associated with the calculated height
                return new Size(availableSize.Width, height);
            }
            return new Size();
        }


        //===========================================================================
        /// <summary>
        /// Positions child elements and determines a size for this element.
        /// </summary>
        /// <param name="arrangeSize">The final area within the parent this 
        /// element should use to arrange itself and its children.</param>
        /// <returns>The actual size used.</returns>
        //===========================================================================
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            //Gets the single child of the Border
            UIElement singleChild = Child;

            //Checks if it exists
            if (singleChild != null)
            {
                //Calculates the y-coordinate of the top left corner of the child
                double y = singleChild.DesiredSize.Height * (AnimationProgress - 1.0);

                //Tells the single child its location and size
                singleChild.Arrange(new Rect(new Point(0.0, y),
                    new Size(arrangeSize.Width, singleChild.DesiredSize.Height)));

                //Returns the original size
                return arrangeSize;
            }
            return new Size();
        }

        #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 Code Project Open License (CPOL)


Written By
Team Leader
France France
I have been developing and managing projects for real-time embedded softwares for eight years. Then, I moved from Paris to the south of France and began to lead a team who was developping Java applications.

My main occupation right now is to continue my journey in the WPF world.

You can check out my blog here. [^]

Comments and Discussions