Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C#

XPTable: .NET ListView Update

Rate me:
Please Sign up or sign in to vote.
4.86/5 (71 votes)
4 Jul 2007CPOL9 min read 1.2M   9.1K   242  
An update to the excellent XPTable control
/*
 * Copyright � 2005, Mathew Hall
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *    - Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 * 
 *    - Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */


using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

using XPTable.Editors;
using XPTable.Events;
using XPTable.Renderers;


namespace XPTable.Models
{
	/// <summary>
	/// Summary description for Column.
	/// </summary>
	[DesignTimeVisible(false),
	ToolboxItem(false)]
	public abstract class Column : Component
	{
		#region Event Handlers

		/// <summary>
		/// Occurs when one of the Column's properties changes
		/// </summary>
		public event ColumnEventHandler PropertyChanged;

		#endregion


		#region Class Data

		// Column state flags
		private readonly static int STATE_EDITABLE = 1;
		private readonly static int STATE_ENABLED = 2;
		private readonly static int STATE_VISIBLE = 4;
		private readonly static int STATE_SELECTABLE = 8;
		private readonly static int STATE_SORTABLE = 16;
		
		/// <summary>
		/// The amount of space on each side of the Column that can 
		/// be used as a resizing handle
		/// </summary>
		public static readonly int ResizePadding = 8;

		/// <summary>
		/// The default width of a Column
		/// </summary>
		public static readonly int DefaultWidth = 75;
		
		/// <summary>
		/// The maximum width of a Column
		/// </summary>
		public static readonly int MaximumWidth = 1024;
		
		/// <summary>
		/// The minimum width of a Column
		/// </summary>
		public static readonly int MinimumWidth = ResizePadding * 2;

		/// <summary>
		/// Contains the current state of the the Column
		/// </summary>
		public byte state;

		/// <summary>
		/// The text displayed in the Column's header
		/// </summary>
		private string text;
		
		/// <summary>
		/// A string that specifies how a Column's Cell contents are formatted
		/// </summary>
		private string format;

		/// <summary>
		/// The alignment of the text displayed in the Column's Cells
		/// </summary>
		private ColumnAlignment alignment;

		/// <summary>
		/// The width of the Column
		/// </summary>
		private int width;

		/// <summary>
		/// The Image displayed on the Column's header
		/// </summary>
		private Image image;

		/// <summary>
		/// Specifies whether the Image displayed on the Column's header should 
		/// be draw on the right hand side of the Column
		/// </summary>
		private bool imageOnRight;

		/// <summary>
		/// The current state of the Column
		/// </summary>
		private ColumnState columnState;

		/// <summary>
		/// The text displayed when a ToolTip is shown for the Column's header
		/// </summary>
		private string tooltipText;

		/// <summary>
		/// The ColumnModel that the Column belongs to
		/// </summary>
		private ColumnModel columnModel;

		/// <summary>
		/// The x-coordinate of the column's left edge in pixels
		/// </summary>
		private int x;

		/// <summary>
		/// The current SortOrder of the Column
		/// </summary>
		private SortOrder sortOrder;

		/// <summary>
		/// The CellRenderer used to draw the Column's Cells
		/// </summary>
		private ICellRenderer renderer;

		/// <summary>
		/// The CellEditor used to edit the Column's Cells
		/// </summary>
		private ICellEditor editor;

		/// <summary>
		/// The Type of the IComparer used to compare the Column's Cells
		/// </summary>
		private Type comparer;

		#endregion
		

		#region Constructor
		
		/// <summary>
		/// Creates a new Column with default values
		/// </summary>
		public Column() : base()
		{
			this.Init();
		}


		/// <summary>
		/// Creates a new Column with the specified header text
		/// </summary>
		/// <param name="text">The text displayed in the column's header</param>
		public Column(string text) : base()
		{
			this.Init();

			this.text = text;
		}


		/// <summary>
		/// Creates a new Column with the specified header text and width
		/// </summary>
		/// <param name="text">The text displayed in the column's header</param>
		/// <param name="width">The column's width</param>
		public Column(string text, int width) : base()
		{
			this.Init();

			this.text = text;
			this.width = width;
		}


