Click here to Skip to main content
15,881,248 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.
/*
 * Copyright (c) 2004, Mathew Hall
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *    - Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 * 
 *    - Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */


using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;
using System.Windows.Forms.Design;


namespace XPExplorerBar
{
	#region TaskPane
	
	/// <summary>
	///  A ScrollableControl that can contain Expandos
	/// </summary>
	[ToolboxItem(true), 
	DesignerAttribute(typeof(TaskPaneDesigner))]
	public class TaskPane : ScrollableControl, ISupportInitialize
	{
		#region Event Handlers

		/// <summary>
		/// Occurs when an Expando is added to the TaskPane
		/// </summary>
		public event ExpandoEventHandler ExpandoAdded; 

		/// <summary>
		/// Occurs when an Expando is removed from the TaskPane
		/// </summary>
		public event ExpandoEventHandler ExpandoRemoved; 

		#endregion
			
		
		#region Class Data
		
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		/// <summary>
		/// Internal list of Expandos contained in the TaskPane
		/// </summary>
		private TaskPane.ExpandoCollection expandoCollection;
		
		/// <summary>
		/// System defined settings for the TaskBar
		/// </summary>
		private ExplorerBarInfo systemSettings;

		/// <summary>
		/// Specifies whether the TaskPane is able to perform animations
		/// </summary>
		private bool animate;

		/// <summary>
		/// Internal list of AnimationHelpers that aid in the animation 
		/// of Expandos
		/// </summary>
		private ArrayList animationHelpers;

		/// <summary>
		/// Specifies whether the TaskPane is currently initialising
		/// </summary>
		private bool initialising;

		/// <summary>
		/// Specifies whether the TaskPane and its children should render 
		/// themselves using a theme similar to the Windows XP Classic theme
		/// </summary>
		private bool classicTheme;
		
		/// <summary>
		/// Specifies whether the TaskPane and its children should render 
		/// themselves using a non-official Windows XP theme
		/// </summary>
		private bool customTheme;

		/// <summary>
		/// A Rectangle that specifies the size and location of the watermark
		/// </summary>
		private Rectangle watermarkRect;

		/// <summary>
		/// Specifies whether the scrollbars have changed
		/// </summary>
		internal bool scrollChanged = false;

		/// <summary>
		/// Specifies whether the TaskPane is currently performing a 
		/// layout operation
		/// </summary>
		internal bool layout = false;

		#endregion


		#region Constructor

		/// <summary>
		/// Initializes a new instance of the TaskPane class with default settings
		/// </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);
			this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);

			this.expandoCollection = new TaskPane.ExpandoCollection(this);

