Click here to Skip to main content
15,881,424 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.
/***************************************************
 * GlacialList v1.30
 * 
 * Written By Allen Anderson
 * http://www.glacialcomponents.com
 * 
 * February 10th, 2004
 * 
 * You may redistribute this control in binary and modified binary form as you please.  You may
 * use this control in commercial applications without need for external credit royalty free.
 * 
 * However, you are restricted from releasing the source code in any modified fashion
 * whatsoever.
 * 
 * I MAKE NO PROMISES OR WARRANTIES ON THIS CODE/CONTROL.  IF ANY DAMAGE OR PROBLEMS HAPPEN FROM ITS USE
 * THEN YOU ARE RESPONSIBLE.
 * 
 */



using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using System.Text;




namespace GlacialComponents.Controls
{
	/// <summary>
	/// SubItems which make up a GLItem
	/// </summary>
	[
	DesignTimeVisible(true),
	TypeConverter("GlacialComponents.Controls.GLSubItemConverter")
	]
	public class GLSubItem
	{
		#region Events and Delegates


		/// <summary>
		/// Sub Item has changed.
		/// </summary>
		public event ChangedEventHandler ChangedEvent;

		#endregion

		#region Properties and Variables

		private string					m_Text = "";
		private Color					m_ForeColor =	Color.Black;


		//sf.LineAlignment = System.Drawing.StringAlignment.Near;
		//sf.FormatFlags = StringFormatFlags.LineLimit | StringFormatFlags.MeasureTrailingSpaces;

		private int						m_nImageIndex = -1;
		private HorizontalAlignment	m_ImageAlignment = HorizontalAlignment.Left;

		private Color					m_BackColor;			// = Color.White;
		//private int					m_nSpan = 1;								// always span 1 unless changed
		private bool					m_bSelected = false;
		private object					m_Tag = null;
		private bool					m_bForceText = false;						// for this specific item, show it as normal instead of control based.
		private Control				m_Control = null;
		private GlacialList			m_Parent = null;							// so we can always access the columns
		private bool					m_bChecked = false;							// if the sub item has a check box, then this holds the value
		private Hashtable			m_EmbeddedControProperties = null;			// only create hashtable if someone starts to use it (saves memory)
		private Rectangle			m_LastCellRect = new Rectangle( 0,0,0,0 );	// last rectangle that text was drawn into



		/// <summary>
		/// last rectangle that text was drawn into
		/// </summary>
		[
		Browsable( false ),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )
		]
		public Rectangle LastCellRect
		{
			get	{ return m_LastCellRect; }
			set { m_LastCellRect = value; }
		}


		/// <summary>
		/// is the checkbox checked or not
		/// </summary>
		[
		Browsable( true ),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Description( "Item Check State" )
		]
		public bool Checked
		{
			get	{ return m_bChecked; }
			set
			{
				if ( m_bChecked != value )
				{
					m_bChecked = value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );
				}
			}
		}


		/// <summary>
		/// pointer to the primary Parent on top
		/// </summary>
		[
		Browsable(false),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )
		]
		public GlacialList Parent
		{
			get	{ return m_Parent; }
			set	{ m_Parent = value;	}
		}


		/// <summary>
		/// Properties of the embedded controls in the listview
		/// 
		/// this is brilliant because it also allows people to set properties of controls that I don't know about
		/// 
		/// the reason I'm even doing this is so many standard control types don't have to be shown
		/// </summary>
		/// 
		[
		Browsable(false),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )
		]
		public Hashtable EmbeddedControProperties
		{
			get 
			{
				if ( m_EmbeddedControProperties == null )
					m_EmbeddedControProperties = new Hashtable();

				return m_EmbeddedControProperties; 
			}
			//			set 
			//			{ 
			//				m_EmbeddedControProperties = value; 
			//			}
		}


		/// <summary>
		/// Force the sub item display to default to text only
		/// 
		/// This will override everything.
		/// </summary>
		[
		Description("We can choose to NOT display the control override coming from the column."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable( true ),
		Category("Appearance")
		]
		public bool ForceText
		{
			get	{ return m_bForceText; }
			set
			{
				if ( m_bForceText != value )
				{
					m_bForceText = value;
					ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, null ) );
				}
			}
		}


		/// <summary>
		/// Embedded Control
		/// </summary>
		[
		Description("Embed control."),
		Browsable( false ),
		Category("Control"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )
		]
		public Control Control
		{
			get	{ return m_Control; }
			set	
			{
				if ( m_Control != value )
				{
					m_Control = value;

					// do all other default control setups here
					m_Control.Visible = false;
					//m_Control.Parent = this.Parent;
					//m_Control.MouseDown += new MouseEventHandler( this.ListView.OnMouseDownFromSubItem );

					//if ( ChangedEvent != null )
					//ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );
				}
			}
		}