		/// <summary>
		/// Creates a new Column with the specified header text, width and visibility
		/// </summary>
		/// <param name="text">The text displayed in the column's header</param>
		/// <param name="width">The column's width</param>
		/// <param name="visible">Specifies whether the column is visible</param>
		public Column(string text, int width, bool visible) : base()
		{
			this.Init();

			this.text = text;
			this.width = width;
			this.Visible = visible;
		}


		/// <summary>
		/// Creates a new Column with the specified header text and image
		/// </summary>
		/// <param name="text">The text displayed in the column's header</param>
		/// <param name="image">The image displayed on the column's header</param>
		public Column(string text, Image image) : base()
		{
			this.Init();

			this.text = text;
			this.image = image;
		}


		/// <summary>
		/// Creates a new Column with the specified header text, image and width
		/// </summary>
		/// <param name="text">The text displayed in the column's header</param>
		/// <param name="image">The image displayed on the column's header</param>
		/// <param name="width">The column's width</param>
		public Column(string text, Image image, int width) : base()
		{
			this.Init();

			this.text = text;
			this.image = image;
			this.width = width;
		}


		/// <summary>
		/// Creates a new Column with the specified header text, image, width and visibility
		/// </summary>
		/// <param name="text">The text displayed in the column's header</param>
		/// <param name="image">The image displayed on the column's header</param>
		/// <param name="width">The column's width</param>
		/// <param name="visible">Specifies whether the column is visible</param>
		public Column(string text, Image image, int width, bool visible) : base()
		{
			this.Init();

			this.text = text;
			this.image = image;
			this.width = width;
			this.Visible = visible;
		}


		/// <summary>
		/// Initialise default values
		/// </summary>
		private void Init()
		{
			this.text = null;
			this.width = Column.DefaultWidth;
			this.columnState = ColumnState.Normal;
			this.alignment = ColumnAlignment.Left;
			this.image = null;
			this.imageOnRight = false;
			this.columnModel = null;
			this.x = 0;
			this.tooltipText = null;
			this.format = "";
			this.sortOrder = SortOrder.None;
			this.renderer = null;
			this.editor = null;
			this.comparer = null;

			this.state = (byte) (STATE_ENABLED | STATE_EDITABLE | STATE_VISIBLE | STATE_SELECTABLE | STATE_SORTABLE);
		}

		#endregion


		#region Methods

		/// <summary>
		/// Gets a string that specifies the name of the Column's default CellRenderer
		/// </summary>
		/// <returns>A string that specifies the name of the Column's default 
		/// CellRenderer</returns>
		public abstract string GetDefaultRendererName();


		/// <summary>
		/// Gets the Column's default CellRenderer
		/// </summary>
		/// <returns>The Column's default CellRenderer</returns>
		public abstract ICellRenderer CreateDefaultRenderer();


		/// <summary>
		/// Gets a string that specifies the name of the Column's default CellEditor
		/// </summary>
		/// <returns>A string that specifies the name of the Column's default 
		/// CellEditor</returns>
		public abstract string GetDefaultEditorName();


		/// <summary>
		/// Gets the Column's default CellEditor
		/// </summary>
		/// <returns>The Column's default CellEditor</returns>
		public abstract ICellEditor CreateDefaultEditor();


		/// <summary>
		/// Returns the state represented by the specified state flag
		/// </summary>
		/// <param name="flag">A flag that represents the state to return</param>
		/// <returns>The state represented by the specified state flag</returns>
		internal bool GetState(int flag)
		{
			return ((this.state & flag) != 0);
		}


		/// <summary>
		/// Sets the state represented by the specified state flag to the specified value
		/// </summary>
		/// <param name="flag">A flag that represents the state to be set</param>
		/// <param name="value">The new value of the state</param>
		internal void SetState(int flag, bool value)
		{
			this.state = (byte) (value ? (this.state | flag) : (this.state & ~flag));
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets or sets the text displayed on the Column header
		/// </summary>
		[Category("Appearance"),
		DefaultValue(null),
		Description("The text displayed in the column's header.")]
		public string Text
		{
			get
			{
				return this.text;
			}

			set
			{
				if (value == null)
				{
					value = "";
				}
				
				if (!value.Equals(this.text))
				{
					string oldText = this.text;
					
					this.text = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.TextChanged, oldText));
				}
			}
		}


