Click here to Skip to main content
15,892,927 members
Articles / Programming Languages / C#

Themed Windows XP style Explorer Bar

Rate me:
Please Sign up or sign in to vote.
4.96/5 (471 votes)
6 Oct 200515 min read 1.9M   72.6K   1.1K  
A fully customizable Windows XP style Explorer Bar that supports Windows XP themes and animated expand/collapse with transparency.
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace XPTaskBar
{
	#region Delegates

	/// <summary>
	/// 
	/// </summary>
	public delegate void TaskPaneEventHandler(TaskPaneEventArgs e);
	
	#endregion
	
	

	#region TaskPane
	
	/// <summary>
	/// Summary description for TaskPane.
	/// </summary>
	[ToolboxItem(true)]
	[DesignerAttribute(typeof(TaskPaneDesigner))]
	public class TaskPane : Panel, ISupportInitialize
	{
		#region Event Handlers

		/// <summary>
		/// 
		/// </summary>
		public event TaskPaneEventHandler ExpandoAdded; 

		/// <summary>
		/// 
		/// </summary>
		public event TaskPaneEventHandler ExpandoRemoved; 

		/// <summary>
		/// 
		/// </summary>
		public event TaskPaneEventHandler ExpandoStateChanged; 

		#endregion
		
		
		#region Class Data
		
		// system defined settings for the TaskBar
		protected TaskBarInfo systemSettings;

		// should we animate expanding/collapsing
		protected bool animate;
		protected ArrayList animationHelpers;

		// internal list of all Expandos in the TaskPane
		protected ExpandoCollection expandoList;

		// are we initialising
		protected bool initialising = false;

		//
		protected bool classicTheme;
		protected bool customTheme;

		// Required designer variable
		private System.ComponentModel.Container components = null;

		#endregion


		#region Constructor

		/// <summary>
		/// 
		/// </summary>
		public TaskPane()
		{
			// This call is required by the Windows.Forms Form Designer.
			components = new System.ComponentModel.Container();

			// set control styles
			this.SetStyle(ControlStyles.DoubleBuffer, true);
			this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
			this.SetStyle(ControlStyles.UserPaint, true);
			this.SetStyle(ControlStyles.ResizeRedraw, true);

			// get the system theme settings
			this.systemSettings = Util.GetSystemTaskBarSettings();
			this.classicTheme = false;
			this.customTheme = false;

			// size
			int width = (this.systemSettings.TaskPane.Padding.Left + 
				this.systemSettings.TaskPane.Padding.Right + 
				this.systemSettings.Header.BackImageWidth);
			int height = width;
			this.Size = new Size(width, height);

			// background color
			this.BackColor = systemSettings.TaskPane.GradientStartColor;

			// setup sutoscrolling
			this.AutoScroll = false;
			this.AutoScrollMargin = new Size(this.systemSettings.TaskPane.Padding.Right, 
				this.systemSettings.TaskPane.Padding.Bottom);

			// don't use animation
			this.animate = false;
			this.animationHelpers = new ArrayList();

			//
			this.expandoList = new ExpandoCollection();
		}

		#endregion


		#region Methods

		#region Animation

		/// <summary>
		/// 
		/// </summary>
		internal void StartAnimation(Expando expando)
		{
			AnimationHelper animationHelper = new AnimationHelper(this, expando);
			this.animationHelpers.Add(animationHelper);

			animationHelper.StartAnimation();
		}


		/// <summary>
		/// 
		/// </summary>
		internal void AnimationStopped(AnimationHelper animationHelper)
		{
			this.animationHelpers.Remove(animationHelper);

			animationHelper = null;

			this.Invalidate();
		}

		#endregion

		#region Appearance

		/// <summary>
		/// Forces the TaskPane and all it's Expandos to use a theme
		/// equivalent to Windows XPs classic theme 
		/// </summary>
		public void UseClassicTheme()
		{
			this.classicTheme = true;
			this.customTheme = false;
			
			this.systemSettings.UseClassicTheme();

			foreach (Expando expando in this.expandoList)
			{
				expando.SystemSettings = this.systemSettings;
			}

			this.DoLayout();

			this.Invalidate(true);
		}


		/// <summary>
		/// Forces the TaskPane and all it's Expandos to use the 
		/// specified theme
		/// </summary>
		/// <param name="stylePath">The path to the custom 
		/// shellstyle.dll to use</param>
		public void UseCustomTheme(string stylePath)
		{
			this.customTheme = true;
			this.classicTheme = false;

			this.systemSettings.Dispose();
			this.systemSettings = null;

			this.systemSettings = Util.GetSystemTaskBarSettings(stylePath);

			foreach (Expando expando in this.expandoList)
			{
				expando.SystemSettings = this.systemSettings;
			}

			this.DoLayout();

			this.Invalidate(true);
		}


		/// <summary>
		/// Forces the TaskPane and all it's Expandos to use the 
		/// current system theme
		/// </summary>
		public void UseDefaultTheme()
		{
			this.customTheme = false;
			this.classicTheme = false;

			this.systemSettings.Dispose();
			this.systemSettings = null;

			this.systemSettings = Util.GetSystemTaskBarSettings();

			foreach (Expando expando in this.expandoList)
			{
				expando.SystemSettings = this.systemSettings;
			}

			this.DoLayout();

			this.Invalidate(true);
		}

		#endregion

		#region Dispose

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (components != null)
				{
					components.Dispose();
				}

				if (this.systemSettings != null)
				{
					this.systemSettings.Dispose();
				}
			}

			base.Dispose(disposing);
		}

		#endregion

		#region Expandos

		/// <summary>
		/// Collaspes all the Expandos contained in the TaskPane
		/// </summary>
		public void CollapseAll()
		{
			foreach (Expando expando in this.expandoList)
			{
				expando.Collapsed = true;
			}
		}


		/// <summary>
		/// Expands all the Expandos contained in the TaskPane
		/// </summary>
		public void ExpandAll()
		{
			foreach (Expando expando in this.expandoList)
			{
				expando.Collapsed = false;
			}
		}


		/// <summary>
		/// Collaspes all the Expandos contained in the TaskPane, 
		/// except for the specified Expando which is expanded
		/// </summary>
		public void CollapseAllButOne(Expando expando)
		{
			foreach (Expando e in this.expandoList)
			{
				if (e != expando)
				{
					e.Collapsed = true;
				}
				else
				{
					expando.Collapsed = false;
				}
			}
		}

		#endregion

		#region ISupportInitialize Members

		/// <summary>
		/// 
		/// </summary>
		public void BeginInit()
		{
			this.initialising = true;
		}


		/// <summary>
		/// 
		/// </summary>
		public void EndInit()
		{
			this.initialising = false;

			this.DoLayout();
		}

		#endregion

		#region Layout

		/// <summary> 
		/// 
		/// </summary>
		public void DoLayout()
		{
			// stop the layout engine
			this.SuspendLayout();
			
			Expando e;
			Point p;
			
			// work out how wide to make the controls, and where
			// the top of the first control should be
			int y = this.DisplayRectangle.Y + this.Padding.Top;
			int width = this.ClientSize.Width - this.Padding.Left - this.Padding.Right;

			// for each control in our list...
			for (int i=0; i<this.expandoList.Count; i++)
			{
				e = this.expandoList[i];

				p = new Point(this.Padding.Left, y);

				// set the width and location of the control
				e.Location = p;
				e.Width = width;

				// update the next starting point
				y += e.Height + this.Padding.Bottom;
			}

			// restart the layout engine
			this.ResumeLayout(true);
		}

		#endregion

		#endregion


		#region Properties

		#region Animation

		/// <summary>
		/// Specifies whether the TaskPane should animate Expando's
		/// when they collapse or expand.
		/// </summary>
		[Bindable(true),
		Category("Appearance"), 
		DefaultValue(false),
		Description("Specifies whether the TaskPane should animate Expando's when they collapse or expand.")]
		public bool Animate
		{
			get
			{
				return this.animate;
			}

			set
			{
				this.animate = value;
			}
		}


		/// <summary>
		/// Determines whether the TaskPane is currently animating
		/// an Expando
		/// </summary>
		[Browsable(false)]
		public bool IsAnimating
		{
			get
			{
				return this.animationHelpers.Count > 0;
			}
		}

		#endregion

		#region Border

		/// <summary>
		/// 
		/// </summary>
		protected Border Border
		{
			get
			{
				return this.systemSettings.TaskPane.Border;
			}
		}


		/// <summary>
		/// 
		/// </summary>
		protected Color BorderColor
		{
			get
			{
				return this.systemSettings.TaskPane.BorderColor;
			}
		}

		#endregion

		#region Colors

		/// <summary>
		/// The first color of the TaskPane's background gradient fill.
		/// </summary>
		[Browsable(false)]
		public Color GradientStartColor
		{
			get
			{
				return this.systemSettings.TaskPane.GradientStartColor;
			}
		}


		/// <summary>
		/// The second color of the TaskPane's background gradient fill.
		/// </summary>
		[Browsable(false)]
		public Color GradientEndColor
		{
			get
			{
				return this.systemSettings.TaskPane.GradientEndColor;
			}
		}


		/// <summary>
		/// The direction of the TaskPane's background gradient fill.
		/// </summary>
		[Browsable(false)]
		public LinearGradientMode GradientDirection
		{
			get
			{
				return this.systemSettings.TaskPane.GradientDirection;
			}
		}

		#endregion

		#region Padding

		/// <summary>
		/// The amount of space between the border and the 
		/// Expando's along each side of the TaskPane.
		/// </summary>
		protected Padding Padding
		{
			get
			{
				return this.systemSettings.TaskPane.Padding;
			}
		}

		#endregion

		#endregion


		#region Events

		#region Controls

		/// <summary> 
		/// Raises the ControlAdded event
		/// </summary>
		protected override void OnControlAdded(ControlEventArgs e)
		{
			base.OnControlAdded(e);

			// make sure the control is an Expando
			if ((e.Control as Expando) == null)
			{
				// remove the control
				this.Controls.Remove(e.Control);

				// throw a hissy fit
				throw new InvalidCastException("Only Expando's can be added to the TaskPane");
			}

			//
			OnExpandoAdded(new TaskPaneEventArgs(this, (Expando) e.Control));
		}


		/// <summary>
		/// Raises the ControlRemoved event
		/// </summary>
		protected override void OnControlRemoved(ControlEventArgs e)
		{
			base.OnControlRemoved(e);
				
			if (e.Control is Expando)
			{
				OnExpandoRemoved(new TaskPaneEventArgs(this, (Expando) e.Control));
			}
		}

		#endregion

		#region Expandos

		/// <summary> 
		/// Event handler for the Expando StateChanged event
		/// </summary>
		private void expando_StateChanged(ExpandoEventArgs e)
		{
			OnExpandoStateChanged(new TaskPaneEventArgs(this, e.Expando));
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnExpandoStateChanged(TaskPaneEventArgs e)
		{
			// if we can perform animations...
			if (this.animate && !this.DesignMode)
			{
				this.StartAnimation(e.Expando);
			}
			else
			{
				// if we get here, the Expando has collapsed/expanded itself
				// so we need to update the layout of the controls
				this.DoLayout();

				// sometimes the background colors doesn't update correctly,
				// so get the TaskPane to repaint itself (without painting
				// the Expandos)
				this.Invalidate(false);
			}
			
			//
			if (ExpandoStateChanged != null)
			{
				ExpandoStateChanged(e);
			}
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnExpandoAdded(TaskPaneEventArgs e)
		{
			if (!this.expandoList.Contains(e.Expando))
			{
				// are we initialising
				if (this.initialising)
				{
					// add new controls to the beginning of the list
					this.expandoList.Insert(0, e.Expando);
				}
				else
				{
					// otherwise add to the end of the list
					this.expandoList.Add(e.Expando);
				}

				// set anchor styles
				e.Expando.Anchor = (AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right);
			}			
			
			// tell the Expando who's its daddy...
			e.Expando.TaskPane = this;
			e.Expando.SystemSettings = this.systemSettings;

			// listen for collapse/expand events
			e.Expando.StateChanged += new ExpandoEventHandler(this.expando_StateChanged);

			// update the layout of the controls
			this.DoLayout();

			//
			if (ExpandoAdded != null)
			{
				ExpandoAdded(e);
			}
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnExpandoRemoved(TaskPaneEventArgs e)
		{
			// remove the control from the ExpandoCollection
			this.expandoList.Remove(e.Expando);

			// remove the StateChanged listener
			e.Expando.StateChanged -= new ExpandoEventHandler(this.expando_StateChanged);

			// update the layout of the controls
			this.DoLayout();

			//
			if (ExpandoRemoved != null)
			{
				ExpandoRemoved(e);
			}
		}

		#endregion

		#region Paint

		/// <summary> 
		/// Raises the Paint event
		/// </summary>
		protected override void OnPaint(PaintEventArgs e)
		{
			// paint background

			using (LinearGradientBrush brush = new LinearGradientBrush(this.DisplayRectangle, 
					   this.GradientStartColor, 
					   this.GradientEndColor, 
					   this.GradientDirection))
			{
				e.Graphics.FillRectangle(brush, this.DisplayRectangle);
			}
		}

		#endregion

		#region System Colors

		/// <summary> 
		/// Raises the SystemColorsChanged event
		/// </summary>
		protected override void OnSystemColorsChanged(EventArgs e)
		{
			base.OnSystemColorsChanged(e);

			// don't go any further if we are explicitly using
			// the classic or a custom theme
			if (this.classicTheme || this.customTheme)
			{
				return;
			}

			this.SuspendLayout();

			// get rid of the current system theme info
			this.systemSettings.Dispose();
			this.systemSettings = null;

			// get a new system theme info for the new theme
			this.systemSettings = Util.GetSystemTaskBarSettings();

			// update the system settings for each expando
			foreach (Control control in this.Controls)
			{
				if (control is Expando)
				{
					Expando expando = (Expando) control;
					
					expando.SystemSettings = this.systemSettings;
				}
			}

			// update the layout of the controls
			this.DoLayout();
		}

		#endregion

		#endregion
	}

	#endregion



	#region TaskPaneDesigner

	/// <summary>
	/// Summary description for ExplorerBarDesigner.
	/// </summary>
	public class TaskPaneDesigner : ScrollableControlDesigner
	{
		/// <summary>
		/// 
		/// </summary>
		public TaskPaneDesigner()
		{
			
		}


		/// <summary>
		/// 
		/// </summary>
		protected override void PreFilterProperties(System.Collections.IDictionary properties)
		{
			base.PreFilterProperties(properties);

			properties.Remove("BackColor");
			properties.Remove("BackgroundImage");
		}
	}

	#endregion



	#region AnimationHelper

	/// <summary>
	/// 
	/// </summary>
	public class AnimationHelper
	{
		#region Class Data

		//
		protected TaskPane taskpane;
		protected Expando expando;

		//
		private int animationStepNum;
		protected int numAnimationSteps = 20;
		protected int animationFrameInterval = 10;
		protected bool animating;
		protected Timer animationTimer;

		#endregion


		#region Constructor

		/// <summary>
		/// 
		/// </summary>
		public AnimationHelper(TaskPane taskpane, Expando expando)
		{
			this.taskpane = taskpane;
			this.expando = expando;

			this.animating = false;

			// I know that this isn't the best way to do this, but I 
			// haven't quite worked out how to do it with threads so 
			// this will have to do for the moment
			this.animationTimer = new Timer();
			this.animationTimer.Tick += new EventHandler(this.animationTimer_Tick);
			this.animationTimer.Interval = this.animationFrameInterval;
		}

		#endregion


		#region Methods

		/// <summary>
		/// Starts the Expando collapse/expand animation
		/// </summary>
		public void StartAnimation()
		{
			// don't bother going any further if we are already animating
			if (this.Animating)
			{
				return;
			}
			
			this.animationStepNum = 0;

			// tell the expando to get redy to animate
			this.expando.StartAnimation();

			// start the animation timer
			this.animationTimer.Start();
		}


		/// <summary>
		/// Updates the animation for the Expando
		/// </summary>
		protected void PerformAnimation()
		{
			// if we have more animation steps to perform
			if (this.animationStepNum < this.numAnimationSteps)
			{
				// update the animation step number
				this.animationStepNum++;

				// tell the animating expando to update the animation
				this.expando.UpdateAnimation(this.animationStepNum, this.numAnimationSteps);

				// rearrange the groups
				this.taskpane.DoLayout();
			}
			else
			{
				// stop the animation
				this.animationTimer.Stop();
				this.expando.StopAnimation();

				// let the TaskPane know that the animation has finished
				// so it can do some cleaning up (like getting rid of us)
				this.taskpane.AnimationStopped(this);
			}
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets the TaskPane that the AnimationHelper belongs to
		/// </summary>
		public TaskPane TaskPane
		{
			get
			{
				return this.taskpane;
			}
		}


		/// <summary>
		/// Gets the Expando that is te be animated
		/// </summary>
		public Expando Expando
		{
			get
			{
				return this.expando;
			}
		}


		/// <summary>
		/// Gets or sets the number of steps that are needed for the Expando 
		/// to get from fully expanded to fully collapsed, or visa versa
		/// </summary>
		public int NumAnimationSteps
		{
			get
			{
				return this.numAnimationSteps;
			}

			set
			{
				if (value < 0)
				{
					throw new ArgumentOutOfRangeException("value", "NumAnimationSteps must be at least 0");
				}
				
				// only change this if we are not currently animating
				// (if we are animating, changing this could cause things
				// to screw up big time)
				if (!this.animating)
				{
					this.numAnimationSteps = value;
				}
			}
		}


		/// <summary>
		/// Gets or sets the number of milliseconds that each "frame" 
		/// of the animation stays on the screen
		/// </summary>
		public int AnimationFrameInterval
		{
			get
			{
				return this.animationFrameInterval;
			}

			set
			{
				this.animationFrameInterval = value;
			}
		}


		/// <summary>
		/// Gets whether the Expando is currently animating
		/// </summary>
		public bool Animating
		{
			get
			{
				return this.animating;
			}
		}

		#endregion


		#region Events

		/// <summary>
		/// Event handler for the animation timer tick event
		/// </summary>
		private void animationTimer_Tick(object sender, EventArgs e)
		{
			// do the next bit of the aniation
			PerformAnimation();
		}

		#endregion
	}

	#endregion



	#region TaskPaneEventArgs

	/// <summary>
	/// Summary description for TaskPaneEventArgs.
	/// </summary>
	public class TaskPaneEventArgs : EventArgs
	{
		#region Class Data

		/// <summary>
		/// 
		/// </summary>
		protected TaskPane taskpane;
		
		/// <summary>
		/// 
		/// </summary>
		private Expando expando;

		#endregion


		#region Constructor

		/// <summary>
		/// 
		/// </summary>
		public TaskPaneEventArgs() : this(null, null)
		{
			
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="taskPane"></param>
		public TaskPaneEventArgs(TaskPane taskpane) : this(taskpane, null)
		{
			
		}

		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="taskPane"></param>
		/// <param name="expando"></param>
		public TaskPaneEventArgs(TaskPane taskpane, Expando expando) : base()
		{
			this.taskpane = taskpane;
			this.expando = expando;
		}

		#endregion


		#region Properties

		/// <summary>
		/// 
		/// </summary>
		public TaskPane TaskPane
		{
			get
			{
				return this.taskpane;
			}
		}


		/// <summary>
		/// 
		/// </summary>
		public Expando Expando
		{
			get
			{
				return this.expando;
			}
		}

		#endregion
	}

	#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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions