Click here to Skip to main content
15,896,557 members
Articles / Programming Languages / C#

C# List View v1.3

Rate me:
Please Sign up or sign in to vote.
4.90/5 (168 votes)
2 Mar 2004CPOL13 min read 2.1M   55.8K   434  
A fully featured completely managed C# ListView.
using System;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.IO;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections.Specialized;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;




namespace GlacialComponents.Controls
{

	public enum GLGridStyles { gridNone=0, gridDashed=1, gridSolid=2 }

	public enum GLHeaderStyles
	{
		Normal = 0,
		SuperFlat = 1,
		XP = 2
	}



	/// <summary>
	/// Summary description for GlacialList.
	/// </summary>
	public class GlacialList : System.Windows.Forms.Control
	{

		#region Debugging

		public static void DW( string strout )			// debug write
		{
#if false
			//System.IO.StreamWriter sw = new System.IO.StreamWriter( "e:\\debug.txt", true );
			//sw.WriteLine( strout );
			//sw.Close();
#else
			//Debug.WriteLine( strout );
#endif
		}

		private void InitializeComponent()
		{
		
		}

		public static void DI( string strout )			// debug write
		{
#if false
			//System.IO.StreamWriter sw = new System.IO.StreamWriter( "e:\\debug.txt", true );
			//sw.WriteLine( strout );
			//sw.Close();
#else
			//Debug.WriteLine( strout );
#endif
		}

		#endregion

		#region Header

		#region Events and Delegates

		#region Clicked Events

		public delegate void ClickedEventHandler( object source, ClickEventArgs e );//int nItem, int nSubItem );
		public event ClickedEventHandler ColumnClickedEvent;

		#endregion

		#region Changed Events

		public delegate void ChangedEventHandler( object source, ChangedEventArgs e );				//int nItem, int nSubItem );

		public event ChangedEventHandler ItemChangedEvent;
		public event ChangedEventHandler ColumnChangedEvent;

		#endregion
		
		#endregion

		#region VarsDefsProps

		#region Definitions


		public enum WIN32Codes
		{
			WM_GETDLGCODE = 0x0087,
			WM_SETREDRAW = 0x000B,
			WM_CANCELMODE = 0x001F,
			WM_NOTIFY = 0x4e,
			WM_KEYDOWN = 0x100,
			WM_KEYUP = 0x101,
			WM_CHAR = 0x0102,
			WM_SYSKEYDOWN = 0x104,
			WM_SYSKEYUP = 0x105,
			WM_COMMAND = 0x111,
			WM_MENUCHAR = 0x120,
			WM_MOUSEMOVE = 0x200,
			WM_LBUTTONDOWN = 0x201,
			WM_MOUSELAST = 0x20a,
			WM_USER = 0x0400,
			WM_REFLECT = WM_USER + 0x1c00
		}

		public enum DialogCodes
		{
			DLGC_WANTARROWS =     0x0001,
			DLGC_WANTTAB =        0x0002,
			DLGC_WANTALLKEYS =    0x0004,
			DLGC_WANTMESSAGE =    0x0004,
			DLGC_HASSETSEL =      0x0008,
			DLGC_DEFPUSHBUTTON =  0x0010,
			DLGC_UNDEFPUSHBUTTON = 0x0020,
			DLGC_RADIOBUTTON =    0x0040,
			DLGC_WANTCHARS =      0x0080,
			DLGC_STATIC =         0x0100,
			DLGC_BUTTON =         0x2000,
		}

		protected const int WM_KEYDOWN = 0x0100;
		protected const int VK_LEFT = 0x0025;
		protected const int VK_UP = 0x0026;
		protected const int VK_RIGHT = 0x0027;
		protected const int VK_DOWN = 0x0028;


		public enum ListStates { stateNone=0, stateSelecting=1, stateColumnSelect=2, stateColumnResizing=3 }

		const int					RESIZE_ARROW_PADDING = 2;
		const int					MINIMUM_COLUMN_SIZE = 4;

		#endregion

		#region Class Variables

		private int					m_nLastSelectionIndex = 0;
		private int					m_nLastSubSelectionIndex = 0;

		private ListStates			m_nState = ListStates.stateNone;
		private Point				m_pointColumnResizeAnchor;
		private int					m_nResizeColumnNumber;			// the column number thats being resized

		private ArrayList			LiveControls = new ArrayList();		// list of controls currently visible.  THIS IS AN OPTIMIZATION.  This will keep us from having to iterate the entire list beforehand.
		private ArrayList			NewLiveControls = new ArrayList();

		private System.ComponentModel.Container components = null;

		private GlacialComponents.Controls.ManagedVScrollBar vPanelScrollBar;
		private GlacialComponents.Controls.ManagedHScrollBar hPanelScrollBar;


		private BorderStrip vertLeftBorderStrip;
		private BorderStrip vertRightBorderStrip;
		private BorderStrip horiBottomBorderStrip;
		private BorderStrip horiTopBorderStrip;
		private BorderStrip cornerBox;


		#endregion

		#region ClassProperties

		private GLColumnCollection				m_Columns;
		private GLItemCollection				m_Items;
		

		// border
		private bool							m_bShowBorder = true;

		private GLGridStyles					m_GridLineStyle = GLGridStyles.gridSolid;
		private int								m_nItemHeight = 18;
		private int								m_nHeaderHeight = 22;
		private int								m_nBorderWidth = 2;
		private Color							m_colorGridColor = Color.Gray;
		private bool							m_bMultiSelect = false;
		private Color							m_colorSelectionColor = Color.DarkBlue;
		private bool							m_bHeaderVisible = true;
		private ImageList						m_ImageList = null;								// if it doesnt exist, then don't make it yet.

		private Color							m_SelectedTextColor = Color.White;

		private int								m_nMaxHeight = 0;
		private bool							m_bAutoHeight = true;
		private bool							m_bAllowColumnResize = true;
		private bool							m_bFullRowSelect = true;
		private bool							m_bAutoSort = true;

		private GLItem							m_FocusedItem = null;
		private bool							m_bShowFocusRect = false;

		private bool							m_bHotTracking = false;
		private int								m_nHotColumnIndex = -1;							// internal hot column
		private int								m_nHotItemIndex = -1;							// internal hot item index
		private Color							m_HotTrackingColor = Color.LightGray;			// brush color to use

		//private bool							m_bXPTheme = true;								// turn xp themes on for the control


		private bool							m_bAlternatingColors = false;
		private Color							m_colorAlternateBackground = Color.DarkGreen;
		private Color							m_colorSuperFlatHeaderColor = Color.White;

		private GLHeaderStyles					m_HeaderStyle = GLHeaderStyles.Normal;

		private bool							m_bItemWordWrap = false;
		private bool							m_bHeaderWordWrap = false;


		#region Control Properties



		/// <summary>
		/// Word wrap in header
		/// </summary>
		[
		Description("Word wrap in header"),
		Category("Header"),
		Browsable(true)
		]
		public bool HeaderWordWrap
		{
			get	{ return m_bHeaderWordWrap; }
			set { m_bHeaderWordWrap = value; }
		}


		/// <summary>
		/// Word wrap in cells
		/// </summary>
		[
		Description("Word wrap in cells"),
		Category("Item"),
		Browsable(true)
		]
		public bool ItemWordWrap
		{
			get	{ return m_bItemWordWrap; }
			set { m_bItemWordWrap = value; }
		}



		/// <summary>
		/// background color to use if flat
		/// </summary>
		[
		Description("Color for text in boxes that are selected."),
		Category("Header"),
		Browsable(true)
		]
		public Color SuperFlatHeaderColor
		{
			get	{ return m_colorSuperFlatHeaderColor; }
			set { m_colorSuperFlatHeaderColor = value; }
		}


		/// <summary>
		/// Allows for more custom header styles
		/// </summary>
		[
		Description("Allows for more custom header styles"),
		Category("Header"),
		Browsable(true)
		]
		public GLHeaderStyles HeaderStyle
		{
			get	{ return m_HeaderStyle; }
			set
			{
				m_HeaderStyle = value;
				DI("Calling Invalidate from HeaderStyle Property");
				Invalidate();
			}
		}


		/// <summary>
		/// Alternating Colors on or off
		/// </summary>
		[
		Description("turn xp themes on or not"),
		Category("Item Alternating Colors"),
		Browsable(true)
		]
		public bool AlternatingColors
		{
			get	{ return m_bAlternatingColors; }
			set { m_bAlternatingColors = value; }
		}


		/// <summary>
		/// second background color if we use alternating colors
		/// </summary>
		[
		Description("Color for text in boxes that are selected."),
		Category("Item Alternating Colors"),
		Browsable(true)
		]
		public Color AlternateBackground
		{
			get	{ return m_colorAlternateBackground; }
			set { m_colorAlternateBackground = value; }
		}