			// get the system theme settings
			this.systemSettings = ThemeManager.GetSystemExplorerBarSettings();
			this.BackColor = this.systemSettings.TaskPane.GradientStartColor;
			this.BackgroundImage = this.BackImage;
			
			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);

			// 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.initialising = false;
		}

		#endregion


		#region Methods

		#region Animation

		/// <summary>
		/// Starts a fade animation for the specified Expando
		/// </summary>
		/// <param name="expando">The Expando that should perform the 
		/// fade animation</param>
		public void StartFadeAnimation(Expando expando)
		{
			AnimationHelper animationHelper = new AnimationHelper(this, expando, AnimationHelper.FadeAnimation);
			this.animationHelpers.Add(animationHelper);

			animationHelper.StartAnimation();
		}


		/// <summary>
		/// Starts a slide animation for the specified Expando
		/// </summary>
		/// <param name="expando">The Expando that should perform the 
		/// slide animation</param>
		public void StartSlideAnimation(Expando expando)
		{
			AnimationHelper animationHelper = new AnimationHelper(this, expando, AnimationHelper.SlideAnimation);
			this.animationHelpers.Add(animationHelper);

			animationHelper.StartAnimation();
		}


		/// <summary>
		/// Notifies the TaskPane that the specified AnimationHelper 
		/// has completed its animation
		/// </summary>
		/// <param name="animationHelper">The AnimationHelper that 
		/// has completed its animation</param>
		public void AnimationStopped(AnimationHelper animationHelper)
		{
			this.animationHelpers.Remove(animationHelper);

			animationHelper = null;

			this.Invalidate(true);
		}

		#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;
			
			ExplorerBarInfo settings = ThemeManager.GetSystemExplorerBarSettings();
			settings.UseClassicTheme();

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

			this.SystemSettings = settings;
		}


		/// <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;
			
			ExplorerBarInfo settings = ThemeManager.GetSystemExplorerBarSettings(stylePath);

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

			this.SystemSettings = settings;
		}


		/// <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;
			
			ExplorerBarInfo settings = ThemeManager.GetSystemExplorerBarSettings();

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

			this.SystemSettings = settings;
		}

		#endregion

		#region Dispose

		/// <summary> 
		/// Releases the unmanaged resources used by the TaskPane 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 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>
		// suggested by: PaleyX (jmpalethorpe@tiscali.co.uk)
		//               03/06/2004
		//               v1.1
		public void CollapseAll()
		{
			foreach (Expando expando in this.Expandos)
			{
				expando.Collapsed = true;
			}
		}


		/// <summary>
		/// Expands all the Expandos contained in the TaskPane
		/// </summary>
		// suggested by: PaleyX (jmpalethorpe@tiscali.co.uk)
		//               03/06/2004
		//               v1.1
		public void ExpandAll()
		{
			foreach (Expando expando in this.Expandos)
			{
				expando.Collapsed = false;
			}
		}


		/// <summary>
		/// Collaspes all the Expandos contained in the TaskPane, 
		/// except for the specified Expando which is expanded
		/// </summary>
		/// <param name="expando">The Expando that is to be expanded</param>
		// suggested by: PaleyX (jmpalethorpe@tiscali.co.uk)
		//               03/06/2004
		//               v1.1
		public void CollapseAllButOne(Expando expando)
		{
			foreach (Expando e in this.Expandos)
			{
				if (e != expando)
				{
					e.Collapsed = true;
				}
				else
				{
					expando.Collapsed = false;
				}
			}
		}


		/// <summary>
		/// Invalidates the non-client areas of all the Expandos 
		/// contained in the TaskPane
		/// </summary>
		internal void InvalidateAllExpandoFrames()
		{
			foreach (Expando expando in this.Expandos)
			{
				expando.InvalidateFrame();
			}
		}

		#endregion

		#region ISupportInitialize Members

		/// <summary>
		/// Signals the TaskPane that initialization is starting
		/// </summary>
		public void BeginInit()
		{
			this.initialising = true;
		}


		/// <summary>
		/// Signals the TaskPane that initialization is complete
		/// </summary>
		public void EndInit()
		{
			this.initialising = false;

			this.DoLayout();

			foreach (Expando expando in this.Expandos)
			{
				expando.RecalcFrame(true);
			}
		}


		/// <summary>
		/// Gets whether the TaskPane is currently initialising
		/// </summary>
		[Browsable(false)]
		public bool Initialising
		{
			get
			{
				return this.initialising;
			}
		}

		#endregion

		#region Layout

		/// <summary>
		/// Forces the TaskPane to apply layout logic to child Expandos, 
		/// and adjusts the Size and Location of the Expandos if necessary
		/// </summary>
		public void DoLayout()
		{
			if (this.layout)
			{
				return;
			}

			this.layout = true;
			
			// 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.Expandos.Count; i++)
			{
				e = this.Expandos[i];

				// go to the next expando if this one is invisible
				if (!e.Visible)
				{
					continue;
				}

				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
				if (this.scrollChanged && e.AutoLayout)
				{
					e.DoLayout();
				}
				
				y += e.Height + this.Padding.Bottom;
			}

			// restart the layout engine
			this.ResumeLayout(false);

			this.layout = false;
		}


		/// <summary>
		/// Updates the layout of the Expandos while in design mode, and 
		/// adds/removes Expandos from the ControlCollection as necessary
		/// </summary>
		internal void UpdateExpandos()
		{
			if (this.Expandos.Count == this.Controls.Count)
			{
				// make sure the the expandos index in the ControlCollection 
				// are the same as in the ExpandoCollection (indexes in the 
				// ExpandoCollection may have changed due to the user moving 
				// them around in the editor)
				this.MatchControlCollToExpandoColl();				
				
				return;
			}

			// were any expandos added
			if (this.Expandos.Count > this.Controls.Count)
			{
				// add any extra expandos in the ExpandoCollection to the 
				// ControlCollection
				for (int i=0; i<this.Expandos.Count; i++)
				{
					if (!this.Controls.Contains(this.Expandos[i]))
					{
						this.OnExpandoAdded(new ExpandoEventArgs(this.Expandos[i]));
					}
				}
			}
			else
			{
				// expandos were removed
				int i = 0;
				Expando expando;

				// remove any extra expandos from the ControlCollection
				while (i < this.Controls.Count)
				{
					expando = (Expando) this.Controls[i];
					
					if (!this.Expandos.Contains(expando))
					{
						this.OnExpandoRemoved(new ExpandoEventArgs(expando));
					}
					else
					{
						i++;
					}
				}
			}
		}


		/// <summary>
		/// Make sure the the expandos index in the ControlCollection 
		/// are the same as in the ExpandoCollection (indexes in the 
		/// ExpandoCollection may have changed due to the user moving 
		/// them around in the editor or calling ExpandoCollection.Move())
		/// </summary>
		internal void MatchControlCollToExpandoColl()
		{
			this.SuspendLayout();
				
			for (int i=0; i<this.Expandos.Count; i++)
			{
				this.Controls.SetChildIndex(this.Expandos[i], i);
			}

			this.ResumeLayout(false);
				
			this.DoLayout();

			this.Invalidate(true);
		}

		#endregion

		#endregion


		#region Properties

		#region Animation

		/// <summary>
		/// Gets or sets 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 Colors

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


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


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

		#endregion

		#region Expandos

		/// <summary>
		/// A TaskPane.ExpandoCollection representing the collection of 
		/// Expandos contained within the TaskPane
		/// </summary>
		[Category("Behavior"),
		DefaultValue(null), 
		Description("The Expandos contained in the TaskPane"), 
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content), 
		Editor(typeof(ExpandoCollectionEditor), typeof(UITypeEditor))]
		public TaskPane.ExpandoCollection Expandos
		{
			get
			{
				return this.expandoCollection;
			}
		}


		/// <summary>
		/// A Control.ControlCollection representing the collection of 
		/// controls contained within the control
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new Control.ControlCollection Controls
		{
			get
			{
				return base.Controls;
			}
		}

		#endregion

		#region Images

		/// <summary>
		/// Gets the Image used as the TaskPane's background
		/// </summary>
		protected Image BackImage
		{
			get
			{
				return this.systemSettings.TaskPane.BackImage;
			}
		}


		/// <summary>
		/// Gets how the TaskPane's background Image is to be drawn
		/// </summary>
		protected ImageStretchMode StretchMode
		{
			get
			{
				return this.systemSettings.TaskPane.StretchMode;
			}
		}


		/// <summary>
		/// Gets the Image that is used as a watermark in the TaskPane's 
		/// client area
		/// </summary>
		protected Image Watermark
		{
			get
			{
				return this.systemSettings.TaskPane.Watermark;
			}
		}


		/// <summary>
		/// Gets the alignment of the TaskPane's watermark
		/// </summary>
		protected ContentAlignment WatermarkAlignment
		{
			get
			{
				return this.systemSettings.TaskPane.WatermarkAlignment;
			}
		}

		#endregion

		#region Padding

		/// <summary>
		/// Gets 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

		#region SystemSettings

		/// <summary>
		/// Gets or sets the system defined settings for the TaskPane
		/// </summary>
		internal ExplorerBarInfo SystemSettings
		{
			get
			{
				return this.systemSettings;
			}

			set
			{
				// ignore null values
				if (value == null)
				{
					return;
				}
				
				if (this.systemSettings != value)
				{
					this.SuspendLayout();
					
					if (this.systemSettings != null)
					{
						this.systemSettings.Dispose();
						this.systemSettings = null;
					}

					this.watermarkRect = Rectangle.Empty;

					this.systemSettings = value;
					this.BackColor = this.systemSettings.TaskPane.GradientStartColor;
					this.BackgroundImage = this.BackImage;

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

					this.DoLayout();

					this.ResumeLayout(true);

					this.Invalidate(true);
				}
			}
		}

		#endregion

		#endregion


		#region Events

		#region Controls

		/// <summary>
		/// Raises the ControlAdded event
		/// </summary>
		/// <param name="e">A ControlEventArgs that contains the event data</param>
		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");
			}

			// add the expando to the ExpandoCollection if necessary
			if (!this.Expandos.Contains((Expando) e.Control))
			{
				this.Expandos.Add((Expando) e.Control);
			}
		}


		/// <summary>
		/// Raises the ControlRemoved event
		/// </summary>
		/// <param name="e">A ControlEventArgs that contains the event data</param>
		protected override void OnControlRemoved(ControlEventArgs e)
		{
			base.OnControlRemoved (e);

			// remove the control from the itemList
			if (this.Expandos.Contains(e.Control))
			{
				this.Expandos.Remove((Expando) e.Control);
			}

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

		#endregion

		#region Expandos

		/// <summary> 
		/// Event handler for the Expando StateChanged event
		/// </summary>
		/// <param name="sender">The object that fired the event</param>
		/// <param name="e">An ExpandoEventArgs that contains the event data</param>
		private void expando_StateChanged(object sender, ExpandoEventArgs e)
		{
			OnExpandoStateChanged(e);
		}


		/// <summary>
		/// Occurs when the value of an Expandos Collapsed property changes
		/// </summary>
		/// <param name="e">An ExpandoEventArgs that contains the event data</param>
		protected virtual void OnExpandoStateChanged(ExpandoEventArgs e)
		{
			// check if we need to animate
			
			// if we are in design mode, don't animate as the gripper thingys
			// that are shown around the outside of the expando are incorrect
			// once the animation has finished.  instead, go straight to
			// being collapsed/expanded
			if (this.DesignMode)
			{
				if (e.Expando.Collapsed)
				{
					e.Expando.Collapse();
				}
				else
				{
					e.Expando.Expand();
				}

				// re-align the expandos
				this.DoLayout();
			}
			// start the animation if we are able to animate and we aren't 
			// initialising the taskpane
			else if (this.Animate && !this.initialising)
			{
				this.StartFadeAnimation(e.Expando);
			}
			else
			{
				// can't animate, so collapse/expand the expando
				if (e.Collapsed)
				{
					e.Expando.Collapse();
				}
				else
				{
					e.Expando.Expand();
				}
				
				this.DoLayout();

				this.Invalidate(true);
			}
		}


		/// <summary>
		/// Raises the ExpandoAdded event
		/// </summary>
		/// <param name="e">An ExpandoEventArgs that contains the event data</param>
		protected virtual void OnExpandoAdded(ExpandoEventArgs e)
		{
			// add the expando to the ControlCollection if it hasn't already
			if (!this.Controls.Contains(e.Expando))
			{
				this.Controls.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(this, e);
			}
		}


		/// <summary>
		/// Raises the ExpandoRemoved event
		/// </summary>
		/// <param name="e">An ExpandoEventArgs that contains the event data</param>
		protected virtual void OnExpandoRemoved(ExpandoEventArgs e)
		{
			// remove the control from the ControlCollection if it hasn't already
			if (this.Controls.Contains(e.Expando))
			{
				this.Controls.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(this, e);
			}
		}

		#endregion

		#region Paint

		/// <summary> 
		/// Raises the PaintBackground event
		/// </summary>
		/// <param name="e">A PaintEventArgs that contains the event data</param>
		protected override void OnPaintBackground(PaintEventArgs e)
		{
			// paint background
			if (this.BackImage != null)
			{
				//base.OnPaintBackground(e);

				WrapMode wrap = WrapMode.Clamp;
				
				if ((this.StretchMode == ImageStretchMode.Tile) || (this.StretchMode == ImageStretchMode.Horizontal))
				{
					wrap = WrapMode.Tile;
				}

				using (TextureBrush brush = new TextureBrush(this.BackImage, wrap))
				{
					e.Graphics.FillRectangle(brush, this.DisplayRectangle);
				}
			}
			else
			{
				if (this.GradientStartColor != this.GradientEndColor)
				{
					using (LinearGradientBrush brush = new LinearGradientBrush(this.DisplayRectangle, 
							   this.GradientStartColor, 
							   this.GradientEndColor, 
							   this.GradientDirection))
					{
						e.Graphics.FillRectangle(brush, this.DisplayRectangle);
					}
				}
				else
				{
					using (SolidBrush brush = new SolidBrush(this.GradientStartColor))
					{
						e.Graphics.FillRectangle(brush, this.ClientRectangle);
					}
				}
			}

			// draw the watermark if we have one
			if (this.Watermark != null)
			{
				Rectangle rect = new Rectangle(0, 0, this.Watermark.Width, this.Watermark.Height);

				// work out a rough location of where the watermark should go

				switch (this.WatermarkAlignment)
				{
					case ContentAlignment.BottomCenter:
					case ContentAlignment.BottomLeft:
					case ContentAlignment.BottomRight:
					{
						rect.Y = this.DisplayRectangle.Bottom - this.Watermark.Height;
						
						break;
					}

					case ContentAlignment.MiddleCenter:
					case ContentAlignment.MiddleLeft:
					case ContentAlignment.MiddleRight:
					{
						rect.Y = this.DisplayRectangle.Top + ((this.DisplayRectangle.Height - this.Watermark.Height) / 2);
						
						break;
					}
				}

				switch (this.WatermarkAlignment)
				{
					case ContentAlignment.BottomRight:
					case ContentAlignment.MiddleRight:
					case ContentAlignment.TopRight:
					{
						rect.X = this.ClientRectangle.Right - this.Watermark.Width;
						
						break;
					}

					case ContentAlignment.BottomCenter:
					case ContentAlignment.MiddleCenter:
					case ContentAlignment.TopCenter:
					{
						rect.X = this.ClientRectangle.Left + ((this.ClientRectangle.Width - this.Watermark.Width) / 2);
						
						break;
					}
				}

				// shrink the destination rect if necesary so that we
				// can see all of the image
				
				if (rect.X < 0)
				{
					rect.X = 0;
				}

				if (rect.Width > this.ClientRectangle.Width)
				{
					rect.Width = this.ClientRectangle.Width;
				}

				if (rect.Y < this.DisplayRectangle.Top)
				{
					rect.Y = this.DisplayRectangle.Top;
				}

				if (rect.Height > this.DisplayRectangle.Height)
				{
					rect.Height = this.DisplayRectangle.Height;
				}

				// draw the watermark
				e.Graphics.DrawImage(this.Watermark, rect);
			}
		}

		#endregion

		#region Size

		/// <summary>
		/// Raises the Layout event
		/// </summary>
		/// <param name="e">A LayoutEventArgs that contains the event data</param>
		protected override void OnLayout(LayoutEventArgs levent)
		{
			bool hScroll = this.HScroll;
			bool vScroll = this.VScroll;

			this.SuspendLayout();
			
			base.OnLayout(levent);

			if (hScroll != this.HScroll || vScroll != this.VScroll)
			{
				this.scrollChanged = true;

				this.DoLayout();

				this.scrollChanged = false;

				this.Invalidate(this.ClientRectangle, true);
			}
			
			this.ResumeLayout(true);
		}

		#endregion

		#region System Colors

		/// <summary> 
		/// Raises the SystemColorsChanged event
		/// </summary>
		/// <param name="e">An EventArgs that contains the event data</param>
		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 = ThemeManager.GetSystemExplorerBarSettings();
			
			this.BackgroundImage = this.BackImage;


			// 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


		#region ExpandoCollection

		/// <summary>
		/// Represents a collection of Expando objects
		/// </summary>
		public class ExpandoCollection : CollectionBase
		{
			#region Class Data

			/// <summary>
			/// The TaskPane that owns this ExpandoCollection
			/// </summary>
			private TaskPane owner;

			#endregion


			#region Constructor

			/// <summary>
			/// Initializes a new instance of the TaskPane.ExpandoCollection class
			/// </summary>
			/// <param name="owner">A TaskPane representing the taskpane that owns 
			/// the Expando collection</param>
			public ExpandoCollection(TaskPane owner) : base()
			{
				if (owner == null)
				{
					throw new ArgumentNullException("owner");
				}
				
				this.owner = owner;
			}

			#endregion


			#region Methods

			/// <summary>
			/// Adds the specified expando to the expando collection
			/// </summary>
			/// <param name="value">The Expando to add to the expando collection</param>
			public void Add(Expando value)
			{
				if (value == null)
				{
					throw new ArgumentNullException("value");
				}

				this.List.Add(value);
				this.owner.Controls.Add(value);

				this.owner.OnExpandoAdded(new ExpandoEventArgs(value));
			}


			/// <summary>
			/// Adds an array of expando objects to the collection
			/// </summary>
			/// <param name="expandos">An array of Expando objects to add 
			/// to the collection</param>
			public void AddRange(Expando[] expandos)
			{
				if (expandos == null)
				{
					throw new ArgumentNullException("expandos");
				}

				for (int i=0; i<expandos.Length; i++)
				{
					this.Add(expandos[i]);
				}
			}
			
			
			/// <summary>
			/// Removes all expandos from the collection
			/// </summary>
			public new void Clear()
			{
				while (this.Count > 0)
				{
					this.RemoveAt(0);
				}
			}


			/// <summary>
			/// Determines whether the specified expando is a member of the 
			/// collection
			/// </summary>
			/// <param name="expandos">The Expando to locate in the collection</param>
			/// <returns>true if the Expando is a member of the collection; 
			/// otherwise, false</returns>
			public bool Contains(Expando expando)
			{
				if (expando == null)
				{
					throw new ArgumentNullException("expando");
				}

				return (this.IndexOf(expando) != -1);
			}


			/// <summary>
			/// Determines whether the specified control is a member of the 
			/// collection
			/// </summary>
			/// <param name="control">The Control to locate in the collection</param>
			/// <returns>true if the Control is a member of the collection; 
			/// otherwise, false</returns>
			public bool Contains(Control control)
			{
				if (!(control is Expando))
				{
					return false;
				}

				return this.Contains((Expando) control);
			}


			/// <summary>
			/// Retrieves the index of the specified expando in the expando 
			/// collection
			/// </summary>
			/// <param name="expando">The Expando to locate in the collection</param>
			/// <returns>A zero-based index value that represents the position 
			/// of the specified Expando in the TaskPane.ExpandoCollection</returns>
			public int IndexOf(Expando expando)
			{
				if (expando == null)
				{
					throw new ArgumentNullException("expando");
				}
				
				for (int i=0; i<this.Count; i++)
				{
					if (this[i] == expando)
					{
						return i;
					}
				}

				return -1;
			}


			/// <summary>
			/// Removes the specified expando from the expando collection
			/// </summary>
			/// <param name="value">The Expando to remove from the 
			/// TaskPane.ExpandoCollection</param>
			public void Remove(Expando value)
			{
				if (value == null)
				{
					throw new ArgumentNullException("value");
				}

				this.List.Remove(value);
				this.owner.Controls.Remove(value);

				this.owner.OnExpandoRemoved(new ExpandoEventArgs(value));
			}

			
			/// <summary>
			/// Removes an expando from the expando collection at the 
			/// specified indexed location
			/// </summary>
			/// <param name="index">The index value of the Expando to 
			/// remove</param>
			public new void RemoveAt(int index)
			{
				this.Remove(this[index]);
			}


			/// <summary>
			/// Moves the specified expando to the specified indexed location 
			/// in the expando collection
			/// </summary>
			/// <param name="expando">The expando to be moved</param>
			/// <param name="index">The indexed location in the expando collection 
			/// that the specified expando will be moved to</param>
			public void Move(Expando value, int index)
			{
				if (value == null)
				{
					throw new ArgumentNullException("value");
				}

				// make sure the index is within range
				if (index < 0)
				{
					index = 0;
				}
				else if (index > this.Count)
				{
					index = this.Count;
				}

				// don't go any further if the expando is already 
				// in the desired position or we don't contain it
				if (!this.Contains(value) || this.IndexOf(value) == index)
				{
					return;
				}

				this.List.Remove(value);

				// if the index we're supposed to move the expando to
				// is now greater to the number of expandos contained, 
				// add it to the end of the list, otherwise insert it at 
				// the specified index
				if (index > this.Count)
				{
					this.List.Add(value);
				}
				else
				{
					this.List.Insert(index, value);
				}

				// re-layout the controls
				this.owner.MatchControlCollToExpandoColl();
			}


			/// <summary>
			/// Moves the specified expando to the top of the expando collection
			/// </summary>
			/// <param name="value">The expando to be moved</param>
			public void MoveToTop(Expando value)
			{
				this.Move(value, 0);
			}


			/// <summary>
			/// Moves the specified expando to the bottom of the expando collection
			/// </summary>
			/// <param name="value">The expando to be moved</param>
			public void MoveToBottom(Expando value)
			{
				this.Move(value, this.Count);
			}

			#endregion


			#region Properties

			/// <summary>
			/// The Expando located at the specified index location within 
			/// the expando collection
			/// </summary>
			/// <param name="index">The index of the expando to retrieve 
			/// from the expando collection</param>
			public virtual Expando this[int index]
			{
				get
				{
					return this.List[index] as Expando;
				}
			}

			#endregion
		}

		#endregion
	
	
		#region ExpandoCollectionEditor

		/// <summary>
		/// 
		/// </summary>
		internal class ExpandoCollectionEditor : CollectionEditor
		{
			/// <summary>
			/// Initializes a new instance of the CollectionEditor class 
			/// using the specified collection type
			/// </summary>
			/// <param name="type"></param>
			public ExpandoCollectionEditor(Type type) : base(type)
			{
			
			}


			/// <summary>
			/// Edits the value of the specified object using the specified 
			/// service provider and context
			/// </summary>
			/// <param name="context">An ITypeDescriptorContext that can be 
			/// used to gain additional context information</param>
			/// <param name="isp">A service provider object through which 
			/// editing services can be obtained</param>
			/// <param name="value">The object to edit the value of</param>
			/// <returns>The new value of the object. If the value of the 
			/// object has not changed, this should return the same object 
			/// it was passed</returns>
			public override object EditValue(ITypeDescriptorContext context, IServiceProvider isp, object value)
			{
				TaskPane originalControl = (TaskPane) context.Instance;

				object returnObject = base.EditValue(context, isp, value);

				originalControl.UpdateExpandos();

				return returnObject;
			}


			/// <summary>
			/// Creates a new instance of the specified collection item type
			/// </summary>
			/// <param name="itemType">The type of item to create</param>
			/// <returns>A new instance of the specified object</returns>
			protected override object CreateInstance(Type itemType)
			{
				object expando = base.CreateInstance(itemType);
			
				((Expando) expando).Name = "expando";
			
				return expando;
			}
		}

		#endregion
	}

	#endregion



	#region TaskPaneDesigner

	/// <summary>
	/// A custom designer used by TaskPanes to remove unwanted 
	/// properties from the Property window in the designer
	/// </summary>
	public class TaskPaneDesigner : ScrollableControlDesigner
	{
		/// <summary>
		/// Initializes a new instance of the TaskPaneDesigner class
		/// </summary>
		public TaskPaneDesigner() : base()
		{
			
		}


		/// <summary>
		/// Adjusts the set of properties the component exposes through 
		/// a TypeDescriptor
		/// </summary>
		/// <param name="properties">An IDictionary containing the properties 
		/// for the class of the component</param>
		protected override void PreFilterProperties(System.Collections.IDictionary properties)
		{
			base.PreFilterProperties(properties);

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

	#endregion



	#region AnimationHelper

	/// <summary>
	/// A class that helps Expandos animate
	/// </summary>
	public class AnimationHelper
	{
		#region Class Data

		/// <summary>
		/// Specifes that a fade animation is to be performed
		/// </summary>
		public static int FadeAnimation = 1;
		
		/// <summary>
		/// Specifes that a slide animation is to be performed
		/// </summary>
		public static int SlideAnimation = 2;

		/// <summary>
		/// The type of animation to perform
		/// </summary>
		private int animationType;

		/// <summary>
		/// the TaskPane the helper belongs to
		/// </summary>
		private TaskPane taskpane;

		/// <summary>
		/// The Expando to animate
		/// </summary>
		private Expando expando;

		/// <summary>
		/// The current frame in animation
		/// </summary>
		private int animationStepNum;

		/// <summary>
		/// The number of frames in the animation
		/// </summary>
		private int numAnimationSteps;

		/// <summary>
		/// The amount of time each frame is shown (in milliseconds)
		/// </summary>
		private int animationFrameInterval;

		/// <summary>
		/// Specifies whether an animation is being performed
		/// </summary>
		private bool animating;

		/// <summary>
		/// A timer that notifies the helper when the next frame is due
		/// </summary>
		private Timer animationTimer;

		#endregion


		#region Constructor

		/// <summary>
		/// Initializes a new instance of the AnimationHelper class with the specified settings
		/// </summary>
		/// <param name="taskpane">The TaskPane the AnimationHelper belongs to</param>
		/// <param name="expando">The Expando to be animated</param>
		/// <param name="animationType">The type of animation to perform</param>
		public AnimationHelper(TaskPane taskpane, Expando expando, int animationType)
		{
			this.taskpane = taskpane;
			this.expando = expando;
			this.animationType = animationType;

			this.animating = false;

			this.numAnimationSteps = 20;
			this.animationFrameInterval = 10;

			// 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 ready to animate
			if (this.AnimationType == FadeAnimation)
			{
				this.expando.StartFadeAnimation();
			}
			else
			{
				this.expando.StartSlideAnimation();
			}

			// 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
				if (this.AnimationType == FadeAnimation)
				{
					this.expando.UpdateFadeAnimation(this.animationStepNum, this.numAnimationSteps);
				}
				else
				{
					this.expando.UpdateSlideAnimation(this.animationStepNum, this.numAnimationSteps);
				}

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

				if (this.AnimationType == FadeAnimation)
				{
					this.expando.StopFadeAnimation();
				}
				else
				{
					this.expando.StopSlideAnimation();
				}

				// 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;
			}
		}


		/// <summary>
		/// Gets the type of animation to perform
		/// </summary>
		public int AnimationType
		{
			get
			{
				return this.animationType;
			}
		}

		#endregion


		#region Events

		/// <summary>
		/// Event handler for the animation timer tick event
		/// </summary>
		/// <param name="sender">The object that fired the event</param>
		/// <param name="e">An EventArgs that contains the event data</param>
		private void animationTimer_Tick(object sender, EventArgs e)
		{
			// do the next bit of the aniation
			this.PerformAnimation();
		}

		#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