Click here to Skip to main content
15,897,968 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.
/***************************************************
 * Glacial List v1.30
 * 
 * Written By Allen Anderson
 * http://www.glacialcomponents.com
 * 
 * February 24th, 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.Windows.Forms.Design;



namespace GlacialComponents.Controls
{
	/// <summary>
	/// Column State enumerations
	/// </summary>
	public enum ColumnStates 
	{ 
		/// <summary>
		/// Column is in normal state
		/// </summary>
		csNone, 
		/// <summary>
		/// Column is showing pressed state
		/// </summary>
		csPressed, 
		/// <summary>
		/// Mouse cursor is over column header, but not pressed
		/// </summary>
		csHot 
	}



	/// <summary>
	/// Summary description for Column.
	/// </summary>
	/// 
	[
	DesignTimeVisible(true),
	TypeConverter("GlacialComponents.Controls.GLColumnConverter")
	]
	public class GLColumn
	{
		#region Construction

		/// <summary>
		/// Default constructor for use with the collection editor (only)
		/// </summary>
		public GLColumn()
		{
		}

		/// <summary>
		/// Constructor for GLColumn
		/// </summary>
		/// <param name="name"></param>
		public GLColumn( string name )
		{
			this.Name = name;
			this.Text = name;
		}

		#endregion

		#region Events and Delegates


		/// <summary>
		/// Column has changed event
		/// </summary>
		public event ChangedEventHandler ChangedEvent;

		#endregion

		#region VarEnumProperties

		private int								m_nWidth = 100;
		private string							m_strName = "Name";
		private string							m_strText = "Column";
		private ColumnStates					m_State = ColumnStates.csNone;
		private SortDirections					m_LastSortDirection = SortDirections.SortDescending;
		private ArrayList						m_ActiveControlItems = new ArrayList();		
		private ContentAlignment				m_TextAlignment = ContentAlignment.MiddleLeft;
		private int								m_ImageIndex = -1;
		private bool							m_bCheckBoxes = false;
		private GlacialList						m_Parent = null;

		private Control							m_ActivatedEmbeddedControlTemplate = null;
		private GLActivatedEmbeddedTypes		m_ActivatedEmbeddedType = GLActivatedEmbeddedTypes.None;


		private bool							m_bNumericSort = false;







		/// <summary>
		/// Not sure if I'm going to end up using this
		/// </summary>
		[
		Description("Activated embedded control types available."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
		Browsable(false)
		]
		public Control ActivatedEmbeddedControlTemplate 
		{
			get { return m_ActivatedEmbeddedControlTemplate; }
			set 
			{	// interogate control to make sure it has the correct interface
				m_ActivatedEmbeddedControlTemplate = value; 
			}
		}


		/// <summary>
		/// Type of activated embedded control to use
		/// </summary>
		/// <remarks>
		/// This comes with some built in basic types but is also useable with the User type if you want to put your
		/// own type in.
		/// </remarks>
		[
		Description("Type of system embedded control you would like activated in place here."),
		Category("Behavior"),
		Browsable( true )
		]
		public GLActivatedEmbeddedTypes ActivatedEmbeddedType
		{
			get 
			{
				return m_ActivatedEmbeddedType;
			}
			set
			{
				// set the activated embedded control template here
				m_ActivatedEmbeddedType = value;

				// only handle system types
				if ( value == GLActivatedEmbeddedTypes.TextBox )
				{
					this.ActivatedEmbeddedControlTemplate = new GLTextBox();
				}
				else if ( value == GLActivatedEmbeddedTypes.ComboBox )
				{
					this.ActivatedEmbeddedControlTemplate = new GLComboBox();
				}
				else if ( value == GLActivatedEmbeddedTypes.DateTimePicker )
				{
					this.ActivatedEmbeddedControlTemplate = new GLDateTimePicker();
				}
				else if ( value == GLActivatedEmbeddedTypes.None )
				{
					this.ActivatedEmbeddedControlTemplate = null;
				}

				// if its none or user control them leave it alone
			}
		}


#if false
		[
		Browsable(false)
		]
		public ImageList ImageList
		{
			get { return Parent.ImageList; }
		}
#endif

		/// <summary>
		/// Whether or not NumericSort are visible in this column
		/// </summary>
		[
		Description("When sort turned on, only compare numeric values in cells."),
		Category("Behavior"),
		Browsable( true )
		]
		public bool NumericSort
		{
			get { return m_bNumericSort; }
			set { m_bNumericSort = 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; }
		}


		/// <summary>
		/// Whether or not checkboxes are visible in this column
		/// </summary>
		[
		Description("Whether or not checkboxes are visible in this column."),
		Category("Behavior"),
		Browsable( true )
		]
		public bool CheckBoxes
		{
			get { return m_bCheckBoxes; }
			set { m_bCheckBoxes = value; }
		}


		/// <summary>
		/// Image Index
		/// </summary>
		/// <remarks>
		/// Index of image based on image list included in main list
		/// </remarks>
		[
		TypeConverter( typeof( System.Windows.Forms.ImageIndexConverter ) )
		]
		public int ImageIndex
		{
			get { return m_ImageIndex; }
			set { m_ImageIndex = value; }
		}


		/// <summary>
		/// Alignment of text in the header and in the cells
		/// </summary>
		[
		Description("Text alignment inside column header."),
		Browsable( true )
		]
		public ContentAlignment TextAlignment
		{
			get { return m_TextAlignment; }
			set { m_TextAlignment = value; }
		}


		/// <summary>
		/// ActiveControlItems
		/// </summary>
		/// <remarks>
		/// this holds references to items that currently contain live controls.  
		/// This is an optimization so I don't have to iterate the entire Items list
		/// each draw cycle to remove controls no longer visible
		/// </remarks>
		[
		Description("Array of items that have live controls."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
		Browsable( false )
		]
		public ArrayList ActiveControlItems
		{
			get { return m_ActiveControlItems; }
			set { m_ActiveControlItems = value; }
		}



		/// <summary>
		/// Last sort state
		/// </summary>
		[
		Description("Last time sort was done, which direction."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
		Browsable( false )
		]
		public SortDirections LastSortState
		{
			get { return m_LastSortDirection; }
			set { m_LastSortDirection = value; }
		}


		/// <summary>
		/// Width of column
		/// </summary>
		[
		Category("Design"),
		Browsable( true )
		]
		public int Width
		{
			get
			{
				return m_nWidth;
			}
			set
			{
				if ( m_nWidth != value )
				{
					m_nWidth = value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnChanged, this, null, null ) );				// fire the column clicked event
				}
			}
		}


		/// <summary>
		/// Text 
		/// </summary>
		[
		Category("Misc"),
		Description("Text to be displayed in header."),
		Browsable( true )
		]
		public string Text
		{
			get
			{
				return m_strText;
			}
			set
			{
				if ( m_strText != (string)value )
				{
					m_strText = (string)value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnChanged, this, null, null ) );				// fire the column clicked event
				}
			}
		}

		/// <summary>
		/// Name of the column internally
		/// </summary>
		[
		Category("Design"),
		Browsable( true )
		]
		public string Name
		{
			get
			{
				return m_strName;
			}
			set
			{
				if ( m_strName != (string)value )
				{
					m_strName = (string)value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnChanged, this, null, null ) );				// fire the column clicked event
				}
			}
		}


		/// <summary>
		/// State of the column
		/// </summary>
		[
		Browsable(false),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
		]
		public ColumnStates State
		{
			get
			{
				return m_State;
			}
			set
			{
				if ( m_State != value)
				{
					m_State = value;
					if ( ChangedEvent != null )
						ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnStateChanged, this, null, null ) );				// fire the column clicked event
				}
			}
		}


		#endregion
	}


	/// <summary>
	/// gl column collection
	/// </summary>
	public class GLColumnCollection : CollectionBase
	{
		#region Construction

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="parent"></param>
		public GLColumnCollection( GlacialList parent )
		{
			this.Parent = parent;
		}

		#endregion

		#region Events and Delegates


		/// <summary>
		/// Column or Column Collection has changed 
		/// </summary>
		public event ChangedEventHandler ChangedEvent;


		/// <summary>
		/// Column has changed.  Pass Event up the chain.
		/// </summary>
		/// <param name="source"></param>
		/// <param name="e"></param>
		public void GLColumn_Changed( object source, ChangedEventArgs e )
		{	// this gets called when an item internally changes

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

		#endregion

		#region VarsPropertiesAndEnums

		private GlacialList			m_Parent = null;

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


		/// <summary>
		/// Indexer
		/// </summary>
		public GLColumn this[ int nColumnIndex ]
		{
			get
			{
				return List[nColumnIndex] as GLColumn;
			}
		}


		/// <summary>
		/// Index by column name
		/// </summary>
		public GLColumn this[ string strColumnName ]
		{
			get
			{
				return (GLColumn)List[ GetColumnIndex( strColumnName ) ];			// make sure the column is seeded with which one it is before we call it
			}
		}


		/// <summary>
		/// Get the column index that corresponds to the column name
		/// </summary>
		/// <param name="strColumnName"></param>
		/// <returns></returns>
		public int GetColumnIndex( string strColumnName )
		{

			for ( int index = 0; index < List.Count; index++ )
			{
				GLColumn column = (GLColumn)List[index];
				if ( column.Name == strColumnName )
					return index;
			}

			return -1;
		}


		/// <summary>
		/// the combined width of all of the columns
		/// </summary>
		public int Width
		{
			get
			{
				int nTotalWidth = 0;
				GLColumn col;
				for (int index=0; index<List.Count; index++)
				{
					col = (GLColumn)List[index];
					nTotalWidth += col.Width;
				}

				return nTotalWidth;
			}
		}
		#endregion

		#region Functionality

		/// <summary>
		/// Get Span Size for column spanning
		/// </summary>
		/// <param name="strStartColumnName"></param>
		/// <param name="nColumnsSpanned"></param>
		/// <returns></returns>
		public int GetSpanSize( string strStartColumnName, int nColumnsSpanned )
		{
			int nStartColumn = GetColumnIndex( strStartColumnName );

			int nSpanSize = 0;

			if ( (nColumnsSpanned+nStartColumn) > Count )
				nColumnsSpanned = (Count-nStartColumn);

			for ( int nIndex = nStartColumn; nIndex<(nStartColumn+nColumnsSpanned); nIndex++ )
				nSpanSize += this[nIndex].Width;

			return nSpanSize;
		}


		/// <summary>
		/// Add a column to collection
		/// </summary>
		/// <param name="newColumn"></param>
		public void Add( GLColumn newColumn )
		{
			newColumn.Parent = Parent;

			//item.ChangedEvent += new BSLItem.ChangedEventHandler( BSLItem_Changed );				// listen to event changes inside the item
			newColumn.ChangedEvent += new ChangedEventHandler( GLColumn_Changed );

			while ( GetColumnIndex( newColumn.Name ) != -1 )
				newColumn.Name += "x";					// change the name till it is not the same

			int nIndex = List.Add( newColumn );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnCollectionChanged, newColumn, null, null ) );				// fire the column clicked event
		}

		/// <summary>
		/// Add Column to collection
		/// </summary>
		/// <param name="strColumnName"></param>
		/// <param name="nColumnWidth"></param>
		public GLColumn Add( string strColumnName, int nColumnWidth )
		{
			GLColumn newColumn = new GLColumn();
			newColumn.Text = strColumnName;
			newColumn.Name = strColumnName;
			newColumn.Width = nColumnWidth;
			newColumn.State = ColumnStates.csNone;
			newColumn.TextAlignment = ContentAlignment.MiddleLeft;
			newColumn.Parent = Parent;

			Add( newColumn );

			return newColumn;
		}

		/// <summary>
		/// Add Column to collection
		/// </summary>
		/// <param name="strColumnName"></param>
		/// <param name="nColumnWidth"></param>
		/// <param name="align"></param>
		public GLColumn Add( string strColumnName, int nColumnWidth, HorizontalAlignment align )
		{
			GLColumn newColumn = new GLColumn();
			newColumn.Text = strColumnName;
			newColumn.Name = strColumnName;
			newColumn.Width = nColumnWidth;
			newColumn.State = ColumnStates.csNone;
			newColumn.TextAlignment = ContentAlignment.MiddleLeft;

			Add( newColumn );

			return newColumn;
		}

		/// <summary>
		/// Add Range of columns to collection
		/// </summary>
		/// <param name="columns"></param>
		public void AddRange( GLColumn[] columns)
		{
			lock(List.SyncRoot)
			{
				for (int i=0; i<columns.Length; i++)
					Add( columns[i] );
			}
		}

		/// <summary>
		/// Remove Column from collection
		/// </summary>
		/// <param name="nColumnIndex"></param>
		public void Remove( int nColumnIndex )
		{
			if ( ( nColumnIndex >= this.Count ) || (nColumnIndex < 0) )
				return;			// error

			List.RemoveAt( nColumnIndex );

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnCollectionChanged, null, null, null ) );				// fire the column clicked event
		}

		/// <summary>
		/// Remove all columns from collection
		/// </summary>
		public new void Clear()
		{
			List.Clear();

			if ( ChangedEvent != null )
				ChangedEvent( this, new ChangedEventArgs( ChangedTypes.ColumnCollectionChanged, null, null, null ) );				// fire the column clicked event
		}

		/// <summary>
		/// Return index of column in collection
		/// </summary>
		/// <param name="column"></param>
		/// <returns></returns>
		public int IndexOf( GLColumn column )
		{
			return List.IndexOf( column );
		}

		/// <summary>
		/// Clear column states
		/// </summary>
		/// <remarks>
		/// Primarily used to clear pressed / hot states
		/// </remarks>
		public void ClearStates()
		{
			foreach ( GLColumn column in List )
				column.State = ColumnStates.csNone;
		}

		/// <summary>
		/// Clear only hot states from column collection
		/// </summary>
		public void ClearHotStates()
		{
			foreach ( GLColumn column in List )
			{
				if ( column.State == ColumnStates.csHot )
					column.State = ColumnStates.csNone;
			}
		}

		/// <summary>
		/// if any of the columns are in a pressed state then disable all hotting
		/// </summary>
		/// <returns></returns>
		public bool AnyPressed()
		{
			foreach ( GLColumn column in List )
				if ( column.State == ColumnStates.csPressed )
					return true;

			return false;
		}

		#endregion
	}


	#region Collection Editors

	/// <summary>
	/// Class created so we can force an invalidation/update on the control when the column editor returns
	/// </summary>
	internal class CustomCollectionEditor : CollectionEditor
	{
		private int m_nUnique = 1;

		/// <summary>
		/// Default Constructor for custom column collection editor
		/// </summary>
		/// <param name="type"></param>
		public CustomCollectionEditor(Type type) : base(type)
		{
			
		}

		/// <summary>
		/// Called to edit a value in collection editor
		/// </summary>
		/// <param name="context"></param>
		/// <param name="isp"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public override object EditValue(ITypeDescriptorContext context, IServiceProvider isp, object value)
		{
			GlacialList originalControl = (GlacialList)context.Instance;

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

			originalControl.Refresh();//.Invalidate( true );
			return returnObject;
		}

		/// <summary>
		/// Creates a new instance of a column for custom collection
		/// </summary>
		/// <param name="itemType"></param>
		/// <returns></returns>
		protected override object CreateInstance(Type itemType)
		{
			// here we are making sure that we generate a unique column name every time
			object[] cols;
			string strTmpColName;
			do
			{
				strTmpColName = "Column" + m_nUnique.ToString();
				cols = this.GetItems( strTmpColName );

				m_nUnique++;
			} while ( cols.Length != 0 );

			// instance the column and set its ident name
			object col = base.CreateInstance (itemType);
			((GLColumn)col).Name = strTmpColName;

			return col;
		}


	}

	/// <summary>
	/// GLColumnConverter
	/// </summary>
	/// 
	public class GLColumnConverter : TypeConverter
	{
		/// <summary>
		/// Required for correct collection editor use
		/// </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>
		/// Required for correct collection editor use
		/// </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 GLColumn)
			{
				GLColumn column = (GLColumn)value;
              
				ConstructorInfo ci = typeof(GLColumn).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