		/// <summary>
		/// Gets or sets the string that specifies how a Column's Cell contents 
		/// are formatted
		/// </summary>
		[Category("Appearance"),
		DefaultValue(""),
		Description("A string that specifies how a column's cell contents are formatted.")]
		public string Format
		{
			get
			{
				return this.format;
			}

			set
			{
				if (value == null)
				{
					value = "";
				}

				if (!value.Equals(this.format))
				{
					string oldFormat = this.format;
					
					this.format = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.FormatChanged, oldFormat));
				}
			}
		}


		/// <summary>
		/// Gets or sets the horizontal alignment of the Column's Cell contents
		/// </summary>
		[Category("Appearance"),
		DefaultValue(ColumnAlignment.Left),
		Description("The horizontal alignment of the column's cell contents.")]
		public virtual ColumnAlignment Alignment
		{
			get
			{
				return this.alignment;
			}

			set
			{
				if (!Enum.IsDefined(typeof(ColumnAlignment), value)) 
				{
					throw new InvalidEnumArgumentException("value", (int) value, typeof(ColumnAlignment));
				}
					
				if (this.alignment != value)
				{
					ColumnAlignment oldAlignment = this.alignment;
					
					this.alignment = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.AlignmentChanged, oldAlignment));
				}
			}
		}


		/// <summary>
		/// Gets or sets the width of the Column
		/// </summary>
		[Category("Appearance"),
		Description("The width of the column.")]
		public int Width
		{
			get
			{
				return this.width;
			}

			set
			{
				if (this.width != value)
				{
					int oldWidth = this.Width;
					
					// Set the width, and check min & max
					this.width = Math.Min(Math.Max(value, MinimumWidth), MaximumWidth);
					
					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.WidthChanged, oldWidth));
				}
			}
		}


		/// <summary>
		/// Specifies whether the Width property should be serialized at 
		/// design time
		/// </summary>
		/// <returns>true if the Width property should be serialized, 
		/// false otherwise</returns>
		private bool ShouldSerializeWidth()
		{
			return this.Width != Column.DefaultWidth;
		}


		/// <summary>
		/// Gets or sets the Image displayed in the Column's header
		/// </summary>
		[Category("Appearance"),
		DefaultValue(null),
		Description("Ihe image displayed in the column's header")]
		public Image Image
		{
			get
			{
				return this.image;
			}

			set
			{
				if (this.image != value)
				{
					Image oldImage = this.Image;
					
					this.image = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.ImageChanged, oldImage));
				}
			}
		}


		/// <summary>
		/// Gets or sets whether the Image displayed on the Column's header should 
		/// be draw on the right hand side of the Column
		/// </summary>
		[Category("Appearance"),
		DefaultValue(false),
		Description("Specifies whether the image displayed on the column's header should be drawn on the right hand side of the column")]
		public bool ImageOnRight
		{
			get
			{
				return this.imageOnRight;
			}

			set
			{
				if (this.imageOnRight != value)
				{
					this.imageOnRight = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.ImageChanged, null));
				}
			}
		}


		/// <summary>
		/// Gets the state of the Column
		/// </summary>
		[Browsable(false),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ColumnState ColumnState
		{
			get
			{
				return this.columnState;
			}
		}


		/// <summary>
		/// Gets or sets the state of the Column
		/// </summary>
		internal ColumnState InternalColumnState
		{
			get
			{
				return this.ColumnState;
			}

			set
			{
				if (!Enum.IsDefined(typeof(ColumnState), value)) 
				{
					throw new InvalidEnumArgumentException("value", (int) value, typeof(ColumnState));
				}

				if (this.columnState != value)
				{
					ColumnState oldState = this.columnState;
					
					this.columnState = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.StateChanged, oldState));
				}
			}
		}


		/// <summary>
		/// Gets or sets the whether the Column is displayed
		/// </summary>
		[Category("Appearance"),
		DefaultValue(true),
		Description("Determines whether the column is visible or hidden.")]
		public bool Visible
		{
			get
			{
				return this.GetState(STATE_VISIBLE);
			}

			set
			{
				bool visible = this.Visible;
				
				this.SetState(STATE_VISIBLE, value);
				
				if (visible != value)
				{
					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.VisibleChanged, visible));
				}
			}
		}


		/// <summary>
		/// Gets or sets whether the Column is able to be sorted
		/// </summary>
		[Category("Appearance"),
		DefaultValue(true),
		Description("Determines whether the column is able to be sorted.")]
		public virtual bool Sortable
		{
			get
			{
				return this.GetState(STATE_SORTABLE);
			}

			set
			{
				bool sortable = this.Sortable;
				
				this.SetState(STATE_SORTABLE, value);
				
				if (sortable != value)
				{
					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.SortableChanged, sortable));
				}
			}
		}


		/// <summary>
		/// Gets or sets the user specified ICellRenderer that is used to draw the 
		/// Column's Cells
		/// </summary>
		[Browsable(false),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ICellRenderer Renderer
		{
			get
			{
				return this.renderer;
			}

			set
			{
				if (this.renderer != value)
				{
					this.renderer = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.RendererChanged, null));
				}
			}
		}


		/// <summary>
		/// Gets or sets the user specified ICellEditor that is used to edit the 
		/// Column's Cells
		/// </summary>
		[Browsable(false),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ICellEditor Editor
		{
			get
			{
				return this.editor;
			}

			set
			{
				if (this.editor != value)
				{
					this.editor = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.EditorChanged, null));
				}
			}
		}
		
		
		/// <summary>
		/// Gets or sets the user specified Comparer type that is used to edit the 
		/// Column's Cells
		/// </summary>
		[Browsable(false),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Type Comparer
		 {
			 get
			 {
				 return this.comparer;
			 }

			 set
			 {
				 if (this.comparer != value)
				 {
					 this.comparer = value;

					 this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.ComparerChanged, null));
				 }
			 }
		 }


		/// <summary>
		/// Gets the Type of the default Comparer used to compare the Column's Cells when 
		/// the Column is sorting
		/// </summary>
		[Browsable(false)]
		public abstract Type DefaultComparerType
		{
			get;
		}


		/// <summary>
		/// Gets the current SortOrder of the Column
		/// </summary>
		[Browsable(false)]
		public SortOrder SortOrder
		{
			get
			{
				return this.sortOrder;
			}
		}


		/// <summary>
		/// Gets or sets the current SortOrder of the Column
		/// </summary>
		internal SortOrder InternalSortOrder
		{
			get
			{
				return this.SortOrder;
			}

			set
			{
				if (!Enum.IsDefined(typeof(SortOrder), value)) 
				{
					throw new InvalidEnumArgumentException("value", (int) value, typeof(SortOrder));
				}

				if (this.sortOrder != value)
				{
					SortOrder oldOrder = this.sortOrder;
					
					this.sortOrder = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.SortOrderChanged, oldOrder));
				}
			}
		}


		/// <summary>
		/// Gets or sets a value indicating whether the Column's Cells contents 
		/// are able to be edited
		/// </summary>
		[Category("Appearance"),
		Description("Controls whether the column's cell contents are able to be changed by the user")]
		public virtual bool Editable
		{
			get
			{
				if (!this.GetState(STATE_EDITABLE))
				{
					return false;
				}

				return this.Visible && this.Enabled;
			}

			set
			{
				bool editable = this.GetState(STATE_EDITABLE);
				
				this.SetState(STATE_EDITABLE, value);
				
				if (editable != value)
				{
					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.EditableChanged, editable));
				}
			}
		}


		/// <summary>
		/// Specifies whether the Editable property should be serialized at 
		/// design time
		/// </summary>
		/// <returns>true if the Editable property should be serialized, 
		/// false otherwise</returns>
		private bool ShouldSerializeEditable()
		{
			return !this.GetState(STATE_EDITABLE);
		}


		/// <summary>
		/// Gets or sets a value indicating whether the Column's Cells can respond to 
		/// user interaction
		/// </summary>
		[Category("Appearance"),
		Description("Indicates whether the column's cells can respond to user interaction")]
		public bool Enabled
		{
			get
			{
				if (!this.GetState(STATE_ENABLED))
				{
					return false;
				}

				if (this.ColumnModel == null)
				{
					return true;
				}

				return this.ColumnModel.Enabled;
			}

			set
			{
				bool enabled = this.GetState(STATE_ENABLED);
				
				this.SetState(STATE_ENABLED, value);
				
				if (enabled != value)
				{
					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.EnabledChanged, enabled));
				}
			}
		}


		/// <summary>
		/// Specifies whether the Enabled property should be serialized at 
		/// design time
		/// </summary>
		/// <returns>true if the Enabled property should be serialized, 
		/// false otherwise</returns>
		private bool ShouldSerializeEnabled()
		{
			return !this.GetState(STATE_ENABLED);
		}


		/// <summary>
		/// Gets or sets a value indicating whether the Column's Cells can be selected
		/// </summary>
		[Category("Appearance"),
		DefaultValue(true),
		Description("Indicates whether the column's cells can be selected")]
		public virtual bool Selectable
		{
			get
			{
				return this.GetState(STATE_SELECTABLE);
			}

			set
			{
				bool selectable = this.Selectable;
				
				this.SetState(STATE_SELECTABLE, value);
				
				if (selectable != value)
				{
					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.SelectableChanged, selectable));
				}
			}
		}


		/// <summary>
		/// Gets or sets the ToolTip text associated with the Column
		/// </summary>
		[Category("Appearance"),
		DefaultValue(null),
		Description("The ToolTip text associated with the Column")]
		public string ToolTipText
		{
			get
			{
				return this.tooltipText;
			}

			set
			{
				if (value == null)
				{
					value = "";
				}
				
				if (!value.Equals(this.tooltipText))
				{
					string oldTip = this.tooltipText;
					
					this.tooltipText = value;

					this.OnPropertyChanged(new ColumnEventArgs(this, ColumnEventType.ToolTipTextChanged, oldTip));
				}
			}
		}


		/// <summary>
		/// Gets the x-coordinate of the column's left edge in pixels
		/// </summary>
		internal int X
		{
			get
			{
				return this.x;
			}
			
			set
			{
				this.x = value;
			}
		}


		/// <summary>
		/// Gets the x-coordinate of the column's left edge in pixels
		/// </summary>
		[Browsable(false)]
		public int Left
		{
			get
			{
				return this.X;
			}
		}


		/// <summary>
		/// Gets the x-coordinate of the column's right edge in pixels
		/// </summary>
		[Browsable(false)]
		public int Right
		{
			get
			{
				return this.Left + this.Width;
			}
		}


		/// <summary>
		/// Gets or sets the ColumnModel the Column belongs to
		/// </summary>
		protected internal ColumnModel ColumnModel
		{
			get
			{
				return this.columnModel;
			}

			set
			{
				this.columnModel = value;
			}
		}


		/// <summary>
		/// Gets the ColumnModel the Column belongs to.  This member is not 
		/// intended to be used directly from your code
		/// </summary>
		[Browsable(false)]
		public ColumnModel Parent
		{
			get
			{
				return this.ColumnModel;
			}
		}


		/// <summary>
		/// Gets whether the Column is able to raise events
		/// </summary>
		protected bool CanRaiseEvents
		{
			get
			{
				// check if the ColumnModel that the Colum belongs to is able to 
				// raise events (if it can't, the Colum shouldn't raise events either)
				if (this.ColumnModel != null)
				{
					return this.ColumnModel.CanRaiseEvents;
				}

				return true;
			}
		}

		#endregion


		#region Events

		/// <summary>
		/// Raises the PropertyChanged event
		/// </summary>
		/// <param name="e">A ColumnEventArgs that contains the event data</param>
		protected virtual void OnPropertyChanged(ColumnEventArgs e)
		{
			if (this.ColumnModel != null)
			{
				e.SetIndex(this.ColumnModel.Columns.IndexOf(this));	
			}
			
			if (this.CanRaiseEvents)
			{
				if (this.ColumnModel != null)
				{
					this.ColumnModel.OnColumnPropertyChanged(e);
				}
				
				if (PropertyChanged != null)
				{
					PropertyChanged(this, e);
				}
			}
		}

		#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
Architect
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions