/*
* 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.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Design;
using System.Drawing.Imaging;
using System.Data;
using System.Globalization;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace XPExplorerBar
{
#region Delegates
/// <summary>
///
/// </summary>
public delegate void ExpandoEventHandler(ExpandoEventArgs e);
#endregion
#region Expando
/// <summary>
/// Summary description for Expando.
/// </summary>
[ToolboxItem(true)]
[DesignerAttribute(typeof(ExpandoDesigner))]
public class Expando : Control, ISupportInitialize
{
#region EventHandlers
/// <summary>
/// Occurs when the value of the Collapsed property changes
/// </summary>
public event ExpandoEventHandler StateChanged;
/// <summary>
/// Occurs when the value of the TitleImage property changes
/// </summary>
public event ExpandoEventHandler TitleImageChanged;
/// <summary>
/// Occurs when the value of the SpecialGroup property changes
/// </summary>
public event ExpandoEventHandler SpecialGroupChanged;
/// <summary>
///
/// </summary>
public event ControlEventHandler ItemAdded;
/// <summary>
///
/// </summary>
public event ControlEventHandler ItemRemoved;
#endregion
#region Class Data
/// <summary>
/// Required designer variable
/// </summary>
protected Container components = null;
/// <summary>
/// System settings for the Expando
/// </summary>
protected ExplorerBarInfo systemSettings;
/// <summary>
/// Is the Expando a special group
/// </summary>
protected bool specialGroup;
/// <summary>
/// The height of the Expando in its expanded state
/// </summary>
protected int expandedHeight;
/// <summary>
/// The image displayed on the left side of the titlebar
/// </summary>
protected Image titleImage;
/// <summary>
///
/// </summary>
protected Image watermark;
/// <summary>
/// The height of the header section
/// (includes titlebar and title image)
/// </summary>
protected int headerHeight;
/// <summary>
/// Is the Expando collapsed
/// </summary>
protected bool collapsed;
/// <summary>
/// The state of the titlebar
/// </summary>
protected FocusStates focusState;
/// <summary>
/// The height of the titlebar
/// </summary>
protected int titleBarHeight;
/// <summary>
/// Are we currently animating a fade
/// </summary>
protected bool animatingFade;
/// <summary>
/// Are we currently animating a slide
/// </summary>
protected bool animatingSlide;
/// <summary>
/// An image of the "client area" which is used
/// during a fade animation
/// </summary>
protected Image animationImage;
/// <summary>
/// The TaskPane the Expando belongs to
/// </summary>
protected TaskPane taskpane;
/// <summary>
/// Should the Expando layout its items itself
/// </summary>
protected bool autoLayout;
/// <summary>
/// The last known width of the Expando
/// (used while animating)
/// </summary>
protected int oldWidth;
/// <summary>
/// Are we initialising
/// </summary>
protected bool initialising = false;
/// <summary>
/// Internal list of items contained in the Expando
/// </summary>
protected ItemCollection itemList;
/// <summary>
/// Internal list of controls that have been hidden
/// </summary>
protected ArrayList hiddenControls;
/// <summary>
/// Are we able to perform any layout operations;
/// </summary>
protected bool layout;
/// <summary>
/// A panel we can move our controls onto when we are
/// animating from collapsed to expanded.
/// </summary>
internal AnimationPanel dummyPanel;
/// <summary>
/// Is the Expando allowed to collapse
/// </summary>
protected bool canCollapse;
/// <summary>
/// The height of the Expando at the end of its slide animation
/// </summary>
protected int slideEndHeight;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the Expando class with default settings
/// </summary>
public Expando() : base()
{
// This call is required by the Windows.Forms Form Designer.
this.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.SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
// get the system theme settings
this.systemSettings = Util.GetSystemExplorerBarSettings();
this.BackColor = this.systemSettings.Expando.NormalBackColor;
// the height of the Expando in the expanded state
this.expandedHeight = 100;
// size
this.Size = new Size(this.systemSettings.Header.BackImageWidth, this.expandedHeight);
this.titleBarHeight = this.systemSettings.Header.BackImageHeight;
this.headerHeight = this.titleBarHeight;
this.oldWidth = this.Width;
// start expanded
this.collapsed = false;
// not a special group
this.specialGroup = false;
// unfocused titlebar
this.focusState = FocusStates.None;
// no title image
this.titleImage = null;
this.watermark = null;
// animation
this.animatingFade = false;
this.animatingSlide = false;
this.animationImage = null;
this.slideEndHeight = -1;
// don't get the Expando to layout its items itself
this.autoLayout = false;
// don't know which TaskPane we belong to
this.taskpane = null;
// internal list of items
this.itemList = new ItemCollection(this);
this.hiddenControls = new ArrayList();
// initialise the dummyPanel
this.dummyPanel = new AnimationPanel();
this.dummyPanel.Size = this.Size;
this.dummyPanel.Location = new Point(-1000, 0);
this.canCollapse = true;
this.layout = true;
this.Font = new Font(this.TitleFont.Name, 8.25f, FontStyle.Regular);
}
#endregion
#region Methods
#region Animation
#region Fade Collapse/Expand
/// <summary>
/// Collapses the group without any animation.
/// </summary>
public void Collapse()
{
if (!this.Enabled)
{
return;
}
this.Height = this.headerHeight;
}
/// <summary>
/// Expands the group without any animation.
/// </summary>
public void Expand()
{
if (!this.Enabled)
{
return;
}
this.Height = this.expandedHeight;
}
/// <summary>
/// Gets the Expando ready to start its collapse/expand animation
/// </summary>
internal void StartFadeAnimation()
{
if (!this.Enabled)
{
return;
}
//
this.animatingFade = true;
//
this.SuspendLayout();
// get an image of the client area that we can
// use for alpha-blending in our animation
this.animationImage = this.GetFadeAnimationImage();
// set each control invisible (otherwise they
// appear to slide off the bottom of the group)
foreach (Control control in this.Controls)
{
control.Visible = false;
}
// restart the layout engine
this.ResumeLayout(false);
}
/// <summary>
/// Updates the next "frame" of the animation
/// </summary>
internal void UpdateFadeAnimation(int animationStepNum, int numAnimationSteps)
{
if (!this.Enabled)
{
return;
}
// the percentage we need to adjust our height by
// double step = (1 / (double) numAnimationSteps) * animationStepNum;
// replacement by: Joel Holdsworth (joel@airwebreathe.org.uk)
// Paolo Messina (ppescher@hotmail.com)
// 05/06/2004
// v1.1
double step = (1.0 - Math.Cos(Math.PI * (double) animationStepNum / (double) numAnimationSteps)) / 2.0;
// set the height of the group
if (this.collapsed)
{
this.Height = this.expandedHeight - (int) ((this.expandedHeight - this.headerHeight) * step);
}
else
{
this.Height = this.headerHeight + (int) ((this.expandedHeight - this.headerHeight) * step);
}
// draw the next frame
this.Invalidate();
}
/// <summary>
/// Gets the Expando to stop its animation
/// </summary>
internal void StopFadeAnimation()
{
if (!this.Enabled)
{
return;
}
//
this.animatingFade = false;
//
this.SuspendLayout();
// get rid of the image used for the animation
this.animationImage.Dispose();
this.animationImage = null;
// set the final height of the group, depending on
// whether we are collapsed or expanded
if (this.collapsed)
{
this.Collapse();
}
else
{
this.Expand();
}
// set each control visible again
foreach (Control control in this.Controls)
{
control.Visible = !this.hiddenControls.Contains(control);
}
//
this.ResumeLayout(true);
}
/// <summary>
/// Returns an image of the group's display area to be used
/// in the fade animation
/// </summary>
/// <returns>The Image to use during the fade animation</returns>
internal Image GetFadeAnimationImage()
{
if (this.Height == this.ExpandedHeight)
{
return this.GetExpandedImage();
}
else
{
return this.GetCollapsedImage();
}
}
/// <summary>
/// Gets the image to be used in the animation while the
/// Expando is in its expanded state
/// </summary>
/// <returns>The Image to use during the fade animation</returns>
internal Image GetExpandedImage()
{
// create a new image to draw into
Image image = new Bitmap(this.Width, this.Height);
// get a graphics object we can draw into
Graphics g = Graphics.FromImage(image);
IntPtr hDC = g.GetHdc();
// some flags to tell the control how to draw itself
IntPtr flags = (IntPtr) (WmPrintFlags.PRF_CLIENT | WmPrintFlags.PRF_CHILDREN | WmPrintFlags.PRF_ERASEBKGND);
// tell the control to draw itself
NativeMethods.SendMessage(this.Handle, WindowMessageFlags.WM_PRINT, hDC, flags);
// clean up resources
g.ReleaseHdc(hDC);
g.Dispose();
// return the completed animation image
return image;
}
/// <summary>
/// Gets the image to be used in the animation while the
/// Expando is in its collapsed state
/// </summary>
/// <returns>The Image to use during the fade animation</returns>
internal Image GetCollapsedImage()
{
// this is pretty nasty. after much experimentation,
// this is the least preferred way to get the image as
// it is a pain in the backside, but it stops any
// flickering and it gets xp themed controls to draw
// their borders properly.
// we have to do this in two stages:
// 1) pretend we're expanded and draw our background,
// borders and "client area" into a bitmap
// 2) set the bitmap as our dummyPanel's background,
// move all our controls onto the dummyPanel and
// get the dummyPanel to print itself
int width = this.Width;
int height = this.ExpandedHeight;
// create a new image to draw that is the same
// size we would be if we were expanded
Image backImage = new Bitmap(width, height);
// get a graphics object we can draw into
Graphics g = Graphics.FromImage(backImage);
// draw our parents background
this.PaintTransparentBackground(g, new Rectangle(0, 0, width, height));
// don't need to draw the titlebar as it is ignored
// when we paint with the animation image, but we do
// need to draw the borders and "client area"
// borders
using (SolidBrush brush = new SolidBrush(this.BorderColor))
{
// top border
g.FillRectangle(brush,
this.Border.Left,
this.HeaderHeight,
width - this.Border.Left - this.Border.Right,
this.Border.Top);
// left border
g.FillRectangle(brush,
0,
this.HeaderHeight,
this.Border.Left,
height - this.HeaderHeight);
// right border
g.FillRectangle(brush,
width - this.Border.Right,
this.HeaderHeight,
this.Border.Right,
height - this.HeaderHeight);
// bottom border
g.FillRectangle(brush,
this.Border.Left,
height - this.Border.Bottom,
width - this.Border.Left - this.Border.Right,
this.Border.Bottom);
}
// "client area"
using (SolidBrush brush = new SolidBrush(this.BackColor))
{
g.FillRectangle(brush,
this.Border.Left,
this.HeaderHeight,
width - this.Border.Left - this.Border.Right,
height - this.HeaderHeight - this.Border.Bottom - this.Border.Top);
}
// watermark
if (this.Watermark != null)
{
// work out a rough location of where the watermark should go
Rectangle rect = new Rectangle(0, 0, this.Watermark.Width, this.Watermark.Height);
rect.X = width - this.Border.Right - this.Watermark.Width;
rect.Y = height - this.Border.Bottom - this.Watermark.Height;
// 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
g.DrawImage(this.Watermark, rect);
}
// cleanup resources;
g.Dispose();
// make sure the dummyPanel is the same size as our image
// (we don't want any tiling of the image)
this.dummyPanel.Size = new Size(width, height);
this.dummyPanel.HeaderHeight = this.HeaderHeight;
this.dummyPanel.Border = this.Border;
// set the image as the dummyPanels background
this.dummyPanel.BackImage = backImage;
// move all our controls to the dummyPanel, and then add
// the dummyPanel to us
while (this.Controls.Count > 0)
{
Control control = this.Controls[0];
this.Controls.RemoveAt(0);
this.dummyPanel.Controls.Add(control);
control.Visible = !this.hiddenControls.Contains(control);
}
this.Controls.Add(this.dummyPanel);
// create a new image for the dummyPanel to draw itself into
Image image = new Bitmap(width, height);
// get a graphics object we can draw into
g = Graphics.FromImage(image);
IntPtr hDC = g.GetHdc();
// some flags to tell the control how to draw itself
IntPtr flags = (IntPtr) (WmPrintFlags.PRF_CLIENT | WmPrintFlags.PRF_CHILDREN);
// tell the control to draw itself
NativeMethods.SendMessage(this.dummyPanel.Handle, WindowMessageFlags.WM_PRINT, hDC, flags);
// clean up resources
g.ReleaseHdc(hDC);
g.Dispose();
this.Controls.Remove(this.dummyPanel);
// get our controls back
while (this.dummyPanel.Controls.Count > 0)
{
Control control = this.dummyPanel.Controls[0];
control.Visible = false;
this.dummyPanel.Controls.RemoveAt(0);
this.Controls.Add(control);
}
// dispose of the background image
this.dummyPanel.BackImage = null;
backImage.Dispose();
return image;
}
#endregion
#region Slide Show/Hide
/// <summary>
/// Gets the Expando ready to start its show/hide animation
/// </summary>
internal void StartSlideAnimation()
{
if (!this.Enabled)
{
return;
}
this.animatingSlide = true;
this.slideEndHeight = this.CalcHeightAndLayout();
}
/// <summary>
/// Updates the next "frame" of the animation
/// </summary>
internal void UpdateSlideAnimation(int animationStepNum, int numAnimationSteps)
{
if (!this.Enabled)
{
return;
}
// the percentage we need to adjust our height by
// double step = (1 / (double) numAnimationSteps) * animationStepNum;
// replacement by: Joel Holdsworth (joel@airwebreathe.org.uk)
// Paolo Messina (ppescher@hotmail.com)
// 05/06/2004
// v1.1
double step = (1.0 - Math.Cos(Math.PI * (double) animationStepNum / (double) numAnimationSteps)) / 2.0;
// set the height of the group
this.Height = this.expandedHeight + (int) ((this.slideEndHeight - this.expandedHeight) * step);
// draw the next frame
this.Invalidate();
}
/// <summary>
/// Gets the Expando to stop its animation
/// </summary>
internal void StopSlideAnimation()
{
if (!this.Enabled)
{
return;
}
this.animatingSlide = false;
// make sure we're the right height
this.Height = this.slideEndHeight;
this.slideEndHeight = -1;
this.DoLayout();
}
#endregion
#endregion
#region Controls
/// <summary>
/// Hides the specified Control
/// </summary>
/// <param name="control">The Control to hide</param>
public void HideControl(Control control)
{
this.HideControl(control, false);
}
/// <summary>
/// Hides the specified Control
/// </summary>
/// <param name="control">The Control to hide</param>
/// <param name="animate">Will any animation be performed</param>
public void HideControl(Control control, bool animate)
{
this.HideControl(new Control[] {control}, animate);
}
/// <summary>
/// Hides the Controls contained in the specified array
/// </summary>
/// <param name="controls">The array Controls to hide</param>
public void HideControl(Control[] controls)
{
this.HideControl(controls, false);
}
/// <summary>
/// Hides the Controls contained in the specified array
/// </summary>
/// <param name="controls">The array Controls to hide</param>
/// <param name="animate">Will any animation be performed</param>
public void HideControl(Control[] controls, bool animate)
{
// don't bother if we are animating or are collapsed
// or are disabled
if (this.Animating || this.Collapsed || !this.Enabled)
{
return;
}
this.SuspendLayout();
// flag to check if we actually hid any controls
bool anyHidden = false;
foreach (Control control in controls)
{
// hide the control if we own it and it is not already hidden
if (this.Controls.Contains(control) && !this.hiddenControls.Contains(control))
{
anyHidden = true;
control.Visible = false;
this.hiddenControls.Add(control);
}
}
this.ResumeLayout(false);
// if we didn't hide any, get out of here
if (!anyHidden)
{
return;
}
// are we able to animate?
if (!animate || !this.AutoLayout || this.TaskPane == null || !this.TaskPane.Animate)
{
// guess not
this.DoLayout();
}
else
{
this.TaskPane.StartSlideAnimation(this);
}
}
/// <summary>
/// Shows the specified Control
/// </summary>
/// <param name="control">The Control to show</param>
public void ShowControl(Control control)
{
this.ShowControl(control, false);
}
/// <summary>
/// Shows the specified Control
/// </summary>
/// <param name="control">The Control to show</param>
/// <param name="animate">Will any animation be performed</param>
public void ShowControl(Control control, bool animate)
{
this.ShowControl(new Control[] {control}, animate);
}
/// <summary>
/// Shows the Controls contained in the specified array
/// </summary>
/// <param name="controls">The array Controls to show</param>
public void ShowControl(Control[] controls)
{
this.ShowControl(controls, false);
}
/// <summary>
/// Shows the Controls contained in the specified array
/// </summary>
/// <param name="controls">The array Controls to show</param>
/// <param name="animate">Will any animation be performed</param>
public void ShowControl(Control[] controls, bool animate)
{
// don't bother if we are animating or are collapsed
// or are disabled
if (this.Animating || this.Collapsed || !this.Enabled)
{
return;
}
this.SuspendLayout();
// flag to check if any controls were shown
bool anyHidden = false;
foreach (Control control in controls)
{
// show the control if we own it and it is not already shown
if (this.Controls.Contains(control) && this.hiddenControls.Contains(control))
{
anyHidden = true;
control.Visible = true;
this.hiddenControls.Remove(control);
}
}
this.ResumeLayout(false);
// if we didn't show any, get out of here
if (!anyHidden)
{
return;
}
// are we able to animate?
if (!animate || !this.AutoLayout || this.TaskPane == null || !this.TaskPane.Animate)
{
// guess not
this.DoLayout();
}
else
{
this.TaskPane.StartSlideAnimation(this);
}
}
#endregion
#region Invalidation
/// <summary>
/// Invalidates the titlebar area
/// </summary>
protected void InvalidateTitleBar()
{
this.Invalidate(new Rectangle(0, 0, this.Width, this.headerHeight), false);
}
#endregion
#region ISupportInitialize Members
/// <summary>
/// Signals the object that initialization is starting
/// </summary>
public void BeginInit()
{
this.initialising = true;
}
/// <summary>
/// Signals the object that initialization is complete
/// </summary>
public void EndInit()
{
this.initialising = false;
this.DoLayout();
}
#endregion
#region Layout
/// <summary>
/// Forces the control to apply layout logic to child controls,
/// and adjusts the height of the Expando if necessary
/// </summary>
public virtual void DoLayout()
{
// stop the layout engine
this.SuspendLayout();
// work out the height of the header section
// is there an image to display on the titlebar
if (this.titleImage != null)
{
// is the image bigger than the height of the titlebar
if (this.titleImage.Height > this.titleBarHeight)
{
this.headerHeight = this.titleImage.Height;
}
// is the image smaller than the height of the titlebar
else if (this.titleImage.Height < this.titleBarHeight)
{
this.headerHeight = this.titleBarHeight;
}
// is the image smaller than the current header height
else if (this.titleImage.Height < this.headerHeight)
{
this.headerHeight = this.titleImage.Height;
}
}
else
{
this.headerHeight = this.titleBarHeight;
}
// do we need to layout our items
if (this.AutoLayout)
{
Control c;
TaskItem ti;
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.PseudoClientRect.Width - this.Padding.Left - this.Padding.Right;
// for each control in our list...
for (int i=0; i<this.itemList.Count; i++)
{
c = (Control) this.itemList[i];
if (this.hiddenControls.Contains(c))
{
continue;
}
// set the starting point
p = new Point(this.Padding.Left, y);
// is the control a TaskItem? if so, we may
// need to take into account the margins
if (c is TaskItem)
{
ti = (TaskItem) c;
// only adjust the y co-ord if this isn't the first item
if (i > 0)
{
y += ti.Margin.Top;
p.Y = y;
}
// adjust and set the width and height
width -= ti.Margin.Left + ti.Margin.Right;
ti.Width = width;
ti.Height = ti.PreferredHeight;
}
// set the location of the control
c.Location = p;
// update the next starting point.
y += c.Height;
// is the control a TaskItem? if so, we may
// need to take into account the bottom margin
if (i < this.itemList.Count-1)
{
if (c is TaskItem)
{
ti = (TaskItem) c;
y += ti.Margin.Bottom;
}
else
{
y += this.systemSettings.TaskLink.Margin.Bottom;
}
}
}
// workout where the bottom of the Expando should be
y += this.Padding.Bottom + this.Border.Bottom;
// adjust the ExpandedHeight if they're not the same
if (y != this.ExpandedHeight)
{
this.ExpandedHeight = y;
// if we're not collapsed then we had better change
// our height as well
if (!this.Collapsed)
{
this.Height = this.ExpandedHeight;
// if we belong to a TaskPane then it needs to
// re-layout its Expandos
if (this.TaskPane != null)
{
this.TaskPane.DoLayout();
}
}
}
}
// restart the layout engine
this.ResumeLayout(true);
}
/// <summary>
/// Calculates the height that the Expando would be if a
/// call to DoLayout() were made
/// </summary>
/// <returns>The height that the Expando would be if a
/// call to DoLayout() were made</returns>
internal int CalcHeightAndLayout()
{
// stop the layout engine
this.SuspendLayout();
// work out the height of the header section
// is there an image to display on the titlebar
if (this.titleImage != null)
{
// is the image bigger than the height of the titlebar
if (this.titleImage.Height > this.titleBarHeight)
{
this.headerHeight = this.titleImage.Height;
}
// is the image smaller than the height of the titlebar
else if (this.titleImage.Height < this.titleBarHeight)
{
this.headerHeight = this.titleBarHeight;
}
// is the image smaller than the current header height
else if (this.titleImage.Height < this.headerHeight)
{
this.headerHeight = this.titleImage.Height;
}
}
else
{
this.headerHeight = this.titleBarHeight;
}
int y = -1;
// do we need to layout our items
if (this.AutoLayout)
{
Control c;
TaskItem ti;
Point p;
// work out how wide to make the controls, and where
// the top of the first control should be
y = this.DisplayRectangle.Y + this.Padding.Top;
int width = this.PseudoClientRect.Width - this.Padding.Left - this.Padding.Right;
// for each control in our list...
for (int i=0; i<this.itemList.Count; i++)
{
c = (Control) this.itemList[i];
if (this.hiddenControls.Contains(c))
{
continue;
}
// set the starting point
p = new Point(this.Padding.Left, y);
// is the control a TaskItem? if so, we may
// need to take into account the margins
if (c is TaskItem)
{
ti = (TaskItem) c;
// only adjust the y co-ord if this isn't the first item
if (i > 0)
{
y += ti.Margin.Top;
p.Y = y;
}
// adjust and set the width and height
width -= ti.Margin.Left + ti.Margin.Right;
ti.Width = width;
ti.Height = ti.PreferredHeight;
}
// set the location of the control
c.Location = p;
// update the next starting point.
y += c.Height;
// is the control a TaskItem? if so, we may
// need to take into account the bottom margin
if (i < this.itemList.Count-1)
{
if (c is TaskItem)
{
ti = (TaskItem) c;
y += ti.Margin.Bottom;
}
else
{
y += this.systemSettings.TaskLink.Margin.Bottom;
}
}
}
// workout where the bottom of the Expando should be
y += this.Padding.Bottom + this.Border.Bottom;
}
// restart the layout engine
this.ResumeLayout(true);
return y;
}
/// <summary>
/// Updates the layout of the Expandos items while in design mode, and
/// adds/removes itemss from the ControlCollection as necessary
/// </summary>
internal void UpdateItems()
{
if (this.Items.Count == this.Controls.Count)
{
// make sure the the items index in the ControlCollection
// are the same as in the ItemCollection (indexes in the
// ItemCollection may have changed due to the user moving
// them around in the editor)
this.MatchControlCollToItemColl();
return;
}
// were any items added
if (this.Items.Count > this.Controls.Count)
{
// add any extra items in the ItemCollection to the
// ControlCollection
for (int i=0; i<this.Items.Count; i++)
{
if (!this.Controls.Contains(this.Items[i]))
{
this.OnItemAdded(new ControlEventArgs(this.Items[i]));
}
}
}
else
{
// items were removed
int i = 0;
Control control;
// remove any extra items from the ControlCollection
while (i < this.Controls.Count)
{
control = (Control) this.Controls[i];
if (!this.Items.Contains(control))
{
this.OnItemRemoved(new ControlEventArgs(control));
}
else
{
i++;
}
}
}
this.Invalidate(true);
}
/// <summary>
/// Make sure the controls index in the ControlCollection
/// are the same as in the ItemCollection (indexes in the
/// ItemCollection may have changed due to the user moving
/// them around in the editor or calling ItemCollection.Move())
/// </summary>
internal void MatchControlCollToItemColl()
{
this.SuspendLayout();
for (int i=0; i<this.Items.Count; i++)
{
this.Controls.SetChildIndex(this.Items[i], i);
}
this.ResumeLayout(false);
this.DoLayout();
this.Invalidate(true);
}
#endregion
#endregion
#region Properties
#region Alignment
/// <summary>
/// The alignment of the text in the title bar.
/// </summary>
protected ContentAlignment TitleAlignment
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Header.SpecialAlignment;
}
return this.systemSettings.Header.NormalAlignment;
}
}
#endregion
#region Animation
/// <summary>
/// Gets whether the Expando is currently animating
/// </summary>
[Browsable(false)]
public bool Animating
{
get
{
return (this.animatingFade || this.animatingSlide);
}
}
/// <summary>
/// Gets the Image used by the Expando while it is animating
/// </summary>
protected Image AnimationImage
{
get
{
return this.animationImage;
}
}
#endregion
#region Border
/// <summary>
/// The width of the border along each side of the Expando's pane.
/// </summary>
protected Border Border
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Expando.SpecialBorder;
}
return this.systemSettings.Expando.NormalBorder;
}
}
/// <summary>
/// The color of the border along each side of the Expando's pane.
/// </summary>
protected Color BorderColor
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Expando.SpecialBorderColor;
}
return this.systemSettings.Expando.NormalBorderColor;
}
}
/// <summary>
/// The width of the border along each side of the Expando's Title Bar.
/// </summary>
protected Border TitleBorder
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Header.SpecialBorder;
}
return this.systemSettings.Header.NormalBorder;
}
}
#endregion
#region Color
/// <summary>
/// Gets the backgroubd color of the titlebar
/// </summary>
protected Color TitleBackColor
{
get
{
if (this.SpecialGroup)
{
if (this.systemSettings.Header.SpecialBackColor != Color.Transparent)
{
return this.systemSettings.Header.SpecialBackColor;
}
return this.systemSettings.Header.SpecialBorderColor;
}
if (this.systemSettings.Header.NormalBackColor != Color.Transparent)
{
return this.systemSettings.Header.NormalBackColor;
}
return this.systemSettings.Header.NormalBorderColor;
}
}
#endregion
#region Client Rectangle
/// <summary>
/// Returns a fake Client Rectangle.
/// The rectangle takes into account the size of the titlebar
/// and borders (these are actually parts of the real
/// ClientRectangle)
/// </summary>
protected Rectangle PseudoClientRect
{
get
{
return new Rectangle(this.Border.Left,
this.HeaderHeight + this.Border.Top,
this.Width - this.Border.Left - this.Border.Right,
this.Height - this.HeaderHeight - this.Border.Top - this.Border.Bottom);
}
}
/// <summary>
/// Returns the height of the fake client rectangle
/// </summary>
protected int PseudoClientHeight
{
get
{
return this.Height - this.HeaderHeight - this.Border.Top - this.Border.Bottom;
}
}
#endregion
#region Display Rectangle
/// <summary>
/// Overrides DisplayRectangle so that docked controls
/// don't cover the titlebar or borders
/// </summary>
[Browsable(false)]
public override Rectangle DisplayRectangle
{
get
{
return new Rectangle(this.Border.Left,
this.HeaderHeight + this.Border.Top,
this.Width - this.Border.Left - this.Border.Right,
this.ExpandedHeight - this.HeaderHeight - this.Border.Top - this.Border.Bottom);
}
}
/// <summary>
/// Gets a rectangle that contains the titlebar area
/// </summary>
protected Rectangle TitleBarRectangle
{
get
{
return new Rectangle(0,
this.HeaderHeight - this.TitleBarHeight,
this.Width,
this.TitleBarHeight);
}
}
#endregion
#region Fonts
/// <summary>
/// The color of the Title Bar's text.
/// </summary>
protected Color TitleForeColor
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Header.SpecialTitleColor;
}
return this.systemSettings.Header.NormalTitleColor;
}
}
/// <summary>
/// The color of the Title Bar's text when highlighted.
/// </summary>
protected Color TitleHotForeColor
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Header.SpecialTitleHotColor;
}
return this.systemSettings.Header.NormalTitleHotColor;
}
}
/// <summary>
/// Gets the current color of the Title Bar's text, depending
/// on the current state of the Expando
/// </summary>
protected Color TitleColor
{
get
{
if (this.FocusState == FocusStates.Mouse)
{
return this.TitleHotForeColor;
}
return this.TitleForeColor;
}
}
/// <summary>
/// The font used to render the Title Bar's text.
/// </summary>
protected Font TitleFont
{
get
{
return this.systemSettings.Header.TitleFont;
}
}
#endregion
#region Images
/// <summary>
/// Gets the expand/collapse arrow image currently displayed
/// in the title bar, depending on the current state of the Expando
/// </summary>
protected Image ArrowImage
{
get
{
if (this.SpecialGroup)
{
if (this.collapsed)
{
if (this.FocusState == FocusStates.None)
{
return this.systemSettings.Header.SpecialArrowDown;
}
else
{
return this.systemSettings.Header.SpecialArrowDownHot;
}
}
else
{
if (this.FocusState == FocusStates.None)
{
return this.systemSettings.Header.SpecialArrowUp;
}
else
{
return this.systemSettings.Header.SpecialArrowUpHot;
}
}
}
else
{
if (this.collapsed)
{
if (this.FocusState == FocusStates.None)
{
return this.systemSettings.Header.NormalArrowDown;
}
else
{
return this.systemSettings.Header.NormalArrowDownHot;
}
}
else
{
if (this.FocusState == FocusStates.None)
{
return this.systemSettings.Header.NormalArrowUp;
}
else
{
return this.systemSettings.Header.NormalArrowUpHot;
}
}
}
}
}
/// <summary>
/// Gets the width of the expand/collapse arrow image
/// currently displayed in the title bar
/// </summary>
protected int ArrowImageWidth
{
get
{
if (this.ArrowImage == null)
{
return 0;
}
return this.ArrowImage.Width;
}
}
/// <summary>
/// Gets the height of the expand/collapse arrow image
/// currently displayed in the title bar
/// </summary>
protected int ArrowImageHeight
{
get
{
if (this.ArrowImage == null)
{
return 0;
}
return this.ArrowImage.Height;
}
}
/// <summary>
/// The background image used for the Title Bar.
/// </summary>
protected Image TitleBackImage
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Header.SpecialBackImage;
}
return this.systemSettings.Header.NormalBackImage;
}
}
/// <summary>
/// Gets the height of the background image used for the Title Bar.
/// </summary>
protected int TitleBackImageHeight
{
get
{
return this.systemSettings.Header.BackImageHeight;
}
}
/// <summary>
/// The image used on the left side of the Title Bar.
/// </summary>
[Bindable(true),
Category("Appearance"),
DefaultValue(null),
Description("The image used on the left side of the Title Bar.")]
public Image TitleImage
{
get
{
return this.titleImage;
}
set
{
if (this.titleImage != value)
{
this.titleImage = value;
this.DoLayout();
this.Invalidate();
OnTitleImageChanged(new ExpandoEventArgs(this));
}
}
}
/// <summary>
/// The width of the image used on the left side of the Title Bar.
/// </summary>
protected int TitleImageWidth
{
get
{
if (this.TitleImage == null)
{
return 0;
}
return this.TitleImage.Width;
}
}
/// <summary>
/// The height of the image used on the left side of the Title Bar.
/// </summary>
protected int TitleImageHeight
{
get
{
if (this.TitleImage == null)
{
return 0;
}
return this.TitleImage.Height;
}
}
/// <summary>
///
/// </summary>
[Bindable(true),
Category("Appearance"),
DefaultValue(null),
Description("")]
public Image Watermark
{
get
{
return this.watermark;
}
set
{
if (this.watermark != value)
{
this.watermark = value;
this.Invalidate();
}
}
}
#endregion
#region Items
/// <summary>
/// An Expando.ItemCollection representing the collection of
/// Controls contained within the Expando
/// </summary>
[Category("Behavior"),
DefaultValue(null),
Description("The Controls contained in the Expando"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Editor(typeof(ItemCollectionEditor), typeof(UITypeEditor))]
public Expando.ItemCollection Items
{
get
{
return this.itemList;
}
}
/// <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 Layout
/// <summary>
/// Determines whether the Expando will automagically layout its items
/// </summary>
[Bindable(true),
Category("Layout"),
DefaultValue(false),
Description("The AutoLayout property determines whether the Expando will automagically layout its items.")]
public bool AutoLayout
{
get
{
return this.autoLayout;
}
set
{
this.autoLayout = value;
if (this.autoLayout)
{
this.DoLayout();
}
}
}
/// <summary>
/// The height of the Expando in its expanded state
/// </summary>
[Bindable(true),
Category("Layout"),
DefaultValue(100),
Description("The height of the Expando in its expanded state.")]
public int ExpandedHeight
{
get
{
return this.expandedHeight;
}
set
{
this.expandedHeight = value;
if (!this.collapsed && !this.Animating)
{
this.Height = this.expandedHeight;
if (this.taskpane != null)
{
this.taskpane.DoLayout();
}
}
}
}
/// <summary>
/// The height of the header section of the Expando
/// </summary>
protected int HeaderHeight
{
get
{
return this.headerHeight;
}
}
/// <summary>
/// The height of the titlebar
/// </summary>
protected int TitleBarHeight
{
get
{
return this.titleBarHeight;
}
}
#endregion
#region Padding
/// <summary>
/// The amount of space between the border and items along
/// each side of the ExplorerBarGroup.
/// </summary>
protected Padding Padding
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Expando.SpecialPadding;
}
return this.systemSettings.Expando.NormalPadding;
}
}
/// <summary>
/// The amount of space between the border and items along
/// each side of the Title Bar.
/// </summary>
protected Padding TitlePadding
{
get
{
if (this.SpecialGroup)
{
return this.systemSettings.Header.SpecialPadding;
}
return this.systemSettings.Header.NormalPadding;
}
}
#endregion
#region Size
/// <summary>
/// Gets or sets the height and width of the control
/// </summary>
public new Size Size
{
get
{
return base.Size;
}
set
{
if (!this.Size.Equals(value))
{
if (!this.Animating)
{
this.Width = value.Width;
this.ExpandedHeight = value.Height;
}
}
}
}
#endregion
#region Special Groups
/// <summary>
/// Specifies whether the Expando be rendered as a Special Group.
/// </summary>
[Bindable(true),
Category("Appearance"),
DefaultValue(false),
Description("The SpecialGroup property determines whether the Expando will be rendered as a SpecialGroup.")]
public bool SpecialGroup
{
get
{
return this.specialGroup;
}
set
{
this.specialGroup = value;
this.DoLayout();
if (this.specialGroup)
{
this.BackColor = this.systemSettings.Expando.SpecialBackColor;
}
else
{
this.BackColor = this.systemSettings.Expando.NormalBackColor;
}
this.Invalidate();
OnSpecialGroupChanged(new ExpandoEventArgs(this));
}
}
#endregion
#region State
/// <summary>
/// The Collapsed property determines whether the Expando is collapsed.
/// </summary>
[Bindable(true),
Category("Appearance"),
DefaultValue(false),
Description("The Collapsed property determines whether the Expando is collapsed.")]
public bool Collapsed
{
get
{
return this.collapsed;
}
set
{
if (this.collapsed != value)
{
// don't bother if we're not enabled
if (!this.Enabled)
{
return;
}
// if we're supposed to collapse, check if we can
if (value && !this.CanCollapse)
{
// looks like we can't so time to bail
return;
}
this.collapsed = value;
// if we don't have a taskpane or the taskpane can't
// animate then we'd better expand/collapse by ourself
if (this.TaskPane == null || !this.TaskPane.Animate)
{
if (this.collapsed)
{
this.Collapse();
}
else
{
this.Expand();
}
}
// let everyone know we've collapsed/expanded
this.OnStateChanged(new ExpandoEventArgs(this));
}
}
}
/// <summary>
/// Specifies whether the title bar is in a highlighted state.
/// </summary>
[Browsable(false)]
protected FocusStates FocusState
{
get
{
return this.focusState;
}
set
{
if (this.focusState != value)
{
this.focusState = value;
this.InvalidateTitleBar();
}
}
}
/// <summary>
/// Gets or sets whether the Expando is able to collapse
/// </summary>
[Bindable(true),
Category("Behavior"),
DefaultValue(true),
Description("The CanCollapse property determines whether the Expando is able to collapse.")]
public bool CanCollapse
{
get
{
return this.canCollapse;
}
set
{
this.canCollapse = value;
}
}
#endregion
#region System Settings
/// <summary>
/// System settings for the Expando
/// </summary>
[Browsable(false)]
public ExplorerBarInfo SystemSettings
{
set
{
// make sure we have a new value
if (this.systemSettings != value)
{
// get rid of the old settings
if (this.systemSettings != null)
{
this.systemSettings.Dispose();
this.systemSettings = null;
}
// set the new settings
this.systemSettings = value;
}
if (this.SpecialGroup)
{
this.BackColor = this.systemSettings.Expando.SpecialBackColor;
}
else
{
this.BackColor = this.systemSettings.Expando.NormalBackColor;
}
// update the system settings for each TaskItem
for (int i=0; i<this.itemList.Count; i++)
{
Control control = (Control) this.itemList[i];
if (control is TaskItem)
{
((TaskItem) control).SystemSettings = this.systemSettings;
}
}
// if our parent is not an TaskPane then re-layout the
// Expando (don't need to do this if our parent is a
// TaskPane as it will tell us when to do it)
if (this.TaskPane == null)
{
this.DoLayout();
}
}
}
#endregion
#region TaskPane
/// <summary>
/// The TaskPane the Expando belongs to
/// </summary>
[Browsable(false)]
public TaskPane TaskPane
{
get
{
return this.taskpane;
}
set
{
this.taskpane = value;
if (value != null)
{
this.SystemSettings = this.TaskPane.SystemSettings;
}
}
}
#endregion
#region Text
/// <summary>
/// Gets or sets the text displayed on the titlebar
/// </summary>
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
this.InvalidateTitleBar();
}
}
#endregion
#region Visible
/// <summary>
/// Gets or sets a value indicating whether the Expando is displayed
/// </summary>
public new bool Visible
{
get
{
return base.Visible;
}
set
{
if (base.Visible != value)
{
base.Visible = value;
if (this.TaskPane != null)
{
this.TaskPane.DoLayout();
}
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the control can
/// respond to user interaction
/// </summary>
public new bool Enabled
{
get
{
return base.Enabled;
}
set
{
if (base.Enabled != value && !this.Animating)
{
base.Enabled = value;
}
}
}
#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)
{
// don't do anything if we are animating
// (as we're probably the ones who added the control)
if (this.Animating)
{
return;
}
base.OnControlAdded(e);
// check if the expando was added by dragging it onto
// the taskpane instead of being added to the expando
// collection during design time
if (this.DesignMode && !this.Items.Contains(e.Control))
{
this.Items.Add(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)
{
// don't do anything if we are animating
// (as we're probably the ones who removed the control)
if (this.Animating)
{
return;
}
base.OnControlRemoved(e);
// remove the control from the itemList
if (this.Items.Contains(e.Control))
{
this.Items.Remove(e.Control);
}
// update the layout of the controls
this.DoLayout();
}
#endregion
#region Expando
/// <summary>
/// Raises the StateChanged event
/// </summary>
/// <param name="e">An ExpandoEventArgs that contains the event data</param>
protected virtual void OnStateChanged(ExpandoEventArgs e)
{
if (StateChanged != null)
{
StateChanged(e);
}
}
/// <summary>
/// Raises the TitleImageChanged event
/// </summary>
/// <param name="e">An ExpandoEventArgs that contains the event data</param>
protected virtual void OnTitleImageChanged(ExpandoEventArgs e)
{
if (TitleImageChanged != null)
{
TitleImageChanged(e);
}
}
/// <summary>
/// Raises the SpecialGroupChanged event
/// </summary>
/// <param name="e">An ExpandoEventArgs that contains the event data</param>
protected virtual void OnSpecialGroupChanged(ExpandoEventArgs e)
{
if (SpecialGroupChanged != null)
{
SpecialGroupChanged(e);
}
}
#endregion
#region Items
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected virtual void OnItemAdded(ControlEventArgs e)
{
// add the expando to the ControlCollection if it hasn't already
if (!this.Controls.Contains(e.Control))
{
this.Controls.Add(e.Control);
}
// check if the control is a TaskItem
if (e.Control is TaskItem)
{
TaskItem item = (TaskItem) e.Control;
// set anchor styles
item.Anchor = (AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right);
// tell the TaskItem who's its daddy...
item.Expando = this;
item.SystemSettings = this.systemSettings;
}
// update the layout of the controls
this.DoLayout();
//
if (ItemAdded != null)
{
ItemAdded(this, e);
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected virtual void OnItemRemoved(ControlEventArgs e)
{
// remove the control from the ControlCollection if it hasn't already
if (this.Controls.Contains(e.Control))
{
this.Controls.Remove(e.Control);
}
// update the layout of the controls
this.DoLayout();
//
if (ItemRemoved != null)
{
ItemRemoved(this, e);
}
}
#endregion
#region Mouse
/// <summary>
/// Raises the MouseUp event
/// </summary>
/// <param name="e">A MouseEventArgs that contains the event data</param>
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
// was it the left mouse button
if (e.Button == MouseButtons.Left)
{
// was it in the titlebar area
if (e.Y < this.HeaderHeight && e.Y > (this.HeaderHeight - this.TitleBarHeight))
{
// make sure that our taskPane (if we have one) is not animating
if (this.TaskPane == null || !this.TaskPane.IsAnimating)
{
// collapse/expand the group
this.Collapsed = !this.Collapsed;
}
}
}
}
/// <summary>
/// Raises the MouseDown event
/// </summary>
/// <param name="e">A MouseEventArgs that contains the event data</param>
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// we're not doing anything here yet...
// but we might later :)
}
/// <summary>
/// Raises the MouseMove event
/// </summary>
/// <param name="e">A MouseEventArgs that contains the event data</param>
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// check if the mouse is moving in the titlebar area
if (e.Y < this.HeaderHeight && e.Y > (this.HeaderHeight - this.TitleBarHeight))
{
// change the cursor to a hand and highlight the titlebar
this.Cursor = Cursors.Hand;
this.FocusState = FocusStates.Mouse;
}
else
{
// reset the titlebar highlight and cursor if they haven't already
this.Cursor = Cursors.Default;
this.FocusState = FocusStates.None;
}
}
/// <summary>
/// Raises the MouseLeave event
/// </summary>
/// <param name="e">An EventArgs that contains the event data</param>
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
// reset the titlebar highlight if it hasn't already
this.FocusState = FocusStates.None;
}
#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)
{
// if we have a transparent backgroubd color, get windows
// to paint our background for us
if (this.BackColor == Color.Transparent)
{
base.OnPaintBackground(e);
}
// we have a solid background color, but the titlebar back image
// might have treansparent bits, so instead we draw our own
// transparent background (rather than getting windows to draw
// a solid background)
else
{
this.PaintTransparentBackground(e.Graphics, this.ClientRectangle);
}
}
/// <summary>
/// Raises the Paint event
/// </summary>
/// <param name="e">A PaintEventArgs that contains the event data</param>
protected override void OnPaint(PaintEventArgs e)
{
// paint the titlebar
PaintTitleBar(e.Graphics);
// only paint the border and "display rect" if we are not collapsed
if (this.Height != this.headerHeight)
{
PaintBorder(e.Graphics);
PaintDisplayRect(e.Graphics);
}
}
/// <summary>
/// Simulates a transparent background by getting the Expandos parent
/// to paint its background and foreground into the specified Graphics
/// at the specified location
/// </summary>
/// <param name="g">The Graphics used to paint the background</param>
/// <param name="rect">The Rectangle that represents the rectangle
/// in which to paint</param>
protected void PaintTransparentBackground(Graphics g, Rectangle clipRect)
{
// check if we have a parent
if (this.Parent != null)
{
// convert the clipRects coordinates from ours to our parents
clipRect.Offset(this.Location);
PaintEventArgs e = new PaintEventArgs(g, clipRect);
// save the graphics state so that if anything goes wrong
// we're not fubar
GraphicsState state = g.Save();
try
{
// move the graphics object so that we are drawing in
// the correct place
g.TranslateTransform((float) -this.Location.X, (float) -this.Location.Y);
// draw the parents background and foreground
this.InvokePaintBackground(this.Parent, e);
this.InvokePaint(this.Parent, e);
return;
}
finally
{
// reset everything back to where they were before
g.Restore(state);
clipRect.Offset(-this.Location.X, -this.Location.Y);
}
}
// we don't have a parent, so fill the rect with
// the default control color
g.FillRectangle(SystemBrushes.Control, clipRect);
}
/// <summary>
/// Paints the "Display Rectangle". This is the dockable
/// area of the control (ie non-titlebar/border area). This is
/// also the same as the PseudoClientRect.
/// </summary>
/// <param name="g">The Graphics used to paint the DisplayRectangle</param>
protected void PaintDisplayRect(Graphics g)
{
// are we animating a fade
if (this.animatingFade && this.AnimationImage != null)
{
// calculate the transparency value for the animation image
float alpha = (((float) (this.Height - this.HeaderHeight)) / ((float) (this.ExpandedHeight - this.HeaderHeight)));
float[][] ptsArray = {new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, alpha, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix colorMatrix = new ColorMatrix(ptsArray);
ImageAttributes imageAttributes = new ImageAttributes();
imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
// work out how far up the animation image we need to start
int y = this.AnimationImage.Height - this.PseudoClientHeight - this.Border.Bottom;
// draw the image
g.DrawImage(this.AnimationImage,
new Rectangle(0, this.HeaderHeight, this.Width, this.Height - this.HeaderHeight),
0,
y,
this.AnimationImage.Width,
this.AnimationImage.Height - y,
GraphicsUnit.Pixel,
imageAttributes);
}
// are we animating a slide
else if (this.animatingSlide)
{
// just paint the area with a solid brush
using (SolidBrush brush = new SolidBrush(this.BackColor))
{
g.FillRectangle(brush,
this.Border.Left,
this.HeaderHeight + this.Border.Top,
this.Width - this.Border.Left - this.Border.Right,
this.Height - this.HeaderHeight - this.Border.Top - this.Border.Bottom);
}
}
else
{
// just paint the area with a solid brush
using (SolidBrush brush = new SolidBrush(this.BackColor))
{
g.FillRectangle(brush, this.DisplayRectangle);
}
if (this.Watermark != null)
{
// work out a rough location of where the watermark should go
Rectangle rect = new Rectangle(0, 0, this.Watermark.Width, this.Watermark.Height);
rect.X = this.PseudoClientRect.Right - this.Watermark.Width;
rect.Y = this.DisplayRectangle.Bottom - this.Watermark.Height;
// 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
g.DrawImage(this.Watermark, rect);
}
}
}
/// <summary>
/// Paints the title bar
/// </summary>
/// <param name="g">The Graphics used to paint the titlebar</param>
protected void PaintTitleBar(Graphics g)
{
// fix: draw grayscale titlebar when disabled
// Brad Jones (brad@bradjones.com)
// 20/08/2004
// v1.21
int y = 0;
// work out where the top of the titleBar actually is
if (this.HeaderHeight > this.TitleBarHeight)
{
y = this.HeaderHeight - this.TitleBarHeight;
}
// draw the title background image if we have one
if (this.TitleBackImage != null)
{
if (this.Enabled)
{
g.DrawImage(this.TitleBackImage, 0, y, this.Width, this.TitleBarHeight);
}
else
{
// first stretch the background image for ControlPaint.
using (Image image = new Bitmap(this.TitleBackImage, this.Width, this.TitleBarHeight))
{
ControlPaint.DrawImageDisabled(g, image, 0, y, this.TitleBackColor);
}
}
}
else
{
Color c = this.TitleBackColor;
using (SolidBrush brush = new SolidBrush(this.TitleBackColor))
{
g.FillRectangle(brush, 0, y, this.Width, this.TitleBarHeight);
}
}
// draw the titlebar image if we have one
if (this.TitleImage != null)
{
if (this.Enabled)
{
g.DrawImage(this.TitleImage, 0, 0);
}
else
{
ControlPaint.DrawImageDisabled(g, TitleImage, 0, 0, this.TitleBackColor);
}
}
// get which collapse/expand arrow we should draw
Image arrowImage = this.ArrowImage;
// get the titlebar's border and padding
Border border = this.TitleBorder;
Padding padding = this.TitlePadding;
// draw the arrow if we have one
if (arrowImage != null)
{
// work out where to position the arrow
int x = this.Width - arrowImage.Width - border.Right - padding.Right;
y += border.Top + padding.Top;
// draw it...
if (this.Enabled)
{
g.DrawImage(arrowImage, x, y);
}
else
{
ControlPaint.DrawImageDisabled(g, arrowImage, x, y, this.TitleBackColor);
}
}
// check if we have any text to draw in the titlebar
if (this.Text.Length > 0)
{
// a rectangle that will contain our text
Rectangle rect = new Rectangle();
// work out the x coordinate
if (this.TitleImage == null)
{
rect.X = border.Left + padding.Left;
}
else
{
rect.X = this.TitleImage.Width + border.Left;
}
// work out the y coordinate
ContentAlignment alignment = this.TitleAlignment;
switch (alignment)
{
case ContentAlignment.MiddleLeft:
case ContentAlignment.MiddleCenter:
case ContentAlignment.MiddleRight: rect.Y = ((this.HeaderHeight - this.TitleFont.Height) / 2) + ((this.HeaderHeight - this.TitleBarHeight) / 2) + border.Top + padding.Top;
break;
case ContentAlignment.TopLeft:
case ContentAlignment.TopCenter:
case ContentAlignment.TopRight: rect.Y = (this.HeaderHeight - this.TitleBarHeight) + border.Top + padding.Top;
break;
case ContentAlignment.BottomLeft:
case ContentAlignment.BottomCenter:
case ContentAlignment.BottomRight: rect.Y = this.HeaderHeight - this.TitleFont.Height;
break;
}
// the height of the rectangle
rect.Height = this.TitleFont.Height;
// make sure the text stays inside the header
if (rect.Bottom > this.HeaderHeight)
{
rect.Y -= rect.Bottom - this.HeaderHeight;
}
// work out how wide the rectangle should be
if (arrowImage != null)
{
rect.Width = this.Width - arrowImage.Width - border.Right - padding.Right - rect.X;
}
else
{
rect.Width = this.Width - border.Right - padding.Right - rect.X;
}
// don't wrap the string, and use an ellipsis if
// the string is too big to fit the rectangle
StringFormat sf = new StringFormat();
sf.FormatFlags = StringFormatFlags.NoWrap;
sf.Trimming = StringTrimming.EllipsisCharacter;
// should the string be aligned to the left/center/right
switch (alignment)
{
case ContentAlignment.MiddleLeft:
case ContentAlignment.TopLeft:
case ContentAlignment.BottomLeft: sf.Alignment = StringAlignment.Near;
break;
case ContentAlignment.MiddleCenter:
case ContentAlignment.TopCenter:
case ContentAlignment.BottomCenter: sf.Alignment = StringAlignment.Center;
break;
case ContentAlignment.MiddleRight:
case ContentAlignment.TopRight:
case ContentAlignment.BottomRight: sf.Alignment = StringAlignment.Far;
break;
}
// draw the text
using (SolidBrush brush = new SolidBrush(this.TitleColor))
{
//g.DrawString(this.Text, this.TitleFont, brush, rect, sf);
if (this.Enabled)
{
g.DrawString(this.Text, this.TitleFont, brush, rect, sf);
}
else
{
ControlPaint.DrawStringDisabled(g, this.Text, this.TitleFont, SystemColors.ControlLightLight, rect, sf);
}
}
}
}
/// <summary>
/// Paints the borders
/// </summary>
/// <param name="g">The Graphics used to paint the border</param>
protected void PaintBorder(Graphics g)
{
// get the current border and border colors
Border border = this.Border;
Color c = this.BorderColor;
// check if we are currently animating a fade
if (this.animatingFade)
{
// calculate the alpha value for the color
int alpha = (int) (255 * (((float) (this.Height - this.HeaderHeight)) / ((float) (this.ExpandedHeight - this.HeaderHeight))));
// make sure it doesn't go past 0 or 255
if (alpha < 0)
{
alpha = 0;
}
else if (alpha > 255)
{
alpha = 255;
}
// update the color with the alpha value
c = Color.FromArgb(alpha, c.R, c.G, c.B);
}
// draw the borders
using (SolidBrush brush = new SolidBrush(c))
{
g.FillRectangle(brush, border.Left, this.HeaderHeight, this.Width-border.Left-border.Right, border.Top); // top border
g.FillRectangle(brush, 0, this.HeaderHeight, border.Left, this.Height-this.HeaderHeight); // left border
g.FillRectangle(brush, this.Width-border.Right, this.HeaderHeight, border.Right, this.Height-this.HeaderHeight); // right border
g.FillRectangle(brush, border.Left, this.Height-border.Bottom, this.Width-border.Left-border.Right, border.Bottom); // bottom border
}
}
#endregion
#region Size
/// <summary>
/// Raises the SizeChanged event
/// </summary>
/// <param name="e">An EventArgs that contains the event data</param>
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
// if we are currently animating and the width of the
// group has changed (eg. due to a scrollbar on the
// TaskPane appearing/disappearing), get a new image
// to use for the animation. (if we were to continue to
// use the old image it would be shrunk or stretched making
// the animation look wierd)
if (this.Animating && this.Width != this.oldWidth)
{
this.oldWidth = this.Width;
if (this.AnimationImage != null)
{
// get the new animationImage
this.animationImage = this.GetFadeAnimationImage();
}
}
// check if the width has changed. if it has re-layout
// the group so that the TaskItems can resize themselves
// if neccessary
else if (this.Width != this.oldWidth)
{
this.oldWidth = this.Width;
this.DoLayout();
}
}
#endregion
#endregion
#region AnimationPanel
/// <summary>
/// An extremely stripped down version of an Expando that an
/// Expando can use instead of itself to get an image of its
/// "client area" and child controls
/// </summary>
internal class AnimationPanel : Panel
{
#region Class Data
/// <summary>
/// The height of the header section
/// (includes titlebar and title image)
/// </summary>
protected int headerHeight;
/// <summary>
/// The border around the "client area"
/// </summary>
protected Border border;
/// <summary>
/// The background image displayed in the control
/// </summary>
protected Image backImage;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the AnimationPanel class with default settings
/// </summary>
public AnimationPanel() : base()
{
this.headerHeight = 0;
this.border = new Border();
this.backImage = null;
}
#endregion
#region Properties
/// <summary>
/// Overrides AutoScroll to disable scrolling
/// </summary>
public new bool AutoScroll
{
get
{
return false;
}
set
{
}
}
/// <summary>
/// Gets or sets the height of the header section of the Expando
/// </summary>
public int HeaderHeight
{
get
{
return this.headerHeight;
}
set
{
this.headerHeight = value;
}
}
/// <summary>
/// Gets or sets the border around the "client area"
/// </summary>
public Border Border
{
get
{
return this.border;
}
set
{
this.border = value;
}
}
/// <summary>
/// Gets or sets the background image displayed in the control
/// </summary>
public Image BackImage
{
get
{
return this.backImage;
}
set
{
this.backImage = value;
}
}
/// <summary>
/// Overrides DisplayRectangle so that docked controls
/// don't cover the titlebar or borders
/// </summary>
public override Rectangle DisplayRectangle
{
get
{
return new Rectangle(this.Border.Left,
this.HeaderHeight + this.Border.Top,
this.Width - this.Border.Left - this.Border.Right,
this.Height - this.HeaderHeight - this.Border.Top - this.Border.Bottom);
}
}
#endregion
#region Events
/// <summary>
/// Raises the Paint event
/// </summary>
/// <param name="e">A PaintEventArgs that contains the event data</param>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (this.BackImage != null)
{
e.Graphics.DrawImageUnscaled(this.BackImage, 0, 0);
}
}
#endregion
}
#endregion
#region ItemCollection
/// <summary>
/// Represents a collection of Control objects
/// </summary>
public class ItemCollection : CollectionBase
{
#region Class Data
/// <summary>
/// The Expando that owns this ControlCollection
/// </summary>
private Expando owner;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the Expando.ItemCollection class
/// </summary>
/// <param name="owner">An Expando representing the expando that owns
/// the Control collection</param>
public ItemCollection(Expando owner) : base()
{
if (owner == null)
{
throw new ArgumentNullException("owner");
}
this.owner = owner;
}
#endregion
#region Methods
/// <summary>
/// Adds the specified control to the control collection
/// </summary>
/// <param name="value">The Control to add to the control collection</param>
public void Add(Control value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.List.Add(value);
this.owner.Controls.Add(value);
this.owner.OnItemAdded(new ControlEventArgs(value));
}
/// <summary>
/// Adds an array of control objects to the collection
/// </summary>
/// <param name="expandos">An array of Control objects to add
/// to the collection</param>
public void AddRange(Control[] controls)
{
if (controls == null)
{
throw new ArgumentNullException("controls");
}
for (int i=0; i<controls.Length; i++)
{
this.Add(controls[i]);
}
}
/// <summary>
/// Removes all controls from the collection
/// </summary>
public new void Clear()
{
while (this.Count > 0)
{
this.RemoveAt(0);
}
}
/// <summary>
/// Determines whether the specified control is a member of the
/// collection
/// </summary>
/// <param name="expandos">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 == null)
{
throw new ArgumentNullException("control");
}
return (this.IndexOf(control) != -1);
}
/// <summary>
/// Retrieves the index of the specified control in the control
/// collection
/// </summary>
/// <param name="expando">The Control to locate in the collection</param>
/// <returns>A zero-based index value that represents the position
/// of the specified Control in the Expando.ItemCollection</returns>
public int IndexOf(Control control)
{
if (control == null)
{
throw new ArgumentNullException("control");
}
for (int i=0; i<this.Count; i++)
{
if (this[i] == control)
{
return i;
}
}
return -1;
}
/// <summary>
/// Removes the specified control from the control collection
/// </summary>
/// <param name="value">The Control to remove from the
/// Expando.ItemCollection</param>
public void Remove(Control value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.List.Remove(value);
this.owner.Controls.Remove(value);
this.owner.OnItemRemoved(new ControlEventArgs(value));
}
/// <summary>
/// Removes a control from the control collection at the
/// specified indexed location
/// </summary>
/// <param name="index">The index value of the Control to
/// remove</param>
public new void RemoveAt(int index)
{
this.Remove(this[index]);
}
/// <summary>
/// Moves the specified control to the specified indexed location
/// in the control collection
/// </summary>
/// <param name="expando">The control to be moved</param>
/// <param name="index">The indexed location in the control collection
/// that the specified control will be moved to</param>
public void Move(Control 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 control to the top of the control collection
/// </summary>
/// <param name="value">The control to be moved</param>
public void MoveToTop(Control value)
{
this.Move(value, 0);
}
/// <summary>
/// Moves the specified control to the bottom of the control collection
/// </summary>
/// <param name="value">The control to be moved</param>
public void MoveToBottom(Control value)
{
this.Move(value, this.Count);
}
#endregion
#region Properties
/// <summary>
/// The Control located at the specified index location within
/// the control collection
/// </summary>
/// <param name="index">The index of the control to retrieve
/// from the control collection</param>
public virtual Control this[int index]
{
get
{
return this.List[index] as Control;
}
}
#endregion
}
#endregion
#region ItemCollectionEditor
/// <summary>
///
/// </summary>
internal class ItemCollectionEditor : CollectionEditor
{
/// <summary>
/// Initializes a new instance of the CollectionEditor class
/// using the specified collection type
/// </summary>
/// <param name="type"></param>
public ItemCollectionEditor(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)
{
Expando originalControl = (Expando) context.Instance;
object returnObject = base.EditValue(context, isp, value);
originalControl.UpdateItems();
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)
{
// ignore the type of item we're supposed to create
// and assume the user wants to create a taskitem
object item = base.CreateInstance(typeof(TaskItem));
((TaskItem) item).Name = "taskitem";
return item;
}
}
#endregion
}
#endregion
#region ExpandoEventArgs
/// <summary>
/// Summary description for ExpandoEventArgs.
/// </summary>
public class ExpandoEventArgs : EventArgs
{
#region Class Data
/// <summary>
/// The Expando thet generated the event
/// </summary>
private Expando expando;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the ExpandoEventArgs class with default settings
/// </summary>
public ExpandoEventArgs()
{
expando = null;
}
/// <summary>
/// Initializes a new instance of the ExpandoEventArgs class with specific Expando
/// </summary>
/// <param name="expando"></param>
public ExpandoEventArgs(Expando expando)
{
this.expando = expando;
}
#endregion
#region Properties
/// <summary>
/// Gets the Expando that generated the event
/// </summary>
public Expando Expando
{
get
{
return this.expando;
}
}
/// <summary>
/// Gets whether the Expando is collapsed
/// </summary>
public bool Collapsed
{
get
{
return this.expando.Collapsed;
}
}
#endregion
}
#endregion
#region ExpandoConverter
/// <summary>
///
/// </summary>
internal class ExpandoConverter : TypeConverter
{
/// <summary>
/// Returns whether this converter can convert the object to the
/// specified type, using the specified context
/// </summary>
/// <param name="context">An ITypeDescriptorContext that provides a
/// format context</param>
/// <param name="destinationType">A Type that represents the type
/// you want to convert to</param>
/// <returns>true if this converter can perform the conversion; o
/// therwise, false</returns>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
/// <summary>
/// Converts the given value object to the specified type, using
/// the specified context and culture information
/// </summary>
/// <param name="context">An ITypeDescriptorContext that provides
/// a format context</param>
/// <param name="culture">A CultureInfo object. If a null reference
/// is passed, the current culture is assumed</param>
/// <param name="value">The Object to convert</param>
/// <param name="destinationType">The Type to convert the value
/// parameter to</param>
/// <returns>An Object that represents the converted value</returns>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor) && value is Expando)
{
ConstructorInfo ci = typeof(Expando).GetConstructor(new Type[] {});
if (ci != null)
{
return new InstanceDescriptor(ci, null, false);
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
#endregion
#region ExpandoDesigner
/// <summary>
/// Summary description for ExpandoDesigner.
/// </summary>
public class ExpandoDesigner : ParentControlDesigner
{
/// <summary>
/// Initializes a new instance of the ExpandoDesigner class
/// </summary>
public ExpandoDesigner()
{
}
/// <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(IDictionary properties)
{
base.PreFilterProperties(properties);
//properties.Remove("AutoScroll");
//properties.Remove("AutoScrollMargin");
//properties.Remove("AutoScrollMinSize");
properties.Remove("BackColor");
properties.Remove("BackgroundImage");
properties.Remove("BorderStyle");
properties.Remove("Cursor");
properties.Remove("BackgroundImage");
}
}
#endregion
#region FocusStates
/// <summary>
/// Summary description for FocusState.
/// </summary>
public enum FocusStates
{
/// <summary>
/// Normal state
/// </summary>
None = 0,
/// <summary>
/// The mouse is over the title bar
/// </summary>
Mouse = 1,
/// <summary>
/// Gained focus via the keyboard
/// </summary>
Keyboard = 2
}
#endregion
}