		/// <summary>
		/// Whether or not to show a border.
		/// </summary>
		[
		Description("Whether or not to show a border."),
		Category("Appearance"),
		Browsable(true),
		]
		public bool ShowBorder
		{
			get	{ return m_bShowBorder; }
			set { m_bShowBorder = value; }
		}


		/// <summary>
		/// Color for text in boxes that are selected
		/// </summary>
		[
		Description("Color for text in boxes that are selected."),
		Category("Item"),
		Browsable(true)
		]
		public Color SelectedTextColor
		{
			get	{ return m_SelectedTextColor; }
			set { m_SelectedTextColor = value; }
		}


		/// <summary>
		/// hot tracking
		/// </summary>
		[
		Description("Color for hot tracking."),
		Category("Appearance"),
		Browsable(true)
		]
		public Color HotTrackingColor
		{
			get	{ return m_HotTrackingColor; }
			set { m_HotTrackingColor = value; }
		}


		/// <summary>
		/// Hot Tracking of columns and items
		/// </summary>
		[
		Description("Show hot tracking."),
		Category("Behavior"),
		Browsable(true)
		]
		public bool HotTracking
		{
			get	{ return m_bHotTracking; }
			set { m_bHotTracking = value; }
		}


		/// <summary>
		/// Show the focus rect or not
		/// </summary>
		[
		Description("Show Focus Rect on items."),
		Category("Item"),
		Browsable(true)
		]
		public bool ShowFocusRect
		{
			get	{ return m_bShowFocusRect; }
			set { m_bShowFocusRect = value; }
		}


		/// <summary>
		/// auto sorting
		/// </summary>
		[
		Description("Autosort items as they are added."),
		Category("Item"),
		Browsable(true),
		]
		public bool AutoSort
		{
			get	{ return m_bAutoSort; }
			set { m_bAutoSort = value; }
		}


		/// <summary>
		/// 
		/// </summary>
		[
		Description("ImageList to be used in listview."),
		Category("Behavior"),
		Browsable(true),
		]
		public ImageList ImageList
		{
			get	{ return m_ImageList; }
			set { m_ImageList = value; }
		}


		/// <summary>
		/// Allow columns to be resized
		/// </summary>
		[
		Description("Allow resizing of columns"),
		Category("Header"),
		Browsable(true)
		]
		public bool AllowColumnResize
		{
			get { return m_bAllowColumnResize; }
			set { m_bAllowColumnResize = value; }
		}


		/// <summary>
		/// Control resizes height of row based on size.
		/// </summary>
		[
		Description("Do we want rows to automatically adjust height"),
		Category("Item"),
		Browsable(true)
		]
		public bool AutoHeight
		{
			get { return m_bAutoHeight; }
			set { m_bAutoHeight = value; }
		}


		/// <summary>
		/// you want the header to be visible or not
		/// </summary>
		[
		Description("Column Headers Visible"),
		Category("Header"),
		Browsable(true)
		]
		public bool HeaderVisible
		{
			get { return m_bHeaderVisible; }
			set { m_bHeaderVisible = value; }
		}


		/// <summary>
		/// Collection of columns
		/// </summary>
		[
		Category("Header"),
		Description("Column Collection"),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		Editor(typeof(CustomCollectionEditor), typeof(UITypeEditor)),
		Browsable(true)
		]
		public GLColumnCollection Columns
		{
			get	{ return m_Columns; }
		}


		[
		Category("Behavior"),
		Description("Items collection"),
		Browsable(false)
		]
		public GLItemCollection Items
		{
			get	{ return m_Items; }
		}


		/// <summary>
		/// selection bar color
		/// </summary>
		[
		Description("Background color to mark selection."),
		Category("Item"),
		Browsable(true),
		]
		public Color SelectionColor
		{
			get	{ return m_colorSelectionColor; }
			set { m_colorSelectionColor = value; }
		}


		[
		Description("Allow full row select."),
		Category("Item"),
		Browsable(true)
		]
		public bool FullRowSelect
		{
			get	{ return m_bFullRowSelect; }
			set	{ m_bFullRowSelect = value; }
		}


		[
		Description("Allow multiple selections."),
		Category("Item"),
		Browsable(true)
		]
		public bool AllowMultiselect
		{
			get	{ return m_bMultiSelect; }
			set	{ m_bMultiSelect = value; }
		}


		[
		Description("Border Padding"),
		Category("Appearance"),
		Browsable(false),
		DefaultValue(2)
		]
		public int BorderPadding
		{
			get	
			{ 
				if ( ShowBorder )
					return 2; 
				else
					return 0;
			}
			set	{ m_nBorderWidth = value; }
		}


		[
		Description("Whether or not to draw gridlines"),
		Category("Grid"),
		Browsable(true)
		]
		public GLGridStyles GLGridLines
		{
			get	{ return m_GridLineStyle; }
			set
			{
				m_GridLineStyle = value;
				DI("Calling Invalidate From GLGridStyles");
				Invalidate();
			}
		}


		[
		Description("Color of the grid if we draw it."),
		Category("Grid"),
		Browsable(true)
		]
		public Color GridColor
		{
			get	{ return m_colorGridColor; }
			set
			{
				m_colorGridColor = (Color)value;
				DI("Calling Invalidate From GridColor");
				Invalidate();
			}
		}


		/// <summary>
		/// how big do we want the individual items to be
		/// </summary>
		[
		Description("How high each row is."),
		Category("Item"),
		Browsable(true)
		]
		public int ItemHeight
		{
			get { return m_nItemHeight;	}
			set
			{
				//Debug.WriteLine( "Setting item height to " + value.ToString() );

				//if ( value == 15 )
					//Debug.WriteLine( "stop" );

				m_nItemHeight = value;
				DI("Calling Invalidate From ItemHeight");
				Invalidate();
			}
		}


		[
		Description("How high the columns are."),
		Category("Header"),
		Browsable(true)
		]
		public int HeaderHeight
		{
			get
			{
				if ( HeaderVisible == true )
					return m_nHeaderHeight;
				else
					return 0;
			}
			set
			{
				m_nHeaderHeight = value;
				DI("Calling Invalidate From HeaderHeight");
				Invalidate();
			}
		}


		/// <summary>
		/// amount of space inside any given cell to borders
		/// </summary>
		[
		Description("Cell padding area"),
		Browsable(false)
		]
		public int CellPaddingSize
		{
			get	{ return 2; }			// default I set to 4
		}


		#endregion

		#region Working Properties

		private int m_nSortIndex = 0;

		/// <summary>
		/// Current index we are sorting against
		/// </summary>
		[
		Description("Current index we are sorting against"),
		Browsable(false)
		]
		public int SortIndex
		{
			get 
			{
				return m_nSortIndex;
			}
			set 
			{
				m_nSortIndex = value;
			}
		}




		/// <summary>
		/// currently focused item
		/// </summary>
		[
		Description("Currently Focused Column"),
		Browsable(false)
		]
		public int HotColumnIndex
		{
			get 
			{
				return m_nHotColumnIndex;
			}
			set 
			{
				if ( m_bHotTracking )
					if ( m_nHotColumnIndex != value )
					{
						m_nHotItemIndex = -1;
						m_nHotColumnIndex = value; 

						DI("Calling Invalidate From HotColumnIndex");
						Invalidate();
					}
			}
		}


		/// <summary>
		/// currently focused item
		/// </summary>
		[
		Description("Currently Focused Item"),
		Browsable(false)
		]
		public int HotItemIndex
		{
			get 
			{
				return m_nHotItemIndex;
			}
			set 
			{
				if ( m_bHotTracking )
					if ( m_nHotItemIndex != value )
					{
						m_nHotColumnIndex = -1;
						m_nHotItemIndex = value; 

						DI("Calling Invalidate From HotItemIndex");
						Invalidate();
					}
			}
		}


		/// <summary>
		/// currently focused item
		/// </summary>
		[
		Description("Currently Focused Item"),
		Browsable(false)
		]
		public GLItem FocusedItem
		{
			get 
			{
				if ( !m_bShowFocusRect )				// if they elect not to see the focus rect, then dont show it
					return null;

				return m_FocusedItem; 
			}
			set 
			{
				if ( m_FocusedItem != value )
				{
					m_FocusedItem = value; 
					DI("Calling Invalidate From FocusedItem");
					Invalidate();
				}
			}
		}



		[
		Description("Number of items/rows in the list."),
		Category("Behavior"),
		Browsable(false),
		DefaultValue(0)
		]
		public int Count
		{
			get { return Items.Count; }
		}