#if false
		subitems[e.IndexChanged].MouseDown += new MouseEventHandler(OnSubItemMouseDown);

		private void OnSubItemMouseDown(object sender, MouseEventArgs e)
		{
			if (MouseDown != null)
				MouseDown(this, e);
		}
#endif

		/// <summary>
		/// Index of image
		/// </summary>
		[
		Description("Index of image to display from imagelist.  This assumes that an imagelist exists.  If it does not, this will do nothing."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable( true ),
		Category("ImageIndex")
		]
		public int ImageIndex
		{
			get	{ return m_nImageIndex; }
			set	
			{
				if ( m_nImageIndex != value )
				{
					m_nImageIndex = value;

					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );
				}
			}
		}


		/// <summary>
		/// Alignment of the image within the subitem
		/// </summary>
		[
		Description("Image info for the sub item."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable( true ),
		Category("Image")
		]
		public HorizontalAlignment ImageAlignment
		{
			get	{ return m_ImageAlignment; }
			set
			{
				if ( m_ImageAlignment != (HorizontalAlignment)value )
				{
					m_ImageAlignment = (HorizontalAlignment)value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );				// fire the column clicked event
				}
			}
		}


		/// <summary>
		/// Extra user information
		/// </summary>
		[
		Description("Extra user information"),
		Browsable( false ),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Category("Data")
		]
		public object Tag
		{
			get { return m_Tag; }
			set { m_Tag = value; }
		}


		/// <summary>
		/// Text
		/// </summary>
		[
		Description("Text"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable( true ),
		Category("Data")
		]
		public string Text
		{
			get 
			{
				// need to put a check here if there is an activated embedded control that is live

				if ( ( this.Parent != null ) && ( this.Parent.ActivatedEmbeddedControl != null ) )			// see if we can even see the parent
				{
					GLEmbeddedControl ctrl = (GLEmbeddedControl)this.Parent.ActivatedEmbeddedControl;
					if ( ( ctrl != null ) && ( ctrl.SubItem == this ) )		// just in case this isn't really an activated embedded control
					{
						Debug.WriteLine( ctrl.GLReturnText() );

						return ctrl.GLReturnText();
					}
				}

				return m_Text; 
			}
			set
			{
				if ( m_Text != value )
				{
					m_Text = value;

					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );
				}
			}
		}


		/// <summary>
		/// Color of text in item
		/// </summary>
		[
		Description("Color of the text"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable( true ),
		Category("Appearance")
		]
		public Color ForeColor
		{
			get	{ return m_ForeColor; }
			set
			{
				if ( m_ForeColor != value )
				{
					m_ForeColor = (Color)value;

					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );
				}
			}
		}


		/// <summary>
		/// Background color
		/// </summary>
		[
		Description("Color of background"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable( true ),
		Category("Appearance")
		]
		public Color BackColor
		{
			get	{ return m_BackColor; }
			set
			{
				if ( m_BackColor != value )
				{
					m_BackColor = (Color)value;

					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, null, null, this ) );
				}
			}
		}


		//		[
		//		Description("Columns spanned."),
		//		Browsable( true ),
		//		Category("Appearance")
		//		]
		//		public int Span
		//		{
		//			get	{ return m_nSpan; }
		//			set	
		//			{
		//				if ( m_nSpan != value )
		//				{
		//					m_nSpan = value;
		//
		//					if ( ChangedEvent != null )
		//						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemChanged, "" ) );
		//				}
		//			}
		//		}

		
		/// <summary>
		/// Indicates when the item is selected
		/// </summary>
		[
		Description("Sub item selection state."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable( false ),
		Category("Appearance")
		]
		public bool Selected		// sub item
		{
			get	{ return m_bSelected; }
			set
			{
				if ( m_bSelected != value )
				{
					// this could be more complicated actually


					m_bSelected = value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ItemChanged, null, null, this ) );
				}
			}
		}


		#endregion

		#region Functionality