		[
		Description("All items together height."),
		Browsable(false)
		]
		public int TotalRowHeight
		{
			get
			{
				return ItemHeight * Items.Count;
			}
		}


		[
		Description("Number of rows currently visible in inner rect."),
		Browsable(false)
		]
		public int VisibleRowsCount
		{
			get	{ return RowsInnerClientRect.Height / ItemHeight; }
		}


		[
		Description("this will always reflect the most height any item line has needed"),
		Browsable(false)
		]
		public int MaxHeight
		{
			get	{ return m_nMaxHeight; }
			set
			{
				if ( value > m_nMaxHeight )
				{
					m_nMaxHeight = value;
					if ( AutoHeight == true )
					{
						ItemHeight = MaxHeight;
						DI("Calling Invalidate From MaxHeight");
						Invalidate();
						DW("Item height set bigger");
					}
				}
			}
		}


		[
		Description("The rectangle of the header inside parent control"),
		Browsable(false)
		]
		public Rectangle HeaderRect
		{
			get	{ return new Rectangle( this.BorderPadding, this.BorderPadding, Width-(this.BorderPadding*2), HeaderHeight ); }
		}


		[
		Description("The rectangle of the client inside parent control"),
		Browsable(false)
		]
		public Rectangle RowsClientRect
		{
			get
			{
				int tmpY = HeaderHeight + BorderPadding;							// size of the header and the top border

				int tmpHeight = Height - HeaderHeight - (BorderPadding*2);

#if false		// it really shouldnt be the business of this routine to deal with only a resize problem
				if ( (tmpHeight % ItemHeight) != 0 )
				{	// the size must be adjusted for the control, this is not the right size
					int nRows = tmpHeight / ItemHeight;
					int controlHeight = (nRows * ItemHeight) + tmpY + (BorderPadding*2);

					this.SetBounds( 0, 0, Width, controlHeight );
					Invalidate(true);

					return RowsClientRect;
				}
#endif
				return new Rectangle( BorderPadding, tmpY, Width-(this.BorderPadding*2), tmpHeight );
			}
		}


		[
		Description("The inner rectangle of the client inside parent control taking scroll bars into account."),
		Browsable(false)
		]
		public Rectangle RowsRect
		{
			get
			{
				Rectangle rect = new Rectangle();

				rect.X = -this.hPanelScrollBar.Value;
				rect.Y = HeaderHeight + BorderPadding;
				rect.Width = Columns.Width;
				rect.Height = this.VisibleRowsCount * ItemHeight;

				return rect;
			}
		}


		[
		Description("The inner rectangle of the client inside parent control taking scroll bars into account."),
		Browsable(false)
		]
		public Rectangle RowsInnerClientRect
		{
			get
			{
				Rectangle innerRect = RowsClientRect;

				innerRect.Width -= vPanelScrollBar.mWidth;				// horizontal bar crosses vertical plane and vice versa
				innerRect.Height -= hPanelScrollBar.mHeight;

				if ( innerRect.Width < 0 )
					innerRect.Width = 0;
				if ( innerRect.Height < 0 )
					innerRect.Height= 0;

				return innerRect;
			}
		}


		#endregion

		#endregion

		#endregion

		#endregion

		#region Implementation

		#region Initialization

		public GlacialList()
		{
			DW("Constructor");

			components = new System.ComponentModel.Container();

			this.TabStop = true;

			m_Columns = new GLColumnCollection();
			m_Columns.ChangedEvent += new GLColumnCollection.ChangedEventHandler( Columns_Changed );				// listen to event changes inside the item

			m_Items = new GLItemCollection( this );
			m_Items.ChangedEvent += new GLItemCollection.ChangedEventHandler( Items_Changed );

			SetStyle(
				ControlStyles.AllPaintingInWmPaint |
				ControlStyles.ResizeRedraw |
				ControlStyles.Opaque |
				ControlStyles.UserPaint | 
				ControlStyles.DoubleBuffer |
				ControlStyles.Selectable | 
				ControlStyles.UserMouse, 
				true
				);

			this.BackColor = SystemColors.ControlLightLight;

			this.hPanelScrollBar = new GlacialComponents.Controls.ManagedHScrollBar();
			this.vPanelScrollBar = new GlacialComponents.Controls.ManagedVScrollBar();


			//
			// Creating borders
			//

			//Debug.WriteLine( "Creating borders" );
			this.vertLeftBorderStrip = new BorderStrip();
			this.vertRightBorderStrip = new BorderStrip();
			this.horiBottomBorderStrip = new BorderStrip();
			this.horiTopBorderStrip = new BorderStrip();
			this.cornerBox = new BorderStrip();



			this.SuspendLayout();
			// 
			// hPanelScrollBar
			// 
			this.hPanelScrollBar.Anchor = System.Windows.Forms.AnchorStyles.None;
			this.hPanelScrollBar.CausesValidation = false;
			this.hPanelScrollBar.Location = new System.Drawing.Point(24, 0);
			this.hPanelScrollBar.mHeight = 16;
			this.hPanelScrollBar.mWidth = 120;
			this.hPanelScrollBar.Name = "hPanelScrollBar";
			this.hPanelScrollBar.Size = new System.Drawing.Size(120, 16);
			this.hPanelScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.hPanelScrollBar_Scroll);
			this.hPanelScrollBar.Parent = this;
			this.Controls.Add( hPanelScrollBar );

			// 
			// vPanelScrollBar
			// 
			this.vPanelScrollBar.Anchor = System.Windows.Forms.AnchorStyles.None;
			this.vPanelScrollBar.CausesValidation = false;
			this.vPanelScrollBar.Location = new System.Drawing.Point(0, 12);
			this.vPanelScrollBar.mHeight = 120;
			this.vPanelScrollBar.mWidth = 16;
			this.vPanelScrollBar.Name = "vPanelScrollBar";
			this.vPanelScrollBar.Size = new System.Drawing.Size(16, 120);
			this.vPanelScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.vPanelScrollBar_Scroll);
			this.vPanelScrollBar.Parent = this;
			this.Controls.Add( vPanelScrollBar );


			this.horiTopBorderStrip.Parent = this;
			this.horiTopBorderStrip.BorderType = BorderStrip.BorderTypes.btTop;
			this.horiTopBorderStrip.Visible = true;
			this.horiTopBorderStrip.BringToFront();


			//this.horiBottomBorderStrip.BackColor=Color.Black;
			this.horiBottomBorderStrip.Parent = this;
			this.horiBottomBorderStrip.BorderType = BorderStrip.BorderTypes.btBottom;
			this.horiBottomBorderStrip.Visible = true;
			this.horiBottomBorderStrip.BringToFront();

			//this.vertLeftBorderStrip.BackColor=Color.Black;
			this.vertLeftBorderStrip.BorderType = BorderStrip.BorderTypes.btLeft;
			this.vertLeftBorderStrip.Parent = this;
			this.vertLeftBorderStrip.Visible = true;
			this.vertLeftBorderStrip.BringToFront();

			//this.vertRightBorderStrip.BackColor=Color.Black;
			this.vertRightBorderStrip.BorderType = BorderStrip.BorderTypes.btRight;
			this.vertRightBorderStrip.Parent = this;
			this.vertRightBorderStrip.Visible = true;
			this.vertRightBorderStrip.BringToFront();

			this.cornerBox.BackColor = SystemColors.Control;
			this.cornerBox.BorderType = BorderStrip.BorderTypes.btSquare;
			this.cornerBox.Visible = false;
			this.cornerBox.Parent = this;
			this.cornerBox.BringToFront();

			this.Name = "GlacialList";

			this.ResumeLayout(false);
		}


		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			Debug.WriteLine( "Disposing Glacial List." );

			if( disposing )
			{
				if( components != null )
					components.Dispose();
			}
			base.Dispose( disposing );
		}

		#endregion

		#region System Overrides

		/// <summary>
		/// stop keys from going to scrollbars
		/// </summary>
		/// <param name="m"></param>
		protected override void WndProc(ref Message msg)
		{
			base.WndProc(ref msg);
			if (msg.Msg == (int)WIN32Codes.WM_GETDLGCODE)
			{
				msg.Result = new IntPtr((int)DialogCodes.DLGC_WANTCHARS | (int)DialogCodes.DLGC_WANTARROWS | msg.Result.ToInt32());
			}
		}


		/// <summary>
		/// keep certain keys here
		/// </summary>
		/// <param name="msg"></param>
		/// <returns></returns>
		public override bool PreProcessMessage(ref Message msg)
		{
			if (msg.Msg == WM_KEYDOWN)
			{
				Keys keyData = ((Keys) (int) msg.WParam) | ModifierKeys;
				Keys keyCode = ((Keys) (int) msg.WParam);

				if ( keyData == Keys.Escape )
				{
					FocusedItem = null;
				}
				else if ( ( FocusedItem != null ) && ( Count > 0 ) )
				{
					int nItemIndex = Items.FindItemIndex( FocusedItem );
					if ( nItemIndex < 0 )
						return true;							// this can't move

					if (keyData == Keys.Down)
					{
						nItemIndex++;
					}
					else if (keyData == Keys.Up)
					{
						nItemIndex--;
					}
					else if ( keyData == Keys.PageDown )
					{
						nItemIndex+=this.VisibleRowsCount;
					}
					else if ( keyData == Keys.PageUp )
					{
						nItemIndex-=this.VisibleRowsCount;
					}

					// bounds check them
					if ( nItemIndex < 0 )
						nItemIndex = 0;
					if ( nItemIndex > Count-1 )
						nItemIndex = Count-1;

					// move view
					if (nItemIndex < this.vPanelScrollBar.Value)			// its out of viewable, move the surface
						this.vPanelScrollBar.Value = nItemIndex;
					if ( nItemIndex > ( this.vPanelScrollBar.Value+this.VisibleRowsCount ))
						this.vPanelScrollBar.Value = nItemIndex - this.VisibleRowsCount;

					FocusedItem = Items[nItemIndex];
				}
				else
				{	// else we just move the actual list
					if ( this.vPanelScrollBar.Visible )
					{	// only bother if we at least have a scrollbar
						int nCVP = this.vPanelScrollBar.Value;

						if (keyData == Keys.Down)
						{
							if ( ( nCVP + this.VisibleRowsCount ) < Count )
								this.vPanelScrollBar.Value+=1;
						}
						else if (keyData == Keys.Up)
						{
							if ( nCVP > 0 )
								this.vPanelScrollBar.Value-=1;
						}
						else if ( keyData == Keys.PageDown )
						{
							if ( ( nCVP + this.VisibleRowsCount + this.VisibleRowsCount ) < Count )
								this.vPanelScrollBar.Value = nCVP + this.VisibleRowsCount;
							else
								this.vPanelScrollBar.Value = Count - this.VisibleRowsCount;
						}
						else if ( keyData == Keys.PageUp )
						{
							if ( ( nCVP - this.VisibleRowsCount ) > -1 )
								this.vPanelScrollBar.Value = nCVP - this.VisibleRowsCount;
							else
								this.vPanelScrollBar.Value = 0;
						}

						if ( nCVP != this.vPanelScrollBar.Value )
							Invalidate();
					}
				}
			}

			return base.PreProcessMessage(ref msg);
		}
		
		#endregion

		#region Event Handlers

		protected void Items_Changed( object source, ChangedEventArgs e )
		{
			DW("GlacialList::Items_Changed");

			if ( ItemChangedEvent != null )
				ItemChangedEvent( this, e );				// fire the column clicked event

			DI("Calling Invalidate From Items_Changed");
			Invalidate();
		}


		public void Columns_Changed( object source, ChangedEventArgs e )
		{
			DW("Columns_Changed");

			if ( ColumnChangedEvent != null )
				ColumnChangedEvent( this, e );				// fire the column clicked event

			DI("Calling Invalidate From Columns_Changed");
			Invalidate();
		}

		#endregion

		#region HelperFunctions


		/// <summary>
		/// interpret mouse coordinates
		/// </summary>
		/// <param name="nScreenX"></param>
		/// <param name="nScreenY"></param>
		/// <param name="nItem"></param>
		/// <param name="nColumn"></param>
		/// <param name="nState"></param>
		protected void InterpretCoords( int nScreenX, int nScreenY, out int nItem, out int nColumn, out ListStates nState )
		{
			DW("Interpret Coords");

			nState = ListStates.stateNone;
			nColumn = 0;		// compiler forces me to set this since it sometimes wont get set if routine falls through early
			nItem = 0;

			/*
			 * Calculate horizontal subitem
			 */
			int nCurrentX = -hPanelScrollBar.Value;		//GetHScrollPoint();			// offset the starting point by the current scroll point

			foreach ( GLColumn col in Columns )
			{
				if ( (nScreenX > nCurrentX) && (nScreenX < (nCurrentX+col.Width-RESIZE_ARROW_PADDING)) )
				{
					nState = ListStates.stateColumnSelect;

					break;
				}
				if ( (nScreenX > (nCurrentX+col.Width-RESIZE_ARROW_PADDING)) && (nScreenX < (nCurrentX+col.Width+RESIZE_ARROW_PADDING)) )
				{
					if ( AllowColumnResize == true )
						nState = ListStates.stateColumnResizing;

					return;				// no need for this to fall through
				}

				nColumn++;
				nCurrentX += col.Width;
			}

			if ( ( nScreenY >= RowsInnerClientRect.Y ) && ( nScreenY < RowsInnerClientRect.Bottom ) )
			{	// we are in the client area
				Columns.ClearHotStates();

				nItem = ((nScreenY - RowsInnerClientRect.Y) / ItemHeight) + vPanelScrollBar.Value;

				this.HotItemIndex = nItem;

				if ( nItem >= Items.Count )
					nState = ListStates.stateNone;
				else
				{
					nState = ListStates.stateSelecting;

					// handle case of where FullRowSelect is OFF and we click on the second part of a spanned column
					for ( int nSubIndex = 0; nSubIndex < Columns.Count; nSubIndex++ )
					{
						if ( ( nSubIndex + (Items[nItem].SubItems[nSubIndex].Span-1) ) >= nColumn )
						{
							nColumn = nSubIndex;
							return;
						}
					}

				}

				return;
			}
			else
			{
				if ( ( nScreenY >= this.HeaderRect.Y ) && ( nScreenY < this.HeaderRect.Bottom ) )
				{
					this.HotColumnIndex = nColumn;

					if ( ( ( nColumn > -1 ) && ( nColumn < Columns.Count ) ) && (!Columns.AnyPressed() ) )
						if ( Columns[nColumn].State == ColumnStates.csNone)
						{
							Columns.ClearHotStates();
							Columns[nColumn].State = ColumnStates.csHot;
						}
				}
			}


			return;
		}

		/// <summary>
		/// return the X starting point of a particular column
		/// </summary>
		/// <param name="nColumn"></param>
		/// <returns></returns>
		public int GetColumnScreenX( int nColumn )
		{
			DW("Get Column Screen X");

			if ( nColumn >= Columns.Count )
				return 0;

			int nCurrentX = -hPanelScrollBar.Value;//GetHScrollPoint();			// offset the starting point by the current scroll point
			int nColIndex = 0;
			foreach ( GLColumn col in Columns )
			{
				if ( nColIndex >= nColumn )
					return nCurrentX;

				nColIndex++;
				nCurrentX += col.Width;
			}

			return 0;		// this should never happen;
		}


		/// <summary>
		/// Sort a column.
		/// 
		/// Set to virtual so you can write your own sorting
		/// </summary>
		/// <param name="nColumn"></param>
		public virtual void SortColumn( int nColumn )
		{
			Debug.WriteLine( "Column sorting called." );

			if ( Count < 2 )			// nothing to sort
				return;

			this.SortIndex = nColumn;
			Items.Sort();
		}


		#endregion

		#region Dimensions

		protected override void OnResize(EventArgs e)
		{
			DW("GlacialList_Resize");

			//RecalcScroll();

			DI("Calling Invalidate From OnResize");
			Invalidate();
		}


		#endregion

		#region Drawing

		/// <summary>
		/// Paint
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		/// 
		protected override void OnPaint(PaintEventArgs e)
		{
			DW("Paint");

			RecalcScroll();


			Debug.WriteLine( "Redraw called " + DateTime.Now.ToLongTimeString() );
			Graphics g = e.Graphics;

			if ( Columns.Count > 0 )
			{
				int nInsideWidth;
				if ( Columns.Width > HeaderRect.Width )
					nInsideWidth = Columns.Width;
				else
					nInsideWidth = HeaderRect.Width;

				/*
				 * draw header
				 */
				if ( HeaderVisible == true )
				{
					g.SetClip( HeaderRect );
					DrawHeader( g, new Size( HeaderRect.Width, HeaderRect.Height ) );
				}

				/*
				 * draw client area
				 */
				g.SetClip( RowsInnerClientRect );
				DrawRows( g );

				// very optimized way of removing controls that aren't visible anymore without having to iterate the entire items list
				foreach( Control control in LiveControls )
				{
					Debug.WriteLine( "Setting " + control.ToString() + " to hidden." );
					control.Visible = false;					// make sure the controls that aren't visible aren't shown
				}
				LiveControls = NewLiveControls;
				NewLiveControls = new ArrayList();
			}

			g.SetClip( this.ClientRectangle );


		}


		/// <summary>
		/// Draw Header Control
		/// </summary>
		/// <param name="graphicHeader"></param>
		/// <param name="sizeHeader"></param>
		public void DrawHeader( Graphics graphicHeader, /*Bitmap bmpHeader,*/ Size sizeHeader )
		{
			DW("DrawHeader");

			if ( this.HeaderStyle == GLHeaderStyles.SuperFlat )
			{
				SolidBrush brush = new SolidBrush( this.SuperFlatHeaderColor );
				graphicHeader.FillRectangle( brush, HeaderRect );
				brush.Dispose();
			}
			else
			{
				graphicHeader.FillRectangle( SystemBrushes.Control, HeaderRect );
			}


			if ( Columns.Count <= 0 )
				return;

			// draw vertical lines first, then horizontal lines
			int nCurrentX = (-this.hPanelScrollBar.Value) + HeaderRect.X;
			foreach ( GLColumn column in Columns )
			{
				// cull columns that won't be drawn first
				if ( ( nCurrentX + column.Width ) < 0 )
				{
					nCurrentX += column.Width;
					continue;							// skip this column, its not being drawn
				}
				if ( nCurrentX > HeaderRect.Right )		
					return;								// were past the end of the visible column, stop drawing
				
				DrawColumnHeader( graphicHeader, new Rectangle( nCurrentX, HeaderRect.Y, column.Width, HeaderHeight ), column );

				nCurrentX += column.Width;				// move the parser
			}
		}


		/// <summary>
		/// Draw column in header control
		/// </summary>
		/// <param name="graphicsColumn"></param>
		/// <param name="rectColumn"></param>
		/// <param name="column"></param>
		public void DrawColumnHeader( Graphics graphicsColumn, Rectangle rectColumn, GLColumn column )
		{
			DW("DrawColumn");

			if ( this.HeaderStyle == GLHeaderStyles.SuperFlat )
			{
				SolidBrush brush = new SolidBrush( this.SuperFlatHeaderColor );
				graphicsColumn.FillRectangle( brush, rectColumn );
				brush.Dispose();
			}
			else if (( this.HeaderStyle == GLHeaderStyles.XP )&& OPaC.uxTheme.Wrapper.IsAppThemed() )
			{	// this is really the only thing we care about for themeing right now inside the control
				System.IntPtr hDC = graphicsColumn.GetHdc();;
				//Debug.WriteLine("hci is " + this.HotColumnIndex.ToString() );
				Debug.WriteLine("CH rect " + rectColumn.ToString() );

				if ( column.State == ColumnStates.csNone )
				{
					Debug.WriteLine( "Normal" );
					OPaC.uxTheme.Wrapper.DrawBackground( "HEADER", "HEADERITEM", "NORMAL", hDC, rectColumn.X, rectColumn.Y, rectColumn.Right, rectColumn.Bottom, 
						rectColumn.X, rectColumn.Y, rectColumn.Right, rectColumn.Bottom );
				}
				else if ( column.State == ColumnStates.csPressed )
				{
					Debug.WriteLine( "Pressed" );
					OPaC.uxTheme.Wrapper.DrawBackground( "HEADER", "HEADERITEM", "PRESSED", hDC, rectColumn.X, rectColumn.Y, rectColumn.Right, rectColumn.Bottom, 
						rectColumn.X, rectColumn.Y, rectColumn.Right, rectColumn.Bottom );
				}
				else if ( column.State == ColumnStates.csHot )
				{
					Debug.WriteLine( "Hot" );
					OPaC.uxTheme.Wrapper.DrawBackground( "HEADER", "HEADERITEM", "HOT", hDC, rectColumn.X, rectColumn.Y, rectColumn.Right, rectColumn.Bottom, 
						rectColumn.X, rectColumn.Y, rectColumn.Right, rectColumn.Bottom );
				}

				graphicsColumn.ReleaseHdc(hDC);
			}
			else
			{
				if ( column.State != ColumnStates.csPressed )
					ControlPaint.DrawButton( graphicsColumn, rectColumn, ButtonState.Normal );
				else
					ControlPaint.DrawButton( graphicsColumn, rectColumn, ButtonState.Pushed );
			}


			// if there is an image, this routine will RETURN with exactly the space left for everything else after the image is drawn (or not drawn due to lack of space)
			if ( (column.ImageIndex > -1) && (ImageList != null) && (column.ImageIndex < this.ImageList.Images.Count) )
				rectColumn = DrawCellGraphic( graphicsColumn, rectColumn, this.ImageList.Images[ column.ImageIndex ], HorizontalAlignment.Left );

			DrawCellText( graphicsColumn, rectColumn, column.Text, column.TextAlignment, this.ForeColor, false, HeaderWordWrap );

			// dont really need the arrow
//			if ( ( ta > -25 ) && ( column.LastSortState == GLColumn.ColumnSortState.SortedDown ) )
//			{
//				int tay = rectColumn.Y + (( rectColumn.Height - 6 )/2);
//				graphicsColumn.DrawLine( SystemPens.ControlDarkDark, ta, tay, ta + 6, tay + 6 );
//				graphicsColumn.DrawLine( SystemPens.ControlDarkDark, ta + 12, tay, ta + 6, tay + 6 );
//				graphicsColumn.DrawLine( SystemPens.ControlDarkDark, ta, tay, ta + 12, tay );
//			}
		}


		/// <summary>
		/// Draw client rows of list control
		/// </summary>
		/// <param name="bmpRows"></param>
		/// <param name="sizeRows"></param>
		public void DrawRows( Graphics graphicsRows )
		{
			DW("DrawRows");


			SolidBrush brush = new SolidBrush( this.BackColor );
			graphicsRows.FillRectangle( brush, this.RowsClientRect );
			brush.Dispose();

			// determine start item based on whether or not we have a vertical scrollbar present
			int nStartItem;				// which item to start with in this visible pane
			if ( this.vPanelScrollBar.Visible == true )
				nStartItem = this.vPanelScrollBar.Value;
			else
				nStartItem = 0;


			Rectangle rectRow = this.RowsRect;	
			rectRow.Height = ItemHeight;

			/* Draw Rows */
			for ( int nItem = 0; ((nItem < (VisibleRowsCount +1) ) && ((nItem+nStartItem) < Items.Count )); nItem++ )
			{
				DrawRow( graphicsRows, rectRow, this.Items[ nItem+nStartItem ], nItem+nStartItem );
				rectRow.Y += ItemHeight;
			}


			if ( GLGridLines == GLGridStyles.gridSolid )
				DrawGridLines( graphicsRows, this.RowsInnerClientRect );


			// draw hot tracking column
			if ( ( this.HotColumnIndex != -1 ) && ( HotColumnIndex < Columns.Count ) )
			{
				int nXCursor = -this.hPanelScrollBar.Value;
				for ( int nColumnIndex = 0; nColumnIndex < this.HotColumnIndex; nColumnIndex++ )
					nXCursor += Columns[nColumnIndex].Width;

				Color transparentColor = Color.FromArgb( 100, 182, 189, 210 );
				Brush hotBrush = new SolidBrush(transparentColor);

				graphicsRows.FillRectangle( hotBrush, nXCursor, RowsInnerClientRect.Y, Columns[HotColumnIndex].Width+1, RowsInnerClientRect.Height-1 );

				hotBrush.Dispose();
			}

		}


		/// <summary>
		/// Draw Row to screen
		/// </summary>
		/// <param name="graphicsRow"></param>
		/// <param name="rectRow"></param>
		/// <param name="item"></param>
		public void DrawRow( Graphics graphicsRow, Rectangle rectRow, GLItem item, int nItemIndex )
		{
			DW("DrawRow");


			// row background, if its selected, that trumps all, if not then see if we are using alternating colors, if not draw normal
			// note, this can all be overridden by the sub item background property
			if ( item.Selected )
			{
				SolidBrush brushBK;
				brushBK = new SolidBrush( Color.FromArgb( 255, SelectionColor.R, SelectionColor.G, SelectionColor.B ) );


				// *** WARNING *** need to check for full row select here
				if ( !FullRowSelect )
				{	// calculate how far into the control it goes
					int nWidthFR = -this.hPanelScrollBar.Value + Columns.Width;
					graphicsRow.FillRectangle( brushBK, this.RowsInnerClientRect.X, rectRow.Y, nWidthFR, rectRow.Height );
				}
				else
					graphicsRow.FillRectangle( brushBK, this.RowsInnerClientRect.X, rectRow.Y, this.RowsInnerClientRect.Width, rectRow.Height );


				//graphicsRow.FillRectangle( brushBK, rectRow );
				brushBK.Dispose();
			}
			else
			{
				SolidBrush brushBK = null;

				if ( this.AlternatingColors )
				{
					int nACItemIndex = Items.FindItemIndex( item );
					if ( ( nACItemIndex % 2 ) > 0 )
						brushBK = new SolidBrush( this.AlternateBackground );
					else
						brushBK = new SolidBrush( this.BackColor );
				}
				else
					brushBK = new SolidBrush( this.BackColor );

				graphicsRow.FillRectangle( brushBK, this.RowsInnerClientRect.X, rectRow.Y, this.RowsInnerClientRect.Width, rectRow.Height );
				brushBK.Dispose();
			}



			// draw the row of sub items
			int nXCursor = -this.hPanelScrollBar.Value;
			for ( int nSubItem = 0; nSubItem < Columns.Count; nSubItem++ )
			{
				Rectangle rectSubItem = new Rectangle( nXCursor, rectRow.Y, Columns[nSubItem].Width, rectRow.Height );

				// avoid drawing items that are not in the visible region
				if ( ( rectSubItem.Right < 0 ) || ( rectSubItem.Left > this.RowsInnerClientRect.Right ) )
					Debug.Write( "" );
				else
					DrawSubItem( graphicsRow, rectSubItem, item, item.SubItems[nSubItem], nSubItem );

				nXCursor += Columns[nSubItem].Width;
			}


			if ( nItemIndex == this.HotItemIndex )									// handle hot tracking of items
			{
				Color transparentColor = Color.FromArgb( 75, 182, 189, 210 );
				Brush hotBrush = new SolidBrush(transparentColor);

				graphicsRow.FillRectangle( hotBrush, this.RowsInnerClientRect.X, rectRow.Y, this.RowsInnerClientRect.Width, rectRow.Height );

				hotBrush.Dispose();
			}

			if ( FocusedItem == item )												// deal with focus rect
			{
				//ControlPaint.DrawFocusRectangle( graphicsRow, new Rectangle( rectRow.X+1, rectRow.Y+1, rectRow.Width-1, rectRow.Height-1 ) );
				ControlPaint.DrawFocusRectangle( graphicsRow, new Rectangle( this.RowsInnerClientRect.X+1, rectRow.Y+1, this.RowsInnerClientRect.Width-1, rectRow.Height-1 ) );
			}

		}


		/// <summary>
		/// draw sub item
		/// </summary>
		/// <param name="graphicsSubItem"></param>
		/// <param name="rectSubItem"></param>
		/// <param name="subItem"></param>
		/// <param name="nColumn"></param>
		public void DrawSubItem( Graphics graphicsSubItem, Rectangle rectSubItem, GLItem item, GLSubItem subItem, int nColumn )
		{
			DW("DrawSubItem");

			// precheck to make sure this is big enough for the things we want to do inside it
			Rectangle subControlRect = new Rectangle( rectSubItem.X, rectSubItem.Y, rectSubItem.Width, rectSubItem.Height );


			if ( ( subItem.Control != null ) && (!subItem.ForceText ) )
			{	// custom embedded control here
				Control control = subItem.Control;
				Rectangle subrc = new Rectangle( 
					subControlRect.X+this.CellPaddingSize, 
					subControlRect.Y+this.CellPaddingSize, 
					subControlRect.Width-this.CellPaddingSize*2,
					subControlRect.Height-this.CellPaddingSize*2 );

				Type tp = control.GetType();
				PropertyInfo pi = control.GetType().GetProperty( "PreferredHeight" );
				if ( pi != null )
				{
					int PreferredHeight = (int)pi.GetValue( control, null );

					if ( ( (PreferredHeight + this.CellPaddingSize*2)> this.ItemHeight ) && AutoHeight )
						this.ItemHeight = PreferredHeight + this.CellPaddingSize*2;

					subrc.Y = subControlRect.Y + ((subControlRect.Height - PreferredHeight)/2);
				}

				NewLiveControls.Add( control );						// put it in the new list, remove from old list
				if ( LiveControls.Contains( control ) )				// make sure its in the old list first
				{
					LiveControls.Remove( control );			// remove it from list so it doesn't get put down
				}

				if ( control.Bounds.ToString() != subrc.ToString() )
					control.Bounds = subrc;							// this will force an invalidation

				if ( control.Visible != true )
					control.Visible = true;

			}
			else	// not control based
			{
				// if there is an image, this routine will RETURN with exactly the space left for everything else after the image is drawn (or not drawn due to lack of space)
				if ( (subItem.ImageIndex > -1) && (ImageList != null) && (subItem.ImageIndex < this.ImageList.Images.Count) )
					rectSubItem = DrawCellGraphic( graphicsSubItem, rectSubItem, this.ImageList.Images[ subItem.ImageIndex ], subItem.ImageAlignment );


				// deal with text color in a box on whether it is selected or not
				Color textColor;
				if ( item.Selected )
					textColor = this.SelectedTextColor;
				else
					textColor = item.TextColor;

				DrawCellText( graphicsSubItem, rectSubItem, subItem.Text, Columns[nColumn].TextAlignment, textColor, item.Selected, ItemWordWrap );
			}

		}


		/// <summary>
		/// draw the contents of a cell, do not draw any background or associated things
		/// </summary>
		/// <param name="graphicsCell"></param>
		/// <param name="rectCell"></param>
		/// <param name="img"></param>
		/// <param name="alignment"></param>
		/// <returns>
		/// returns the area of the cell that is left for you to put anything else on.
		/// </returns>
		public Rectangle DrawCellGraphic( Graphics graphicsCell, Rectangle rectCell, Image img, HorizontalAlignment alignment )
		{
			int th, ty, tw, tx;

			th = img.Height + (CellPaddingSize*2);
			tw = img.Width + (CellPaddingSize*2);
			MaxHeight = th;										// this will only set if autosize is true

			if ( ( tw > rectCell.Width ) || ( th > rectCell.Height ) )
				return rectCell;					// not enough room to draw the image, bail out

			if ( alignment == HorizontalAlignment.Left )
			{
				ty = rectCell.Y + CellPaddingSize + ((rectCell.Height-th)/2);
				tx = rectCell.X + CellPaddingSize;

				graphicsCell.DrawImage( img, tx, ty );

				// remove the width that we used for the graphic from the cell
				rectCell.Width -= (img.Width + (CellPaddingSize*2));
				rectCell.X += tw;
			}
			else if ( alignment == HorizontalAlignment.Center )
			{
				ty = rectCell.Y + CellPaddingSize + ((rectCell.Height-th)/2);
				tx = rectCell.X + CellPaddingSize + ((rectCell.Width-tw)/2);;

				graphicsCell.DrawImage( img, tx, ty );

				// remove the width that we used for the graphic from the cell
				//rectCell.Width -= (img.Width + (CellPaddingSize*2));
				//rectCell.X += (img.Width + (CellPaddingSize*2));
				rectCell.Width = 0;
			}
			else if ( alignment == HorizontalAlignment.Right )
			{
				ty = rectCell.Y + CellPaddingSize + ((rectCell.Height-th)/2);
				tx = rectCell.Right - tw;

				graphicsCell.DrawImage( img, tx, ty );

				// remove the width that we used for the graphic from the cell
				rectCell.Width -= tw;
			}



			return rectCell;
		}


		/// <summary>
		/// Draw cell text is used by header and cell to draw properly aligned text in subitems.
		/// </summary>
		/// <param name="graphicsCell"></param>
		/// <param name="rectCell"></param>
		/// <param name="strCellText"></param>
		/// <param name="alignment"></param>
		public void DrawCellText( Graphics graphicsCell, Rectangle rectCell, string strCellText, ContentAlignment alignment, Color textColor, bool bSelected, bool bWordWrap  )
		{
			int nInteriorWidth = rectCell.Width - (CellPaddingSize*2);
			int nInteriorHeight = rectCell.Height - (CellPaddingSize*2);

			// deal with text color in a box on whether it is selected or not
			SolidBrush textBrush;
			if ( bSelected )
				textBrush = new SolidBrush( this.SelectedTextColor );
			else
				textBrush = new SolidBrush( textColor );

			// convert property editor friendly alignment to an alignment we can use for strings
			StringFormat sf = new StringFormat();
			sf.Alignment = GLStringHelpers.ConvertContentAlignmentToHorizontalStringAlignment( alignment );
			sf.LineAlignment = GLStringHelpers.ConvertContentAlignmentToVerticalStringAlignment( alignment );

			SizeF measuredSize;
			if ( bWordWrap )
			{
				sf.FormatFlags = 0;	// word wrapping is on by default for drawing
				measuredSize = graphicsCell.MeasureString( strCellText, Font, new Point( BorderPadding, BorderPadding ), sf );
			}
			else
			{	// they aren't word wrapping so we need to put the ...'s where necessary
				sf.FormatFlags = StringFormatFlags.NoWrap;
				measuredSize = graphicsCell.MeasureString( strCellText, Font, new Point( BorderPadding, BorderPadding ), sf );
				if ( measuredSize.Width > nInteriorWidth )		// dont truncate if we are doing word wrap
					strCellText = GLStringHelpers.TruncateString( strCellText, nInteriorWidth, graphicsCell, Font );
			}

			MaxHeight = (int)measuredSize.Height + (BorderPadding*2);													// this will only set if autosize is true
			graphicsCell.DrawString( strCellText, Font, textBrush, rectCell /*rectCell.X+this.BorderPadding, rectCell.Y+this.BorderPadding*/, sf );

			textBrush.Dispose();
		}




		/// <summary>
		/// draw grid lines in row area
		/// </summary>
		/// <param name="RowsDC"></param>
		/// <param name="rect"></param>
		/// <param name="bVertLines"></param>
		public void DrawGridLines( Graphics RowsDC, Rectangle rect )
		{
			DW("DrawGridLines");

			int nStartItem = this.vPanelScrollBar.Value;
			/* Draw Rows */
			int nYCursor = rect.Y;
			//for (int nItem = 0; ((nItem < (VisibleRowsCount +1) ) && ((nItem+nStartItem) < Items.Count )); nItem++ )
			for (int nItem = 0; (nItem < (VisibleRowsCount +1)); nItem++ )
			{	//Debug.WriteLine( "ItemCount " + Items.Count.ToString() + " Item Number " + nItem.ToString() );
				nYCursor += ItemHeight;

				// draw horizontal line
				RowsDC.DrawLine( SystemPens.ControlLight, 0, nYCursor, rect.Width, nYCursor );			// bottom line will always be there
			}

			int nXCursor = -this.hPanelScrollBar.Value;
			for ( int nColumn = 0; nColumn < Columns.Count; nColumn++ )
			{
				// draw vertical line
				nXCursor += Columns[nColumn].Width;
				RowsDC.DrawLine( SystemPens.ControlLight, nXCursor, rect.Y, nXCursor, rect.Bottom );
			}
		}


		#endregion // drawing

		#region Keyboard

		protected override void OnKeyPress(KeyPressEventArgs e)
		{
			e.Handled = true;
		}
		protected override void OnKeyDown(KeyEventArgs e)
		{
			e.Handled = true;
		}

		#endregion

		#region Scrolling

		// Handlers for scrollbars scroll
		protected void OnScroll(object sender, EventArgs e)
		{
			//GenerateColumnRects();
			DI("Calling Invalidate From OnScroll");
			Invalidate();
		}