#if false
		public void UnsetControlSettings( Control control )
		{


		}

		public void SetControlSettings( Control control )
		{
			// use reflection heavily ?
			IDictionaryEnumerator ide = EmbeddedControProperties.GetEnumerator();

			Type ctype = control.GetType();
			PropertyInfo cpropinfo = ctype.GetProperty( ide.Key.ToString() );
			object test = cpropinfo.GetValue( ide.Key.ToString(), null );
			Debug.WriteLine( test.ToString() );

			//control.ref. ide.Key
		}
#endif


		#endregion
	}


	/// <summary>
	/// Sub Item collection
	/// </summary>
	public class GLSubItemCollection : CollectionBase
	{
		#region Initialization

		/// <summary>
		/// Constructor
		/// </summary>
		public GLSubItemCollection()
		{
		}

		/// <summary>
		/// Constructor that accepts parent pointer
		/// </summary>
		/// <param name="parent"></param>
		public GLSubItemCollection( GlacialList parent )
		{
			m_Parent = parent;
		}

		#endregion

		#region Events and Delegates

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

		/// <summary>
		/// Sub Item Collection has changed
		/// </summary>
		public event ChangedEventHandler ChangedEvent;

		/// <summary>
		/// Sub Item changed handler.
		/// </summary>
		/// <param name="source"></param>
		/// <param name="e"></param>
		public void SubItem_Changed( object source, ChangedEventArgs e )
		{	// this gets called when an item internally changes
			GlacialList.DW("GLSubItemCollection::SubItem_Changed");

			if ( ChangedEvent != null )
				ChangedEvent( source, e );				// fire the column clicked event
		}

		/// <summary>
		/// Clear called against subitems in sub item collection
		/// </summary>
		protected override void OnClear()
		{
			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ItemCollectionChanged, null, null, null ) );				// fire the column clicked event
		}
		
		#endregion

		#region Properties and Variables

		private GlacialList				m_Parent;


		/// <summary>
		/// Internal pointer to parent class
		/// </summary>
		[
		Browsable(false),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )
		]
		public GlacialList Parent
		{
			get	
			{ 
				return m_Parent; 
			}
			set
			{ 
				m_Parent = value;	

				foreach ( GLSubItem subItem in List )
					subItem.Parent = value;
			}
		}


		/// <summary>
		/// Indexer
		/// </summary>
		public GLSubItem this[ int nItemIndex ]
		{
			get
			{
				//				int nTmpCount = 0;
				//				if ( Parent == null )		// rare case when there is no parent set (almost always during collection editor)
				//					nTmpCount = 255;		// basically infinite during collection editor
				//				else
				//					nTmpCount = Parent.Columns.Count;

				int nBailout = 0;

				// check to make sure it exists first
				while ( List.Count <= nItemIndex )
				{
					GLSubItem newitem = new GLSubItem();
					newitem.ChangedEvent += new ChangedEventHandler( SubItem_Changed );
					newitem.Parent = this.m_Parent;
					//newitem.Control = Parent.Columns[ nItemIndex ]

					List.Add( newitem );							// if the index doesn't yet exist, fill in the subitems till it does

					if ( nBailout++ > 25 )
						break;
				}

				return (GLSubItem)List[nItemIndex];
			}
		}


		#endregion

		#region CollectionEditor Support Routines


		/// <summary>
		/// Add range of sub items
		/// </summary>
		/// <param name="subItems"></param>
		public void AddRange( GLSubItem[] subItems)
		{
			lock(List.SyncRoot)
			{
				for (int i=0; i<subItems.Length; i++)
					Add( subItems[i] );
			}
		}


		/// <summary>
		/// add an item to the end of the list
		/// </summary>
		/// <param name="strItemText"></param>
		/// <returns></returns>
		public GLSubItem Add( string strItemText )
		{
			return Insert( -1, strItemText );
		}


		/// <summary>
		/// add an itemto the items collection
		/// </summary>
		/// <param name="subItem"></param>
		/// <returns></returns>
		public int Add( GLSubItem subItem )
		{
			return Insert( -1, subItem );
		}


		/// <summary>
		/// insert an item into the list at specified index
		/// </summary>
		/// <param name="nIndex"></param>
		/// <param name="strItemText"></param>
		/// <returns></returns>
		public GLSubItem Insert( int nIndex, string strItemText )
		{
			GLSubItem subItem = new GLSubItem();			//GLItem item = new GLItem(Parent);
			//item.SubItems[0].Text = strItemText;

			nIndex = Insert( nIndex, subItem );

			return subItem;
		}


		/// <summary>
		/// lowest level of add/insert.  All add and insert routines eventually call this
		/// 
		/// in the future always have routines call this one as well to keep one point of entry
		/// </summary>
		/// <param name="nIndex"></param>
		/// <param name="subItem"></param>
		/// <returns></returns>
		public int Insert( int nIndex, GLSubItem subItem )
		{
			subItem.Parent = this.m_Parent;

			subItem.ChangedEvent += new ChangedEventHandler( SubItem_Changed );

			if ( nIndex < 0 )
				nIndex = List.Add( subItem );			// add the subItem itself
			else
				List.Insert( nIndex, subItem );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemCollectionChanged, null, null, subItem ) );

			return nIndex;
		}


		/// <summary>
		/// remove an item from the list
		/// </summary>
		/// <param name="nSubItemIndex"></param>
		public void Remove( int nSubItemIndex )
		{
			if ( ( nSubItemIndex >= this.Count ) || (nSubItemIndex < 0) )
				return;			// error

			List.RemoveAt( nSubItemIndex );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemCollectionChanged, null, null, null ) );
		}


		/// <summary>
		/// remove an item from the list
		/// </summary>
		/// <param name="subItem"></param>
		public void Remove( GLSubItem subItem )
		{
			List.Remove( subItem );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SubItemCollectionChanged, null, null, null ) );
		}


		/// <summary>
		/// Clears all selection bits in the item structure
		/// </summary>
		public void ClearSelection()
		{
			for ( int index = 0; index<List.Count; index++ )
				this[index].Selected = false;												// changed will generate an invalidation by themselves
		}


		#endregion
	}


	/// <summary>
	/// GLItem which corresponds to rows in the list view
	/// </summary>
	[
	DesignTimeVisible(true),
	TypeConverter("GlacialComponents.Controls.GLItemConverter")
	]
	public class GLItem								// : IComparable
	{
		#region Events and Delegates

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

		/// <summary>
		/// GLItem changed event
		/// </summary>
		public event ChangedEventHandler ChangedEvent;

		/// <summary>
		/// Sub Item collection changed handler
		/// </summary>
		/// <param name="source"></param>
		/// <param name="e"></param>
		public void SubItemCollection_Changed( object source, ChangedEventArgs e )
		{	// this gets called when an item internally changes
			GlacialList.DW("GLItem::SubItemCollection_Changed");

			if ( ChangedEvent != null )
			{
				e.Item = this;							// add which item this came through
				ChangedEvent( this, e );				// fire the column clicked event
			}
		}

		#endregion

		#region Initialization

		/// <summary>
		/// Constructor
		/// </summary>
		public GLItem()
		{
			if ( this.Parent != null )
				m_SubItems = new GLSubItemCollection( this.Parent );
			else
				m_SubItems = new GLSubItemCollection();

			SubItems.ChangedEvent += new ChangedEventHandler( SubItemCollection_Changed );		// this will only happen when a new item is created
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="parent"></param>
		public GLItem( GlacialList parent )
		{
			m_SubItems = new GLSubItemCollection( parent );
			m_SubItems.Parent = parent;

			Parent = parent;		// this has to be after the sub item set becuase it tries to use the subitems

			SubItems.ChangedEvent += new ChangedEventHandler( SubItemCollection_Changed );		// this will only happen when a new item is created
		}


		#endregion

		#region Properties and Variables

		private GLSubItemCollection				m_SubItems;
		private bool										m_bSelected = false;
		private object										m_Tag = null;
		private Color										m_ForeColor = Color.Black;
		private GlacialList								m_Parent = null;
		private Color										m_RowBorderColor = Color.Black;
		private Color										m_BackColor = Color.White;
		private int											m_nRowBorderSize = 0;



		/// <summary>
		/// row border size
		/// </summary>
		/// 
		[
		Description("Size of a border on each row."),
		Category("Behavior"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable(true)
		]
		public int RowBorderSize
		{
			get { return m_nRowBorderSize; }
			set { m_nRowBorderSize = value; }
		}


		/// <summary>
		/// Text color for item
		/// </summary>
		[
		Category("Behavior"),
		Description("Sub Items"),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		//Editor(typeof(SubItemCollectionEditor), typeof(UITypeEditor)),
		Editor(typeof(CollectionEditor), typeof(UITypeEditor)),
		Browsable(true)
		]
		public GLSubItemCollection	SubItems
		{
			get 
			{
				return m_SubItems;
			}
		}


		/// <summary>
		/// Row border color
		/// </summary>
		/// 
		[
		Description("Set the back color for an entire row." ),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable(true)
		]
		public Color BackColor
		{
			get { return m_BackColor; }
			set { m_BackColor = value; }
		}


		/// <summary>
		/// Row border color
		/// </summary>
		/// 
		[
		Description("If you have row border size set to something other than 0 then it will take on this color."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable(true)
		]
		public Color RowBorderColor
		{
			get { return m_RowBorderColor; }
			set { m_RowBorderColor = value; }
		}


		/// <summary>
		/// Text for cell 0 (added by popular request)
		/// </summary>
		public string Text
		{
			get { return SubItems[0].Text; }
			set { SubItems[0].Text = value; }
		}


		/// <summary>
		/// User defineable object
		/// </summary>
		[
		Description("Extra user information."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable(false)
		]
		public object Tag
		{
			get { return m_Tag; }
			set { m_Tag = value; }
		}


		/// <summary>
		/// Text color for item
		/// </summary>
		[
		Description("Text Color override for item."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable(true)
		]
		public Color ForeColor
		{
			get { return m_ForeColor; }
			set { m_ForeColor = value; }
		}


		/// <summary>
		/// pointer to parent
		/// </summary>
		[
		Description("Parent"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable(false)
		]
		public GlacialList Parent
		{
			get	
			{ 
				return this.m_Parent; 
			}
			set	
			{ 
				this.m_Parent = value;

				SubItems.Parent = value; 
			}
		}


		/// <summary>
		/// Selected
		/// </summary>
		[
		Description(""),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		DefaultValue(false),
		Browsable(false)
		]
		public bool Selected
		{
			get { return m_bSelected; }
			set
			{
				if ( m_bSelected != value )
				{
					if ( m_Parent != null && !m_Parent.AllowMultiselect && !m_Parent.Items.SuspendEvents )
					{
						m_Parent.Items.SuspendEvents = true;
						m_Parent.Items.ClearSelection();
						m_Parent.Items.SuspendEvents = false;
					}

					m_bSelected = value;
					ChangedEvent( this, new ChangedEventArgs( ChangedTypes.SelectionChanged, null, this, null ) );
				}
			}
		}


		#endregion
	}


	/// <summary>
	/// Collection of GLItems
	/// </summary>
	public class GLItemCollection : CollectionBase
	{
		#region Events and Delegates


		/// <summary>
		/// Fires when a change occurs to the data
		/// </summary>
		public event ChangedEventHandler ChangedEvent;


		/// <summary>
		/// item has changed
		/// </summary>
		/// <param name="source"></param>
		/// <param name="e"></param>
		public void Item_Changed( object source, ChangedEventArgs e )
		{	// this gets called when an item internally changes
			GlacialList.DW("GLItemCollection::Item_Changed");

			if ( ChangedEvent != null  && !SuspendEvents )
				ChangedEvent( source, e );				// fire the column clicked event
		}


		/// <summary>
		/// Items have been cleared event
		/// </summary>
		protected override void OnClear()
		{
			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ItemCollectionChanged, null, null, null ) );
		}
		

		#endregion

		#region Initialization

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="newParent"></param>
		public GLItemCollection( GlacialList newParent )
		{
			Parent = newParent;
		}


		#endregion

		#region Properties And Variables

		private GlacialList					m_Parent;
		private bool						m_bSuspendEvents = false;



		/// <summary>
		/// this is used for operations where you are changing multiple items consecutively and don't want to send 
		/// a larger number of change events than necessary.
		/// </summary>
		[
		Description("Extra user information."),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable(false)
		]
		public bool SuspendEvents
		{
			set { m_bSuspendEvents = value; }
			get { return m_bSuspendEvents; }
		}


		/// <summary>
		/// Sets the parent variable so we know what to refresh when there is a change
		/// </summary>
		/// 
		[
		Description("Parent"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable(false)
		]
		public GlacialList Parent
		{
			get 
			{ 
				return m_Parent; 
			}
			set 
			{ 
				m_Parent = value;

				// iterate through the children and send them all the parent
				foreach ( GLItem item in this.List )
					item.Parent = m_Parent;						// hopefully this will propogate
			}
		}


		/// <summary>
		/// Indexer that allows the use of Items by []
		/// </summary>
		/// 
		[
		Description("Item Collection"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Visible ),
		Browsable(true)
		]
		public GLItem this[ int nItemIndex ]
		{
			get	
			{	
				return (GLItem)List[nItemIndex];	
			}
			set
			{
				List[nItemIndex] = value;
			}
		}


		/// <summary>
		/// returns a list of only the selected items
		/// </summary>
		[
		Description("Selected Items Array"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable(false)
		]
		public ArrayList SelectedItems
		{
			get
			{
				ArrayList listSelectedItems = new ArrayList();

				// go through list and add only selected items
				for ( int nIndex = 0; nIndex < Count; nIndex++ )
					if ( this[nIndex].Selected == true )
						listSelectedItems.Add( this[nIndex] );

				return listSelectedItems;
			}
		}


		/// <summary>
		/// returns a list of only the selected items indexes
		/// </summary>
		[
		Description("Selected Items Array Of Indicies"),
		DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ),
		Browsable(false)
		]
		public ArrayList SelectedIndicies
		{
			get
			{
				ArrayList listSelectedItems = new ArrayList();

				// go through list and add only selected items
				for ( int nIndex = 0; nIndex < Count; nIndex++ )
					if ( this[nIndex].Selected == true )
						listSelectedItems.Add( nIndex );

				return listSelectedItems;
			}
		}


		#endregion

		#region Methods

#if false
		/// <summary>
		/// Use the built in sorting mechanism to sort the list
		/// </summary>
//		public void Sort()
//		{
//			this.InnerList.Sort();
//		}
#endif

		/// <summary>
		/// Compatability with collection editor
		/// </summary>
		/// <param name="items"></param>
		public void AddRange( GLItem[] items)
		{
			lock(List.SyncRoot)
			{
				for (int i=0; i<items.Length; i++)
					Add( items[i] );
			}
		}

		/// <summary>
		/// add an item to the end of the list
		/// </summary>
		/// <param name="strItemText"></param>
		/// <returns></returns>
		public GLItem Add( string strItemText )
		{
			return Insert( -1, strItemText );
		}


		/// <summary>
		/// add an itemto the items collection
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public int Add( GLItem item )
		{
			return Insert( -1, item );
		}


		/// <summary>
		/// insert an item into the list at specified index
		/// </summary>
		/// <param name="nIndex"></param>
		/// <param name="strItemText"></param>
		/// <returns></returns>
		public GLItem Insert( int nIndex, string strItemText )
		{
			GLItem item = new GLItem( Parent );			//GLItem item = new GLItem(Parent);
			item.SubItems[0].Text = strItemText;

			nIndex = Insert( nIndex, item );

			return item;
		}

		/// <summary>
		/// lowest level of add/insert.  All add and insert routines eventually call this
		/// 
		/// in the future always have routines call this one as well to keep one point of entry
		/// </summary>
		/// <param name="nIndex"></param>
		/// <param name="item"></param>
		/// <returns></returns>
		public int Insert( int nIndex, GLItem item )
		{
			item.Parent = this.Parent;

			item.ChangedEvent += new ChangedEventHandler( Item_Changed );

			if ( nIndex < 0 )
				nIndex = List.Add( item );			// add the item itself
			else
				List.Insert( nIndex, item );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ItemCollectionChanged, null, null, null ) );

			return nIndex;
		}

		/// <summary>
		/// remove an item from the list
		/// </summary>
		/// <param name="nItemIndex"></param>
		public void Remove( int nItemIndex )
		{
			if ( ( nItemIndex >= this.Count ) || (nItemIndex < 0) )
				return;			// error

			List.RemoveAt( nItemIndex );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ItemCollectionChanged, null, null, null ) );
		}


		/// <summary>
		/// remove an item from the list
		/// </summary>
		/// <param name="item"></param>
		public void Remove( GLItem item )
		{
			List.Remove( item );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ItemCollectionChanged, null, null, null ) );
		}


		/// <summary>
		/// Clears all selection bits in the item structure
		/// </summary>
		public void ClearSelection()
		{
			for ( int index = 0; index<List.Count; index++ )
				this[index].Selected = false;												// changed will generate an invalidation by themselves
		}

		/// <summary>
		/// Clears all selection bits in the item structure
		/// 
		/// this overload is an optimization to stop a redraw on a re-selection
		/// </summary>
		public void ClearSelection( GLItem itemIgnore )
		{
			for ( int index = 0; index<List.Count; index++ )
			{
				GLItem citem = this[index];
				if ( citem != itemIgnore )
					citem.Selected = false;												// changed will generate an invalidation by themselves
			}
		}

		/// <summary>
		/// Find the next item index
		/// 
		/// set startindex to -1 to start at the beginning
		/// </summary>
		/// <param name="nStartIndex"></param>
		/// <param name="nColumn"></param>
		/// <param name="strItemText"></param>
		/// <returns></returns>
		public int FindNextItemIndex( int nStartIndex, int nColumn, string strItemText )
		{
			if ( ( nStartIndex < 0 ) || ( nStartIndex > Count ) )
				nStartIndex = 0;

			for ( int nIndex = nStartIndex; nIndex < Count; nIndex++ )
			{
				if ( strItemText == this[nIndex].SubItems[nColumn].Text )
					return nIndex;
			}

			return -1;			// couldn't find it
		}


		/// <summary>
		/// Get the next selected item index.
		/// 
		/// set startindex to -1 to start at the beginning
		/// </summary>
		/// <param name="nStartIndex"></param>
		/// <returns></returns>
		public int GetNextSelectedItemIndex( int nStartIndex )		// use -1 to have it start from the beginning of the list
		{
			if ( ( nStartIndex < 0 ) || ( nStartIndex > Count ) )
				nStartIndex = -1;

			for ( int nIndex = nStartIndex+1; nIndex < Count; nIndex++ )
			{
				if ( this[nIndex].Selected == true )
					return nIndex;
			}

			return -1;			// couldn't find it
		}


		/// <summary>
		/// Find the index of a specified item
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public int FindItemIndex( GLItem item )		// use -1 to have it start from the beginning of the list
		{
			for ( int nIndex = 0; nIndex < Count; nIndex++ )
				if ( item == this[nIndex] )
					return nIndex;

			return -1;			// couldn't find it
		}


		#endregion
	}


	#region Item Converter

	/// <summary>
	/// GLItemConverter
	/// </summary>
	/// 
	public class GLItemConverter : TypeConverter
	{
		/// <summary>
		/// Conversion from Item to string
		/// </summary>
		/// <param name="context"></param>
		/// <param name="destinationType"></param>
		/// <returns></returns>
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof(InstanceDescriptor))
			{
				return true;
			}
			return base.CanConvertTo(context, destinationType);
		}

		/// <summary>
		/// Conversion to string
		/// </summary>
		/// <param name="context"></param>
		/// <param name="culture"></param>
		/// <param name="value"></param>
		/// <param name="destinationType"></param>
		/// <returns></returns>
		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType == typeof(InstanceDescriptor) && value is GLItem)
			{
				GLItem column = (GLItem)value;
              
				ConstructorInfo ci = typeof(GLItem).GetConstructor(new Type[] {});
				if (ci != null)
				{
					return new InstanceDescriptor(ci, null, false);
				}
			}
			return base.ConvertTo(context, culture, value, destinationType);
		}
	}

	/// <summary>
	/// GLSubItemConverter
	/// </summary>
	public class GLSubItemConverter : TypeConverter
	{
		/// <summary>
		/// Conversion from Item to string
		/// </summary>
		/// <param name="context"></param>
		/// <param name="destinationType"></param>
		/// <returns></returns>
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof(InstanceDescriptor))
			{
				return true;
			}
			return base.CanConvertTo(context, destinationType);
		}

		/// <summary>
		/// Conversion to string
		/// </summary>
		/// <param name="context"></param>
		/// <param name="culture"></param>
		/// <param name="value"></param>
		/// <param name="destinationType"></param>
		/// <returns></returns>
		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType == typeof(InstanceDescriptor) && value is GLSubItem)
			{
				//GLItem column = (GLItem)value;
              
				ConstructorInfo ci = typeof(GLSubItem).GetConstructor(new Type[] {});
				if (ci != null)
				{
					return new InstanceDescriptor(ci, null, false);
				}
			}
			return base.ConvertTo(context, culture, value, destinationType);
		}
	}

	#endregion

}

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

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

License

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


Written By
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