#if false
		protected void RecalcSubControls()
		{
			for ( int index = 0, nVisibleItem = 0; index < this.Count; index++ )
			{
				// test and see if this is visible or not

				if ( ( index < this.vPanelScrollBar.Value ) || ( index > ( this.vPanelScrollBar.Value + ( this.VisibleRowsCount ) ) ) )
				{
					for ( int subindex = 0; subindex < Columns.Count; subindex++ )
					{
						if ( ( Columns[ subindex ].ControlType != GLColumn.ColumnControlTypes.None ) && ( Items[index].SubItems[ subindex ].Control != null ) )
							Items[index].SubItems[ subindex ].Control.Hide();
					}
				}
				else
				{
					int nXCursor = -this.hPanelScrollBar.Value;

					for ( int subindex = 0; subindex < Columns.Count; subindex++ )
					{
						if ( ( Columns[ subindex ].ControlType != GLColumn.ColumnControlTypes.None ) && ( Items[index].SubItems[ subindex ].Control != null ) )
						{
							Rectangle rectCtrl = new Rectangle( nXCursor, (nVisibleItem*this.ItemHeight) + this.RowsClientRect.Y, Columns[subindex].Width , this.ItemHeight );

							Items[index].SubItems[ subindex ].Control.Bounds = rectCtrl;
							Items[index].SubItems[ subindex ].Control.Show();
						}

						nXCursor += Columns[subindex].Width;
					}

					nVisibleItem++;
				}
			}
		}
#endif

		protected void RecalcScroll( )//Graphics g )
		{
			DW("RecalcScroll");


			// lets add recalc of transparent picture box as well



			//this.SuspendLayout();

			int nSomethingHasGoneVeryWrongSoBreakOut = 0;
			bool bSBChanged;
			do					// this loop is to handle changes and rechanges that happen when oen or the other changes
			{
				DW("Begin scrolbar updates loop");
				bSBChanged = false;

				if ( (Columns.Width > RowsInnerClientRect.Width) && (hPanelScrollBar.Visible == false) )
				{	// total width of all the rows is less than the visible rect
					hPanelScrollBar.mVisible = true;
					hPanelScrollBar.Value = 0;
					bSBChanged = true;

					DI("Calling Invalidate From RecalcScroll");
					Invalidate();

					DW("showing hscrollbar");
				}

				if ( (Columns.Width <= RowsInnerClientRect.Width) && (hPanelScrollBar.Visible == true) )
				{	// total width of all the rows is less than the visible rect
					hPanelScrollBar.mVisible = false;
					hPanelScrollBar.Value = 0;
					bSBChanged = true;
					DI("Calling Invalidate From RecalcScroll");
					Invalidate();

					DW("hiding hscrollbar");
				}

				if ( (TotalRowHeight > RowsInnerClientRect.Height) && (vPanelScrollBar.Visible == false) )
				{  // total height of all the rows is greater than the visible rect
					vPanelScrollBar.mVisible = true;
					hPanelScrollBar.Value = 0;
					bSBChanged = true;
					DI("Calling Invalidate From RecalcScroll");
					Invalidate();

					DW("showing vscrollbar");
				}

				if ( (TotalRowHeight <= RowsInnerClientRect.Height) && (vPanelScrollBar.Visible == true) )
				{	// total height of all rows is less than the visible rect
					vPanelScrollBar.mVisible = false;
					vPanelScrollBar.Value = 0;
					bSBChanged = true;
					DI("Calling Invalidate From RecalcScroll");
					Invalidate();

					DW("hiding vscrollbar");
				}

				DW("End scrolbar updates loop");

				// *** WARNING *** WARNING *** Kludge.  Not sure why this is sometimes hanging.  Fix this.
				if ( ++nSomethingHasGoneVeryWrongSoBreakOut > 4 )
					break;

			} while ( bSBChanged == true );		// this should never really run more than twice


			//Rectangle headerRect = HeaderRect;		// tihs is an optimization so header rect doesnt recalc every time we call it
			Rectangle rectClient = RowsInnerClientRect;

			/*
			 *  now that we know which scrollbars are showing and which aren't, resize the scrollbars to fit those windows
			 */
			if ( vPanelScrollBar.Visible == true )
			{
				vPanelScrollBar.mTop = rectClient.Y;
				vPanelScrollBar.mLeft = rectClient.Right;
				vPanelScrollBar.mHeight = rectClient.Height;
				vPanelScrollBar.mLargeChange = VisibleRowsCount;
				vPanelScrollBar.mMaximum = Count-1;

				if ( ((vPanelScrollBar.Value + VisibleRowsCount ) > Count) )		// catch all to make sure the scrollbar isnt going farther than visible items
				{
					DW("Changing vpanel value");
					vPanelScrollBar.Value = Count - VisibleRowsCount;				// an item got deleted underneath somehow and scroll value is larger than can be displayed
				}
			}

			if ( hPanelScrollBar.Visible == true )
			{
				hPanelScrollBar.mLeft = rectClient.Left;
				hPanelScrollBar.mTop = rectClient.Bottom;
				hPanelScrollBar.mWidth = rectClient.Width;

				hPanelScrollBar.mLargeChange = rectClient.Width;	// this reall is the size we want to move
				hPanelScrollBar.mMaximum = Columns.Width;

				if ( (hPanelScrollBar.Value + hPanelScrollBar.LargeChange) > hPanelScrollBar.Maximum )
				{
					DW("Changing vpanel value");
					hPanelScrollBar.Value = hPanelScrollBar.Maximum - hPanelScrollBar.LargeChange;
				}
			}

#if true
			if ( BorderPadding > 0 ) 
			{
				horiBottomBorderStrip.Bounds = new Rectangle( 0, this.ClientRectangle.Bottom-this.BorderPadding, this.ClientRectangle.Width, this.BorderPadding ) ;		// horizontal bottom picture box
				horiTopBorderStrip.Bounds = new Rectangle( 0, this.ClientRectangle.Top, this.ClientRectangle.Width, this.BorderPadding ) ;		// horizontal bottom picture box

				vertLeftBorderStrip.Bounds = new Rectangle( 0, 0, this.BorderPadding, this.ClientRectangle.Height ) ;		// horizontal bottom picture box
				vertRightBorderStrip.Bounds = new Rectangle( this.ClientRectangle.Right-this.BorderPadding, 0, this.BorderPadding, this.ClientRectangle.Height ) ;		// horizontal bottom picture box
			}
			else
			{
				if ( this.horiBottomBorderStrip.Visible )
					this.horiBottomBorderStrip.Visible = false;
				if ( this.horiTopBorderStrip.Visible )
					this.horiTopBorderStrip.Visible = false;
				if ( this.vertLeftBorderStrip.Visible )
					this.vertLeftBorderStrip.Visible = false;
				if ( this.vertRightBorderStrip.Visible )
					this.vertRightBorderStrip.Visible = false;
			}

			if ( hPanelScrollBar.Visible && vPanelScrollBar.Visible )
			{
				if ( !cornerBox.Visible )
					cornerBox.Visible = true;

				cornerBox.Bounds = new Rectangle( hPanelScrollBar.Right, vPanelScrollBar.Bottom, vPanelScrollBar.Width, hPanelScrollBar.Height );
			}
			else
			{
				if ( cornerBox.Visible )
					cornerBox.Visible = false;
			}
#endif


			//this.ResumeLayout();

			//DW("Exit recalc scroll");
		}


		private void vPanelScrollBar_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e)
		{
			DW("vPanelScrollBar_Scroll");
			//this.Focus();

			DI("Calling Invalidate From vPanelScrollBar_Scroll");
			Invalidate();
		}


		private void hPanelScrollBar_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e)
		{
			DW("hPanelScrollBar_Scroll");
			//this.Focus();

			DI("Calling Invalidate From hPanelScrollBar_Scroll");

			Invalidate();
		}


		#endregion

		#region Mouse


		/// <summary>
		/// OnDoubleclick
		/// 
		/// if someone double clicks on an area, we need to start a control potentially
		/// </summary>
		/// <param name="e"></param>
		protected override void OnDoubleClick(EventArgs e)
		{
			DW("GlacialList.OnDoubleClick");

			Point pointLocalMouse = this.PointToClient( Cursor.Position );

			Debug.WriteLine( "Double Click Called" );
			Debug.WriteLine( "At Cords X " + pointLocalMouse.X.ToString() + " Y " + pointLocalMouse .Y.ToString() );

			base.OnDoubleClick( e );
		}


		/// <summary>
		/// had to put this routine in because of overriden protection level being unchangable
		/// </summary>
		/// <param name="e"></param>
		internal void OnMouseDownFromSubItem( object Sender, MouseEventArgs e )
		{
			DW("OnMouseDownFromSubItem");
			//Debug.WriteLine( "OnMouseDownFromSubItem called " + e.X.ToString() + " " + e.Y.ToString() );
			Point cp = this.PointToClient( new Point( Control.MousePosition.X, Control.MousePosition.Y ) );
			e = new MouseEventArgs( e.Button, e.Clicks, cp.X, cp.Y, e.Delta );
			//Debug.WriteLine( "after " + cp.X.ToString() + " " + cp.Y.ToString() );
			OnMouseDown( e );
		}


		protected override void OnMouseLeave(EventArgs e)
		{
			this.Columns.ClearHotStates();

			base.OnMouseLeave (e);
		}



		/// <summary>
		/// mouse button pressed
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			DW("GlacialList_MouseDown");

			Debug.WriteLine( "Real " + e.X.ToString() + " " + e.Y.ToString() );

			int nItem = 0, nColumn = 0;
			ListStates eState;
			InterpretCoords( e.X, e.Y, out nItem, out nColumn, out eState );



			if ( e.Button == MouseButtons.Right )			// if its the right button then we don't really care till its released
				return;


			//-----------------------------------------------------------------------------------------
			if ( eState == ListStates.stateColumnSelect )										// Column select
			{
				m_nState = ListStates.stateNone;

				Columns[ nColumn ].State = ColumnStates.csPressed;
				if ( ColumnClickedEvent != null )
					ColumnClickedEvent( this, new ClickEventArgs( nItem, nColumn ) );				// fire the column clicked event

				this.SortColumn( nColumn );
				//Invalidate();
				return;
			}
			//---Resizing -----------------------------------------------------------------------------------
			if ( eState == ListStates.stateColumnResizing )										// resizing
			{
				Cursor.Current = Cursors.VSplit;
				m_nState = ListStates.stateColumnResizing;

				m_pointColumnResizeAnchor = new Point( GetColumnScreenX(nColumn), e.Y );		// deal with moving column sizes
				m_nResizeColumnNumber = nColumn;

				return;
			}
			//--Item check, if no items exist go no further--
			//if ( Items.Count == 0 )
			//return;

			//---Items --------------------------------------------------------------------------------------
			if ( eState == ListStates.stateSelecting )
			{	// ctrl based multi select ------------------------------------------------------------

				m_nState = ListStates.stateSelecting;

				this.FocusedItem = Items[nItem];


				if ( (( ModifierKeys & Keys.Control) == Keys.Control ) && ( AllowMultiselect == true ) )
				{
					m_nLastSelectionIndex = nItem;

					if ( Items[nItem].Selected == true )
						Items[nItem].Selected = false;
					else
						Items[nItem].Selected = true;

					return;
				}

				// shift based multi row select -------------------------------------------------------
				if ( (( ModifierKeys & Keys.Shift) == Keys.Shift ) && ( AllowMultiselect == true ) )
				{
					Items.ClearSelection();
					if ( m_nLastSelectionIndex >= 0 )			// ie, non negative so that we have a starting point
					{
						int index = m_nLastSelectionIndex;
						do
						{
							Items[index].Selected = true;
							if ( index > nItem )		index--;
							if ( index < nItem )		index++;
						} while ( index != nItem );

						Items[index].Selected = true;
					}

					return;
				}

				// the normal single select -----------------------------------------------------------
				Items.ClearSelection( Items[nItem] );

				// following two if statements deal ONLY with non multi=select where a singel sub item is being selected
				if ( ( m_nLastSelectionIndex < Count ) && ( m_nLastSubSelectionIndex < Columns.Count ) )
					Items[m_nLastSelectionIndex].SubItems[m_nLastSubSelectionIndex].Selected = false;
				if ( ( FullRowSelect == false ) && ( ( nItem < Count ) && ( nColumn < Columns.Count ) ) )
					Items[nItem].SubItems[nColumn].Selected = true;


				m_nLastSelectionIndex = nItem;
				m_nLastSubSelectionIndex = nColumn;
				Items[nItem].Selected = true;

			}
		}


		/// <summary>
		/// when mouse moves
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
			DW("GlacialList_MouseMove");


			try
			{
				if ( m_nState == ListStates.stateColumnResizing )
				{
					Cursor.Current = Cursors.VSplit;


					int nWidth;
					nWidth = e.X - m_pointColumnResizeAnchor.X;

					if ( nWidth <= MINIMUM_COLUMN_SIZE )
					{
						nWidth = MINIMUM_COLUMN_SIZE;
					}

					GLColumn col;
					col = (GLColumn)Columns[m_nResizeColumnNumber];
					col.Width = nWidth;

					return;
				}


				int nItem = 0, nSubItem = 0;
				ListStates eState;
				InterpretCoords( e.X, e.Y, out nItem, out nSubItem, out eState );

				if ( eState == ListStates.stateColumnResizing )
				{
					Cursor.Current = Cursors.VSplit;

					return;
				}

				Cursor.Current = Cursors.Arrow;

			}
			catch( Exception ex )
			{
				Debug.WriteLine("Exception throw in GlobalList_MouseMove with text : " + ex.ToString() );

			}

		}


		/// <summary>
		/// mouse up
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
			DW("MouseUp");

			Cursor.Current = Cursors.Arrow;
			Columns.ClearStates();


			int nItem = 0, nColumn = 0;
			ListStates eState;
			InterpretCoords( e.X, e.Y, out nItem, out nColumn, out eState );


			m_nState = ListStates.stateNone;
		}


		#endregion

		#endregion  // functionality
	}


}


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

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Primary Architects, Inc.
United States United States
I started my programming career in the late 80's with video games and have since written games on the AppleIIgs, SNES, Saturn, Playstation, and PC. After leaving the games industry and joining the ranks of consultants I began doing a lot of work with client/server applications, data movement, and communications. I've also become a recent convert to the XP principles of software development. Despite my defection to the business world I am still an avid gamer and I can be found on the gamezone most weekends slugging it out with others in the various online games there.

I currently live in Utah where I run PA (enterprise architecture consulting firm) and occasionally guest speak at architect forums. I mountain bike (badly), golf (very badly), and have fun (often).

Comments and Discussions