Click here to Skip to main content
15,894,017 members
Articles / Desktop Programming / Win32

Another Flexible ListView Control

Rate me:
Please Sign up or sign in to vote.
4.59/5 (12 votes)
17 Jul 2010CPOL5 min read 37.7K   2.3K   47  
A highly object-oriented ListView control with varying-height items and support of complex data types
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Windows.Forms.VisualStyles;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;

namespace Guide.Controls
{
	public enum GCellAlignment
	{
		Left,
		Center,
		Right
	}

	/// <summary>
	/// Represents a GListView's column.
	/// </summary>
	public abstract class GListViewColumn
	{
		public const int MinimumColumnWidth = 30;
		public const int MaximumColumnWidth = Int16.MaxValue;

		private int minWidth;
		private int maxWidth;
		private int initialWidth;

		public GListViewColumn() : this("", GCellAlignment.Center) {}

		public GListViewColumn(string name, GCellAlignment alignment)
		{
			this.Name = name;
			this.Alignment = alignment;

			this.Resizable = true;
			this.minWidth = GListViewColumn.MinimumColumnWidth;
			this.maxWidth = GListViewColumn.MaximumColumnWidth;
			this.initialWidth = GListViewColumn.MinimumColumnWidth;
		}

		/// <summary>
		/// Gets or sets name of the column
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Gets or sets alignment of the column
		/// </summary>
		public GCellAlignment Alignment { get; set; }

		/// <summary>
		/// Determines whether the column can be resized.
		/// </summary>
		public bool Resizable { get; set; }

		/// <summary>
		/// Gets or sets minimum width of the column
		/// </summary>
		public int MinWidth
		{
			get
			{
				return minWidth;
			}
			set
			{
				if (value < GListViewColumn.MinimumColumnWidth)
					value = GListViewColumn.MinimumColumnWidth;

				if (value > this.MaxWidth)
					throw new ArgumentOutOfRangeException("value must not be greater than MaxWidth");

				this.minWidth = value;
			}
		}

		/// <summary>
		/// Gets or sets maximum width of the column
		/// </summary>
		public int MaxWidth
		{
			get
			{
				return maxWidth;
			}
			set
			{
				if (value > GListViewColumn.MaximumColumnWidth)
					value = GListViewColumn.MaximumColumnWidth;

				if (value < this.MinWidth)
					throw new ArgumentOutOfRangeException("value must not be less than MinWidth");

				

				this.maxWidth = value;
			}
		}

		/// <summary>
		/// Gets or sets initial width of the column
		/// </summary>
		public int InitialWidth
		{
			get
			{
				return this.initialWidth;
			}
			set
			{
				if (value < this.MinWidth)
					throw new ArgumentOutOfRangeException("value must not be less than MinWidth");

				if (value > this.MaxWidth)
					throw new ArgumentOutOfRangeException("value must not be greater than MaxWidth");

				this.initialWidth = value;
			}
		}

		/// <summary>
		/// Overridden by subclasses to supply default field value when a new item is created
		/// </summary>
		public abstract object DefaultFieldValue { get; }

		/// <summary>
		/// Asserts if field's data type is correct.
		/// </summary>
		/// <param name="field">Field</param>
		/// <returns>true if field's data type is correct</returns>
		public virtual bool AssertField(object field)
		{
			return true;
		}

		/// <summary>
		/// Overridden by subclasses to calculate the height needed by a cell.
		/// </summary>
		/// <param name="owner">The owner ListView</param>
		/// <param name="g">Graphics context</param>
		/// <param name="field">Corresponding field</param>
		/// <returns></returns>
		public abstract int MeasureCellHeight(Control owner, Graphics g, object field);

		/// <summary>
		/// Overridden by subclasses to calculate the desired width needed by a cell.
		/// </summary>
		/// <param name="owner">The owner ListView</param>
		/// <param name="g">Graphics context</param>
		/// <param name="field">Corresponding field</param>
		/// <returns></returns>
		public abstract int MeasureDesiredCellWidth(Control owner, Graphics g, object field);

		/// <summary>
		/// Overridden by subclasses to draw a cell.
		/// </summary>
		/// <param name="owner">The owner ListView</param>
		/// <param name="g">Graphics context</param>
		/// <param name="bounds">Bounds of the cell</param>
		/// <param name="field">Corresponding field</param>
		/// <param name="state">Item state</param>
		public abstract void DrawCell(Control owner, Graphics g, Rectangle bounds, object field, DrawItemState state);

		/// <summary>
		/// Overridden (if necessary) by subclasses to handle MouseClick event on a cell
		/// </summary>
		/// <param name="owner">The owner ListView</param>
		/// <param name="field">The corresponding field</param>
		/// <param name="e">Event arguments</param>
		public virtual void OnCellMouseClick(Control owner, ref object field, MouseEventArgs e) { }

		/// <summary>
		/// Overridden (if necessary) by subclasses to handle MouseDown event on a cell
		/// </summary>
		/// <param name="owner">The owner ListView</param>
		/// <param name="field">The corresponding field</param>
		/// <param name="e">Event arguments</param>
		public virtual void OnCellMouseDown(Control owner, ref object field, MouseEventArgs e) { }

		/// <summary>
		/// Overridden (if necessary) by subclasses to handle MouseUp event on a cell
		/// </summary>
		/// <param name="owner">The owner ListView</param>
		/// <param name="field">The corresponding field</param>
		/// <param name="e">Event arguments</param>
		public virtual void OnCellMouseUp(Control owner, ref object field, MouseEventArgs e) { }

		/// <summary>
		/// Copies this object's data into another object. Used by subclasses to clone themselves.
		/// </summary>
		/// <param name="obj">The object whose data is filled</param>
		public virtual void CloneInPlace(GListViewColumn obj)
		{
			obj.Name = this.Name;
			obj.Alignment = this.Alignment;
			obj.Resizable = this.Resizable;
			obj.MinWidth = this.MinWidth;
			obj.MaxWidth = this.MaxWidth;
			obj.InitialWidth = this.InitialWidth;
		}

		/// <summary>
		/// Overridden by subclasses to clone themselves.
		/// </summary>
		/// <returns>The copy of this object</returns>
		public abstract GListViewColumn Clone();
	}

	public class GListViewColumnSchema : List<GListViewColumn>
	{
		public GListViewColumnSchema() { }

		public GListViewColumnSchema Clone()
		{
			GListViewColumnSchema o = new GListViewColumnSchema();

			foreach (GListViewColumn obj in this)
				o.Add(obj.Clone());

			return o;
		}
	}

	/// <summary>
	/// Represents a column displaying CheckBoxes
	/// </summary>
	public sealed class GCheckBoxColumn : GListViewColumn
	{
		public const int SuggestedWidth = 30;

		public GCheckBoxColumn() : this("")	{}

		public GCheckBoxColumn(string name)
			: base(name, GCellAlignment.Center)
		{
			this.Resizable = false;
			this.InitialWidth = GCheckBoxColumn.SuggestedWidth;
		}

		public override object DefaultFieldValue
		{
			get
			{
				return false;
			}
		}

		public override bool AssertField(object field)
		{
			return field != null && field.GetType().Equals(typeof(bool));
		}

		public override int MeasureCellHeight(Control owner, Graphics g, object data)
		{
			return CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.CheckedNormal).Height;
		}

		public override int MeasureDesiredCellWidth(Control owner, Graphics g, object field)
		{
			return CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.CheckedNormal).Width;
		}

		public override void DrawCell(Control owner, Graphics g, Rectangle bounds, object field, DrawItemState state)
		{
			CheckBoxState checkState = (bool)field ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal;

			Size checkBoxGlyph = CheckBoxRenderer.GetGlyphSize(g, checkState);
			if (bounds.Width < checkBoxGlyph.Width)
				return;

			CheckBoxRenderer.DrawCheckBox(g, GListViewUtility.AlignInside(bounds, checkBoxGlyph, this.Alignment), checkState);
		}

		public override void OnCellMouseClick(Control owner, ref object field, MouseEventArgs e)
		{
			base.OnCellMouseClick(owner, ref field, e);

			bool v = (bool)field;
			field = !v;
		}

		public override GListViewColumn Clone()
		{
			GCheckBoxColumn obj = new GCheckBoxColumn();

			CloneInPlace(obj);

			return obj;
		}
	}

	/// <summary>
	/// Represents a column displaying images.
	/// </summary>
	public sealed class GImageColumn : GListViewColumn
	{
		public const int SuggestedMinWidth = 90;
		public const int SuggestedWidth = 150;
		public const string DefaultMissingMessage = "...";
		public static readonly Font DefaultMissingMessageFont = SystemFonts.DefaultFont;
		public static readonly Color DefaultMissingMessageForeColor = SystemColors.WindowText;
		
		private GImageColumn() {}

		public GImageColumn(string name) : this(name, GCellAlignment.Center) {}

		public GImageColumn(string name, GCellAlignment alignment)
			: base(name, alignment)
		{
			this.MinWidth = GImageColumn.SuggestedMinWidth;
			this.InitialWidth = GImageColumn.SuggestedWidth;

			this.InvertSelected = true;
			this.MissingMessage = GImageColumn.DefaultMissingMessage;
			this.MissingMessageFont = GImageColumn.DefaultMissingMessageFont;
			this.MissingMessageForeColor = GImageColumn.DefaultMissingMessageForeColor;
		}

		/// <summary>
		/// Gets or sets a value indicating if the image's color should be inverted when the item is selected.
		/// </summary>
		/// <returns>true if the image's color is inverted</returns>
		public bool InvertSelected { get; set; }

		/// <summary>
		/// Gets or sets a message displayed when there is not image (i.e. the image is null)
		/// </summary>
		public string MissingMessage { get; set; }

		/// <summary>
		/// Gets or sets the font of the missing message.
		/// </summary>
		public Font MissingMessageFont { get; set; }

		/// <summary>
		/// Gets or sets the color of the missing message.
		/// </summary>
		public Color MissingMessageForeColor { get; set; }

		public override object DefaultFieldValue
		{
			get
			{
				return null;
			}
		}

		public override bool AssertField(object field)
		{
			return field == null || field is Image;
		}

		public override int MeasureCellHeight(Control owner, Graphics g, object data)
		{
			Image image = data as Image;

			if (image == null)
			{
				return (int)this.MissingMessageFont.GetHeight(g);
			}
			else
			{
				return image.Height;
			}
		}

		public override int MeasureDesiredCellWidth(Control owner, Graphics g, object field)
		{
			Image image = field as Image;

			if (image == null)
			{
				return TextRenderer.MeasureText(g, this.MissingMessage, this.MissingMessageFont).Height;
			}
			else
			{
				return image.Width;
			}
		}

		public override void DrawCell(Control owner, Graphics g, Rectangle bounds, object field, DrawItemState state)
		{
			Image image = field as Image;
			bool selected = (state & DrawItemState.Selected) == DrawItemState.Selected;

			// No image
			if (image == null)
				return;

			// Not enough space
			if (bounds.Width < image.Width)
			{
				GListViewUtility.DrawText(g, bounds, "...", this.MissingMessageFont, this.Alignment, selected ? GListViewUtility.InvertColor(this.MissingMessageForeColor) : this.MissingMessageForeColor, true);
				return;
			}

			// Image
			if (selected && this.InvertSelected)
			{
				// Invert image's color
				ImageAttributes attrs = new ImageAttributes();
				attrs.SetColorMatrix(new ColorMatrix(new float[][] { 
					new float[] {-1, 0, 0, 0, 0},
					new float[] {0, -1, 0, 0, 0},
					new float[] {0, 0, -1, 0, 0},
					new float[] {0, 0, 0, 1, 0},
					new float[] {1, 1, 1, 0, +1}
				}), ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

				g.DrawImage(image, new Rectangle(GListViewUtility.AlignInside(bounds, image.Size, this.Alignment), image.Size), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attrs);
			}
			else
			{
				g.DrawImage(image, GListViewUtility.AlignInside(bounds, image.Size, this.Alignment));
			}
		}

		public override GListViewColumn Clone()
		{
			GImageColumn obj = new GImageColumn();

			CloneInPlace(obj);

			obj.InvertSelected = this.InvertSelected;
			obj.MissingMessage = this.MissingMessage;
			obj.MissingMessageFont = this.MissingMessageFont;
			obj.MissingMessageForeColor = this.MissingMessageForeColor;

			return obj;
		}
	}

	public sealed class GTextColumn : GListViewColumn
	{
		public const int SuggestedWidth = 75;
		public static readonly Font DefaultFont = SystemFonts.DefaultFont;
		public static readonly Color DefaultForeColor = SystemColors.WindowText;

		private GTextColumn() {}

		public GTextColumn(string name) : this(name, GCellAlignment.Center) {}

		public GTextColumn(string name, GCellAlignment alignment)
			: base(name, alignment)
		{
			this.InitialWidth = GTextColumn.SuggestedWidth;

			this.InvertSelected = true;
			this.Font = GTextColumn.DefaultFont;
			this.ForeColor = GTextColumn.DefaultForeColor;
			this.SingleLine = false;
		}

		/// <summary>
		/// Gets or sets a value indicating if the text's color should be inverted when the item is selected.
		/// </summary>
		/// <returns>true if color is inverted</returns>
		public bool InvertSelected { get; set; }

		/// <summary>
		/// Gets or sets the font used for drawing the text.
		/// </summary>
		public Font Font { get; set; }

		/// <summary>
		/// Gets or sets the text's color.
		/// </summary>
		public Color ForeColor { get; set; }

		/// <summary>
		/// Gets or sets a value indicating if the text should be drawn in a single line.
		/// </summary>
		public bool SingleLine { get; set; }

		public override object DefaultFieldValue
		{
			get
			{
				return "";
			}
		}

		public override bool AssertField(object field)
		{
			return field != null && field is String;
		}

		public override int MeasureCellHeight(Control owner, Graphics g, object data)
		{
			return (int)this.Font.GetHeight(g);
		}

		public override int MeasureDesiredCellWidth(Control owner, Graphics g, object field)
		{
			string text = (string)field;

			return TextRenderer.MeasureText(g, text, this.Font).Width;
		}

		public override void DrawCell(Control owner, Graphics g, Rectangle bounds, object field, DrawItemState state)
		{
			string text = (string)field;
			bool selected = (state & DrawItemState.Selected) == DrawItemState.Selected;

			GListViewUtility.DrawText(g, bounds, text, this.Font, this.Alignment, selected ? GListViewUtility.InvertColor(this.ForeColor) : this.ForeColor, this.SingleLine);
		}

		public override GListViewColumn Clone()
		{
			GTextColumn obj = new GTextColumn();

			CloneInPlace(obj);

			obj.InvertSelected = this.InvertSelected;
			obj.Font = this.Font;
			obj.ForeColor = this.ForeColor;
			obj.SingleLine = this.SingleLine;

			return obj;
		}
	}

	public class GListViewUtility
	{
		static public void DrawText(Graphics g, Rectangle bounds, string text, Font font, GCellAlignment alignment, Color color, bool singleline)
		{
			TextFormatFlags flags = TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis | TextFormatFlags.PreserveGraphicsTranslateTransform;

			if (singleline)
				flags |= TextFormatFlags.SingleLine;
			
			switch (alignment)
			{
				case GCellAlignment.Left:
					flags |= TextFormatFlags.Left;
					break;

				case GCellAlignment.Center:
					flags |= TextFormatFlags.HorizontalCenter;
					break;

				case GCellAlignment.Right:
					flags |= TextFormatFlags.Right;
					break;
			}

			TextRenderer.DrawText(g, text, font, bounds, color, flags);
		}

		static public Point AlignInside(Rectangle outer, Size inner, GCellAlignment alignment)
		{
			switch (alignment)
			{
				case GCellAlignment.Left:
					return new Point(outer.Left, outer.Top + (outer.Height - inner.Height) / 2);

				case GCellAlignment.Center:
					return new Point(outer.Left + (outer.Width - inner.Width) / 2, outer.Top + (outer.Height - inner.Height) / 2);

				case GCellAlignment.Right:
					return new Point(outer.Right - inner.Width, outer.Top + (outer.Height - inner.Height) / 2);

				default:
					throw new Exception("Error!");
			}
		}

		static public Color InvertColor(Color color)
		{
			return Color.FromArgb(255 - color.R, 255 - color.G, 255 - color.B);
		}
	}

	class GListViewSettings
	{
		public const string HeaderBackColor = "Control";
		public const string HeaderHighlightBackColor = "ControlLight";
		public const string HeaderPressedBackColor = "Control";
		public const string HeaderForeColor = "ControlText";
		public static readonly Font HeaderFont = SystemFonts.DefaultFont;
		public const int HeaderHeight = 25;
		public const string BorderColor = "ControlDark";
		public const string DraggedSplitterColor = "Black";
		public const int SplitterSensitivity = 2;
		
		public const string SelectionBackColor = "Highlight";
		public const string GridLineColor = "ControlLight";
		public const int CellPadding = 3;
		public const int MinRowHeight = 20;
	}

	public class GHeaderClickEventArgs : EventArgs
	{
		private int iHeader;

		internal GHeaderClickEventArgs(int iHeader)
		{
			this.iHeader = iHeader;
		}

		public int HeaderIndex
		{
			get
			{
				return iHeader;
			}
		}
	}

	public delegate void GHeaderClickEventHandler(object sender, GHeaderClickEventArgs e);

	public abstract class GListView<T> : UserControl where T : class
	{
		class FlickerFreePanel : Panel
		{
			private bool cursorInside = false;

			public FlickerFreePanel()
			{
				this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
			}

			protected override void OnMouseEnter(EventArgs e)
			{
				base.OnMouseEnter(e);

				cursorInside = true;
			}

			protected override void OnMouseLeave(EventArgs e)
			{
				base.OnMouseLeave(e);

				cursorInside = false;
			}

			public bool CursorInside
			{
				get
				{
					return cursorInside;
				}
			}
		}

		/// <summary>
		/// Represents a ListItem used by GListView
		/// </summary>
		class GListItem
		{
			public GListItem(int fieldsCount)
			{
				this.Fields = new object[fieldsCount];
			}

			public object[] Fields { get; set; }

			public object Object { get; set; }
		}


		// Controls
		private FlickerFreePanel pnHeaders = new FlickerFreePanel();
		private ProperListBox lstItems = new ProperListBox();
		
		// Columns
		private GListViewColumnSchema columns;
		private int[] splitterPositions;
		private int iCheckBoxColumn = -1;
		private int iHotHeader = -1;
		private bool headerPressed = false;

		// Objects
		private List<T> objects = new List<T>();

		// State variables
		private int iDraggedSplitter = -1;
		private int xDraggedSplitter = 0;
		private bool splitterBeingDoubleClicked = false;
		
		/// <summary>
		/// Creates a new GListView
		/// </summary>
		public GListView()
		{
			this.BorderStyle = BorderStyle.Fixed3D;

			InitializeElements();

			this.HeaderBackColor = Color.FromName(GListViewSettings.HeaderBackColor);
			this.HeaderHighlightBackColor = Color.FromName(GListViewSettings.HeaderHighlightBackColor);
			this.HeaderPressedBackColor = Color.FromName(GListViewSettings.HeaderPressedBackColor);
			this.HeaderForeColor = Color.FromName(GListViewSettings.HeaderForeColor);
			this.HeaderFont = GListViewSettings.HeaderFont;
			this.HeaderHeight = GListViewSettings.HeaderHeight;
			this.BorderColor = Color.FromName(GListViewSettings.BorderColor);
			this.DraggedSplitterColor = Color.FromName(GListViewSettings.DraggedSplitterColor);

			this.SelectionBackgroundColor = Color.FromName(GListViewSettings.SelectionBackColor);
			this.GridLineColor = Color.FromName(GListViewSettings.GridLineColor);
			this.CellPadding = GListViewSettings.CellPadding;
			this.MinRowHeight = GListViewSettings.MinRowHeight;
			this.GridLines = true;

			this.SelectionMode = SelectionMode.MultiExtended;
			this.AssertsFields = true;
		}

		private void InitializeElements()
		{
			// Header Panel
			pnHeaders.Dock = DockStyle.Top;
			pnHeaders.Height = 25;

			pnHeaders.Paint += new PaintEventHandler(pnHeaders_Paint);
			pnHeaders.MouseDoubleClick += new MouseEventHandler(pnHeaders_MouseDoubleClick);
			pnHeaders.MouseMove += new MouseEventHandler(pnHeaders_MouseMove);
			pnHeaders.MouseDown += new MouseEventHandler(pnHeaders_MouseDown);
			pnHeaders.MouseUp += new MouseEventHandler(pnHeaders_MouseUp);
			pnHeaders.MouseLeave += new EventHandler(pnHeaders_MouseLeave);

			this.Controls.Add(pnHeaders);

			// ListBox
			lstItems.BorderStyle = BorderStyle.None;
			lstItems.IntegralHeight = false;
			lstItems.Left = 0;
			lstItems.Top = pnHeaders.Bottom;
			lstItems.Width = this.Width;
			lstItems.Height = this.Height - lstItems.Top;
			lstItems.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
			lstItems.DrawMode = DrawMode.OwnerDrawVariable;

			lstItems.SelectedIndexChanged += new EventHandler(lstItems_SelectedIndexChanged);

			lstItems.DrawItem += new DrawItemEventHandler(lstItems_DrawItem);
			lstItems.MeasureItem += new MeasureItemEventHandler(lstItems_MeasureItem);
			lstItems.Paint += new PaintEventHandler(lstItems_Paint);

			lstItems.MouseClick += new MouseEventHandler(lstItems_MouseClick);
			lstItems.MouseUp += new MouseEventHandler(lstItems_MouseUp);
			lstItems.MouseDown += new MouseEventHandler(lstItems_MouseDown);
			lstItems.MouseWheel += new MouseEventHandler(lstItems_MouseWheel);

			lstItems.HorizontalScrolled += new EventHandler(lstItems_HorizontalScrolled);

			lstItems.Click += new EventHandler(lstItems_Click);
			lstItems.DoubleClick += new EventHandler(lstItems_DoubleClick);

			this.Controls.Add(lstItems);
		}

		// Properties
		/// <summary>
		/// Gets the column schema of this ListView.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public GListViewColumnSchema Columns
		{
			get
			{
				return columns;
			}
		}

		/// <summary>
		/// Sets the column schema of this ListView. This clears current columns and items.
		/// This method is designed to be called immediately after creation of the ListView.
		/// </summary>
		/// <param name="columns"></param>
		public void SetColumns(GListViewColumnSchema columns)
		{
			this.columns = columns.Clone();
			this.splitterPositions = new int[this.Columns.Count];

			ReconstructColumns();
		}

		/// <summary>
		/// Gets a value indicating if CheckBoxes are supported by current column schema.
		/// (It is supported if at least one column is of type GCheckBoxColumn)
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool CheckBoxes
		{
			get
			{
				return iCheckBoxColumn >= 0;
			}
		}

		/// <summary>
		/// Gets or sets splitter's color.
		/// </summary>
		[Category("ListView")]
		[DefaultValue(typeof(Color), GListViewSettings.BorderColor)]
		public Color BorderColor { get; set; }

		/// <summary>
		/// Gets or sets header's background color.
		/// </summary>
		[Category("ListView.Headers")]
		[DefaultValue(typeof(Color), GListViewSettings.HeaderBackColor)]
		public Color HeaderBackColor { get; set; }

		/// <summary>
		/// Gets or sets header's highlight background color.
		/// </summary>
		[Category("ListView.Headers")]
		[DefaultValue(typeof(Color), GListViewSettings.HeaderHighlightBackColor)]
		public Color HeaderHighlightBackColor { get; set; }

		/// <summary>
		/// Gets or sets header's pressed background color.
		/// </summary>
		[Category("ListView.Headers")]
		[DefaultValue(typeof(Color), GListViewSettings.HeaderPressedBackColor)]
		public Color HeaderPressedBackColor { get; set; }

		/// <summary>
		/// Gets or sets the header's text color.
		/// </summary>
		[Category("ListView.Headers")]
		[DefaultValue(typeof(Color), GListViewSettings.HeaderForeColor)]
		public Color HeaderForeColor { get; set; }

		/// <summary>
		/// Gets or sets the font used for headers.
		/// </summary>
		[Category("ListView.Headers")]
		public Font HeaderFont { get; set; }

		/// <summary>
		/// Gets or sets headers' height.
		/// </summary>
		[Category("ListView.Headers")]
		[DefaultValue(GListViewSettings.HeaderHeight)]
		public int HeaderHeight
		{
			get
			{
				return pnHeaders.Height;
			}
			set
			{
				pnHeaders.Height = value;
			}
		}

		/// <summary>
		/// Gets or sets dragged splitter's color.
		/// </summary>
		[Category("ListView.Headers")]
		[DefaultValue(typeof(Color), GListViewSettings.DraggedSplitterColor)]
		public Color DraggedSplitterColor { get; set; }

		/// <summary>
		/// Gets or sets the ListView's background color of selected items.
		/// </summary>
		[Category("ListView.Items")]
		[DefaultValue(typeof(Color), GListViewSettings.SelectionBackColor)]
		public Color SelectionBackgroundColor { get; set; }

		/// <summary>
		/// Gets or sets color of grid lines.
		/// </summary>
		[Category("ListView.Items")]
		[DefaultValue(typeof(Color), GListViewSettings.GridLineColor)]
		public Color GridLineColor { get; set; }

		/// <summary>
		/// Gets or sets a value indicating if grid lines are displayed
		/// </summary>
		/// <returns>
		/// true if grid lines are shown
		/// </returns>
		[Category("ListView.Items")]
		[DefaultValue(true)]
		public bool GridLines { get; set; }

		/// <summary>
		/// Gets or sets how much space should be padded for each cell.
		/// </summary>
		[Category("ListView.Items")]
		[DefaultValue(GListViewSettings.CellPadding)]
		public int CellPadding { get; set; }

		/// <summary>
		/// Gets or sets minimum row height.
		/// </summary>
		[Category("ListView.Items")]
		[DefaultValue(GListViewSettings.MinRowHeight)]
		public int MinRowHeight { get; set; }

		/// <summary>
		/// Gets or sets the method in which items are selected in the ListView.
		/// </summary>
		/// <returns>
		/// One of the System.Windows.Forms.SelectionMode values. The default is SelectionMode.One.
		/// </returns>
		[Category("ListView.Items")]
		[DefaultValue(SelectionMode.MultiExtended)]
		public SelectionMode SelectionMode
		{
			get
			{
				return lstItems.SelectionMode;
			}
			set
			{
				lstItems.SelectionMode = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating if fields should be asserted by Columns when objects are translated
		/// </summary>
		/// <returns>true if fields should be asserted. False otherwise.</returns>
		/// <remarks>Disabling this will improve performance but invalid data will be allowed, whose sources will be hard to trace out</remarks>
		[Category("ListView.Behavior")]
		[DefaultValue(true)]
		public bool AssertsFields { get; set; }

		// Column Operations
		private int GetColumnWidth(int index)
		{
			if (splitterPositions.Length == 0)
				return pnHeaders.Width;

			if (index == 0)
				return splitterPositions[0];

			return splitterPositions[index] - splitterPositions[index - 1];
		}

		private int GetColumnStart(int index)
		{
			if (index == 0)
				return 0;
			else
				return splitterPositions[index - 1];
		}

		private int GetColumnEnd(int index)
		{
			return splitterPositions[index];
		}

		private void SetColumnSizes(int[] widths)
		{
			int pos = 0;

			for (int i = 0; i < this.Columns.Count; i++)
			{
				pos += widths[i];
				splitterPositions[i] = pos;
			}
		}

		/// <summary>
		/// Gets the index of column which is used as Check column (i.e. it determines which items are checked).
		/// </summary>
		[Browsable(false)]
		public int CheckColumnIndex
		{
			get
			{
				return iCheckBoxColumn;
				
			}
		}

		private void ReconstructColumns()
		{
			InitializeColumnSizes();

			FindCheckColumn();

			ValidateHScrollBar();
		}

		private void InitializeColumnSizes()
		{
			int[] widths = new int[this.Columns.Count];

			for (int i = 0; i < this.Columns.Count; i++)
				widths[i] = this.Columns[i].InitialWidth;

			SetColumnSizes(widths);
		}

		private void FindCheckColumn()
		{
			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
				if (this.Columns[iColumn] is GCheckBoxColumn)
				{
					iCheckBoxColumn = iColumn;
					break;
				}
		}

		// Item Operations
		private GListItem CreateListItem()
		{
			GListItem item = new GListItem(this.Columns.Count);

			item.Fields = new object[this.Columns.Count];

			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
			{
				item.Fields[iColumn] = this.Columns[iColumn].DefaultFieldValue;
			}

			return item;
		}

		/// <summary>
		/// Adds a new object to the ListView.
		/// </summary>
		/// <param name="obj">The object to be added</param>
		public void AddItem(T obj)
		{
			if (obj == null)
				throw new ArgumentNullException();

			objects.Add(obj);

			lstItems.Items.Add(Translate(obj));

			lstItems.Invalidate();
		}

		/// <summary>
		/// Adds multiple objects into the ListView.
		/// </summary>
		/// <param name="obj">The object to be added</param>
		public void AddItems(ICollection<T> objects)
		{
			foreach (T obj in objects)
				AddItem(obj);
		}

		/// <summary>
		/// Replace an existing object in the ListView.
		/// </summary>
		/// <param name="oldObj">The object to be replaced for</param>
		/// <param name="newObj">The new object</param>
		public void ReplaceItem(T oldObj, T newObj)
		{
			for (int iItem = 0; iItem < lstItems.Items.Count; iItem++)
			{
				GListItem item = lstItems.Items[iItem] as GListItem;
				if (item.Object.Equals(oldObj))
				{
					ReplaceItem(iItem, newObj);

					break;
				}
			}
		}

		/// <summary>
		/// Replace an existing object by specifying its index in the ListView.
		/// </summary>
		/// <param name="index">Index of the object</param>
		/// <param name="obj">The new object</param>
		public void ReplaceItem(int index, T newObj)
		{
			try
			{
				GListItem item = lstItems.Items[index] as GListItem;

				item.Object = newObj;
				Translate(newObj, item.Fields);

				lstItems.Invalidate();
			}
			catch (ArgumentOutOfRangeException ex)
			{
				throw ex;
			}
		}

		/// <summary>
		/// Removes an object from the ListView.
		/// </summary>
		/// <param name="obj">The object to be removed</param>
		public void RemoveItem(T obj)
		{
			if (objects.Contains(obj))
			{
				objects.Remove(obj);

				foreach (GListItem item in lstItems.Items)
				{
					if (item.Object.Equals(obj))
					{
						lstItems.Items.Remove(item);
						break;
					}
				}
			}

			lstItems.Invalidate();
		}

		/// <summary>
		/// Removes some objects from the ListView.
		/// </summary>
		/// <param name="objects">The objects to be removed</param>
		public void RemoveItems(IList<T> objects)
		{
			foreach (T obj in objects)
				RemoveItem(obj);
		}

		/// <summary>
		/// Removes an object from the ListView by specifying its index.
		/// </summary>
		/// <param name="index">The index of the object which will be removed</param>
		public void RemoveItemAt(int index)
		{
			try
			{
				objects.Clear();
				lstItems.Items.RemoveAt(index);
			}
			catch (IndexOutOfRangeException ex)
			{
				throw ex;
			}
		}

		/// <summary>
		/// Removes all objects from the ListView.
		/// </summary>
		public void ClearItems()
		{
			objects.Clear();
			lstItems.Items.Clear();
		}

		/// <summary>
		/// Update all objects in the ListView.
		/// </summary>
		public void UpdateItems()
		{
			foreach (GListItem item in lstItems.Items)
				Translate(item.Object as T, item.Fields);

			lstItems.Invalidate();
		}

		/// <summary>
		/// Gets an object at specified index
		/// </summary>
		/// <param name="index">Where to get</param>
		/// <returns>The desired item</returns>
		public T GetItem(int index)
		{
			try
			{
				GListItem item = lstItems.Items[index] as GListItem;

				return item.Object as T;
			}
			catch (ArgumentOutOfRangeException ex)
			{
				throw ex;
			}
		}

		/// <summary>
		/// Get all items of the ListView
		/// </summary>
		/// <returns></returns>
		public List<T> GetItems()
		{
			List<T> objs = new List<T>();

			foreach (GListItem item in this.lstItems.Items)
				objs.Add(item.Object as T);

			return objs;
		}

		/// <summary>
		/// Gets the number of items in the ListView
		/// </summary>
		/// <returns>Number of items</returns>
		public int CountItems()
		{
			return lstItems.Items.Count;
		}

		/// <summary>
		/// Selects or deselects an object
		/// </summary>
		/// <param name="obj">The object to be selected</param>
		public void SelectItem(T obj, bool selected)
		{
			List<T> selection = this.SelectedItems;
			if (!selected && selection.Contains(obj))
				selection.Remove(obj);

			if (selected && !selection.Contains(obj))
				selection.Add(obj);

			this.SelectedItems = selection;
		}

		/// <summary>
		/// Checks or unchecks an object
		/// </summary>
		/// <param name="obj">The object to be selected</param>
		public void CheckItem(T obj, bool value)
		{
			List<T> checkedItems = this.CheckedItems;
			if (!value && checkedItems.Contains(obj))
				checkedItems.Remove(obj);

			if (value && !checkedItems.Contains(obj))
				checkedItems.Add(obj);

			this.CheckedItems = checkedItems;
		}

		/// <summary>
		/// Gets or sets selected items from the ListView.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public List<T> SelectedItems
		{
			get
			{
				List<T> objs = new List<T>();

				foreach (GListItem item in lstItems.SelectedItems)
					objs.Add(item.Object as T);

				return objs;
			}
			set
			{
				// Must de-select items before selecting items
				List<int> nonSelection = new List<int>();
				List<int> selection = new List<int>();

				for (int iItem = 0; iItem < lstItems.Items.Count; iItem++)
				{
					bool selected = false;

					GListItem item = lstItems.Items[iItem] as GListItem;

					foreach (T obj in value)
					{
						if (obj == null)
							throw new ArgumentNullException();

						if (obj.Equals(item.Object))
						{
							selected = true;
							break;
						}
					}

					(selected ? selection : nonSelection).Add(iItem);
				}

				foreach (int iItem in nonSelection)
					lstItems.SetSelected(iItem, false);

				foreach (int iItem in selection)
					lstItems.SetSelected(iItem, true);
			}
		}

		/// <summary>
		/// Gets or sets selected indices of the ListView
		/// </summary>
		/// <remarks>The returned collected is sorted</remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public List<int> SelectedIndices
		{
			get
			{
				List<int> selected = new List<int>();

				foreach (int iItem in lstItems.SelectedIndices)
					selected.Add(iItem);

				selected.Sort();

				return selected;
			}
			set
			{
				List<int> notSelected = new List<int>();

				for (int i = 0; i < CountItems(); i++)
					if (!value.Contains(i))
						notSelected.Add(i);

				foreach (int iItem in notSelected)
					lstItems.SetSelected(iItem, false);

				foreach (int iItem in value)
					lstItems.SetSelected(iItem, true);
			}
		}

		/// <summary>
		/// Gets or sets checked items from the ListView.
		/// </summary>
		/// <remarks>
		/// This property is useful only if the ListView's column schema supports CheckBoxes.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public List<T> CheckedItems
		{
			get
			{
				if (iCheckBoxColumn >= 0)
				{
					List<T> objs = new List<T>();

					foreach (GListItem item in lstItems.Items)
						if ((bool)item.Fields[iCheckBoxColumn])
							objs.Add(item.Object as T);

					return objs;
				}
				else
				{
					return new List<T>();
				}
			}
			set
			{
				if (iCheckBoxColumn >= 0)
				{
					for (int iItem = 0; iItem < CountItems(); iItem++)
					{
						GListItem item = lstItems.Items[iItem] as GListItem;
						item.Fields[iCheckBoxColumn] = value.Contains(item.Object as T);
					}

					lstItems.Invalidate();
				}
			}
		}

		private GListItem Translate(T obj)
		{
			GListItem item = CreateListItem();
			item.Object = obj;

			Translate(obj, item.Fields);
			AssertFields(item.Fields);

			return item;
		}

		private void AssertFields(object[] fields)
		{
			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
				if (!this.Columns[iColumn].AssertField(fields[iColumn]))
					throw new Exception("Field's data type is not correct.");
		}

		/// <summary>
		/// Overridden by subclasses to translate from an object to represented form.
		/// </summary>
		/// <param name="obj">The object to be translated</param>
		/// <param name="fields">The array of fields to be filled by displayed data</param>
		protected abstract void Translate(T obj, object[] fields);

		// Events
		/// <summary>
		/// Occurs when some items are selected or deselected.
		/// </summary>
		[Category("ListView")]
		public event EventHandler SelectedItemsChanged;
		
		protected virtual void OnSelectedItemsChanged(EventArgs e)
		{
			if (SelectedItemsChanged != null)
				SelectedItemsChanged(this, e);
		}

		/// <summary>
		/// Occurs when some items are checked or unchecked.
		/// </summary>
		[Category("ListView")]
		public event EventHandler CheckedItemsChanged;

		protected virtual void OnCheckedItemsChanged(EventArgs e)
		{
			if (CheckedItemsChanged != null)
				CheckedItemsChanged(this, e);
		}

		/// <summary>
		/// Occurs when a header is clicked.
		/// </summary>
		[Category("ListView")]
		public event GHeaderClickEventHandler HeaderClick;

		protected virtual void OnHeaderClick(GHeaderClickEventArgs e)
		{
			if (this.HeaderClick != null)
				this.HeaderClick(this, e);
		}

		/// <summary>
		/// Occurs when an item is clicked.
		/// </summary>
		/// <remarks>The clicked item can be acquired using SelectedItems property</remarks>
		public event EventHandler ItemClick;

		protected virtual void OnItemClick(EventArgs e)
		{
			if (ItemClick != null)
				ItemClick(this, e);
		}

		/// <summary>
		/// Occurs when an item is double clicked.
		/// </summary>
		/// <remarks>The clicked item can be acquired using SelectedItems property</remarks>
		public event EventHandler ItemDoubleClick;

		protected virtual void OnItemDoubleClick(EventArgs e)
		{
			if (ItemDoubleClick != null)
				ItemDoubleClick(this, e);
		}

		/// <summary>
		/// Occurs when a key is down
		/// </summary>
		public new event KeyEventHandler KeyDown
		{
			add
			{
				lstItems.KeyDown += value;
			}
			remove
			{
				lstItems.KeyDown -= value;
			}
		}

		/// <summary>
		/// Occurs when a key is up
		/// </summary>
		public new event KeyEventHandler KeyUp
		{
			add
			{
				lstItems.KeyUp += value;
			}
			remove
			{
				lstItems.KeyUp -= value;
			}
		}

		/// <summary>
		/// Occurs when a key is pressed.
		/// </summary>
		public new event KeyPressEventHandler KeyPress
		{
			add
			{
				lstItems.KeyPress += value;
			}
			remove
			{
				lstItems.KeyPress -= value;
			}
		}

		// Serialization

		/// <summary>
		/// Serializes column sizes into a byte array.
		/// </summary>
		/// <returns>Serialized column size information</returns>
		/// <remarks>This method is designed for easily storing ListView settings in persistent memory</remarks>
		public byte[] SerializeColumnSizes()
		{
			MemoryStream bytes = new MemoryStream();
			BinaryWriter w = new BinaryWriter(bytes);

			w.Write(this.Columns.Count);

			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
			{
				w.Write(GetColumnWidth(iColumn));
			}

			w.Close();

			return bytes.ToArray();
		}

		/// <summary>
		/// Deserializes column sizes from a byte array.
		/// </summary>
		/// <param name="bytes">The serialized column sizes</param>
		/// <remarks>This method is designed for easily storing ListView settings in persistent memory</remarks>
		public void DeserializeColumnSizes(byte[] bytes)
		{
			if (bytes == null)
				return;

			MemoryStream mem = new MemoryStream(bytes);
			BinaryReader r = new BinaryReader(mem);

			int n = r.ReadInt32();
			if (n != this.Columns.Count)
			{
				Debug.WriteLine("[GListView] DeserializeColumnSizes() failed because number of columns doesn't match.");
				return;
			}

			int[] widths = new int[n];
			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
			{
				widths[iColumn] = r.ReadInt32();
			}

			SetColumnSizes(widths);

			r.Close();
		}

		// Headers
		private void pnHeaders_MouseDoubleClick(object sender, MouseEventArgs e)
		{
			int iColumn = FindSplitterAt(lstItems.HScrollPosition + e.X);
			if (iColumn >= 0)
			{
				// Ensure that MouseUp doesn't intervene this...
				splitterBeingDoubleClicked = true;

				GListViewColumn column = this.Columns[iColumn];
				Graphics g = lstItems.CreateGraphics();
				int w = column.MinWidth;

				foreach (GListItem item in lstItems.Items)
				{
					int desired = column.MeasureDesiredCellWidth(this, g, item.Fields[iColumn]);
					if (w < desired)
						w = desired;
				}

				w += 2 * this.CellPadding;

				int dx = w - GetColumnWidth(iColumn);
				int x = splitterPositions[iColumn] + dx;

				RestrictSplitterPosition(iColumn, ref x);

				MoveSplitter(iColumn, x - splitterPositions[iColumn]);
			}
		}

		private void pnHeaders_MouseDown(object sender, MouseEventArgs e)
		{
			int x = e.X + lstItems.HScrollPosition;

			if (e.Button == MouseButtons.Left)
			{
				// Splitter
				iDraggedSplitter = FindSplitterAt(x);

				if (iDraggedSplitter >= 0)
				{
					xDraggedSplitter = x;
				}
				else
				{
					if (iHotHeader != -1)
					{
						if (!headerPressed)
						{
							headerPressed = true;
							pnHeaders.Invalidate();
						}
					}
				}
			}
		}

		private void pnHeaders_MouseUp(object sender, MouseEventArgs e)
		{
			if (splitterBeingDoubleClicked)
			{
				splitterBeingDoubleClicked = false;
				iDraggedSplitter = -1;
				return;
			}

			if (iDraggedSplitter >= 0)
			{
				int dx = xDraggedSplitter - splitterPositions[iDraggedSplitter];

				MoveSplitter(iDraggedSplitter, dx);

				iDraggedSplitter = -1;
			}
			else
			{
				if (iHotHeader != -1)
				{
					if (headerPressed)
					{
						headerPressed = false;
						pnHeaders.Invalidate();

						OnHeaderClick(new GHeaderClickEventArgs(iHotHeader));
					}
				}
			}
		}

		private void pnHeaders_MouseMove(object sender, MouseEventArgs e)
		{
			// Actual position due to scrolling
			int x = e.X + lstItems.HScrollPosition;

			// Highlight header
			int iOldHoveredHeader = iHotHeader;
			iHotHeader = FindHeaderAt(x);
			if (iHotHeader != iOldHoveredHeader)
				pnHeaders.Invalidate();


			// Change Cursor
			if ((!headerPressed) && (iDraggedSplitter >= 0 || FindSplitterAt(x) >= 0))
				pnHeaders.Cursor = Cursors.VSplit;
			else
				pnHeaders.Cursor = this.DefaultCursor;

			if (iDraggedSplitter >= 0)
			{
				xDraggedSplitter = x;
				RestrictSplitterPosition(iDraggedSplitter, ref xDraggedSplitter);

				pnHeaders.Invalidate();
				lstItems.Invalidate();
			}
		}

		private void pnHeaders_MouseLeave(object sender, EventArgs e)
		{
			this.Cursor = this.DefaultCursor;

			if (!headerPressed)
			{
				if (iHotHeader != -1)
				{
					iHotHeader = -1;
					pnHeaders.Invalidate();
				}
			}
		}

		private void RestrictSplitterPosition(int iSplitter, ref int x)
		{
			GListViewColumn column = Columns[iSplitter];

			if (column.Resizable)
			{
				int xColumn = GetColumnStart(iSplitter);
				int min = xColumn + column.MinWidth;
				int max = xColumn + column.MaxWidth;

				if (x < min)
					x = min;

				if (x > max)
					x = max;
			}
			else
			{
				x = GetColumnEnd(iSplitter);
			}
		}

		private void ValidateHScrollBar()
		{
			int edge = (int)splitterPositions[splitterPositions.Length - 1];
			if (edge > this.Width)
			{
				lstItems.HorizontalScrollbar = true;
				lstItems.HorizontalExtent = edge;
			}
			else
			{
				lstItems.HorizontalScrollbar = false;
				lstItems.HorizontalExtent = 0;
			}
		}

		private int FindSplitterAt(int x)
		{
			for (int i = 0; i < splitterPositions.Length; i++)
			{
				if (Math.Abs(x - splitterPositions[i]) <= GListViewSettings.SplitterSensitivity)
				{
					return i;
				}
			}

			return -1;
		}

		private int FindHeaderAt(int x)
		{
			for (int i = 0; i < splitterPositions.Length; i++)
			{
				if (x < splitterPositions[i])
				{
					return i;
				}
			}

			return -1;
		}

		private void MoveSplitter(int iSplitter, int dx)
		{
			for (int i = iSplitter; i < this.Columns.Count; i++)
				splitterPositions[i] += dx;

			ValidateHScrollBar();

			pnHeaders.Invalidate();
			lstItems.Invalidate();
		}

		private void pnHeaders_Paint(object sender, PaintEventArgs e)
		{
			Graphics g = e.Graphics;

			g.TranslateTransform(-lstItems.HScrollPosition, 0);

			// Draw Splitters
			for (int i = 0; i < splitterPositions.Length; i++)
			{
				int x = splitterPositions[i];
				// g.DrawLine(new Pen(this.BorderColor), x, 0, x, pnHeaders.Height);
			}

			// Draw Headers
			for (int i = 0; i < this.Columns.Count; i++)
			{
				string text = Columns[i].Name;
				Rectangle rect = new Rectangle((int)GetColumnStart(i), 0, (int)GetColumnWidth(i), pnHeaders.Height);

				// Fill
				if (i == iHotHeader && pnHeaders.CursorInside)
					if (headerPressed)
						g.FillRectangle(new SolidBrush(this.HeaderPressedBackColor), rect);
					else
						g.FillRectangle(new SolidBrush(this.HeaderHighlightBackColor), rect);
				else
					g.FillRectangle(new SolidBrush(this.HeaderBackColor), rect);

				// Border
				if (i == iHotHeader && headerPressed && pnHeaders.CursorInside)
					ControlPaint.DrawBorder3D(e.Graphics, rect, Border3DStyle.SunkenInner);
				else
					ControlPaint.DrawBorder3D(e.Graphics, rect, Border3DStyle.RaisedInner);

				// Text
				rect.Inflate(-this.CellPadding, -this.CellPadding);
				GListViewUtility.DrawText(g, rect, text, this.HeaderFont, GCellAlignment.Center, this.ForeColor, true);
			}

			// Draw Dragged Splitter
			if (iDraggedSplitter >= 0)
				g.DrawLine(new Pen(this.DraggedSplitterColor), xDraggedSplitter, 0, xDraggedSplitter, pnHeaders.Height);

			g.ResetTransform();

			// Draw Border
			if (this.BorderStyle == BorderStyle.None)
			{
				Pen pen = new Pen(this.BorderColor);
				g.DrawLine(pen, 0, 0, pnHeaders.Width - 1, 0);
				g.DrawLine(pen, 0, 0, 0, pnHeaders.Height - 1);
				g.DrawLine(pen, pnHeaders.Width - 1, 0, pnHeaders.Width - 1, pnHeaders.Height - 1);
			}
		}

		// Items
		private Rectangle GetCellRect(int index, int column)
		{
			Rectangle itemRect = lstItems.GetItemRectangle(index);
			
			return new Rectangle(itemRect.X + GetColumnStart(column), itemRect.Y, GetColumnWidth(column), itemRect.Height);
		}

		private GListItem FindListItem(T obj)
		{
			// lstItems.Items.Cast<GListItem>().First(item => item.Object.Equals(obj));

			foreach (GListItem item in lstItems.Items)
				if (item.Object.Equals(obj))
					return item;

			return null;
		}

		private void lstItems_SelectedIndexChanged(object sender, EventArgs e)
		{
			lstItems.Invalidate();

			OnSelectedItemsChanged(e);
		}

		private void lstItems_MeasureItem(object sender, MeasureItemEventArgs e)
		{
			e.ItemWidth = lstItems.Width + 100;
			e.ItemHeight = CalculateRowHeight(e.Graphics, e.Index);
		}

		private void lstItems_DrawItem(object sender, DrawItemEventArgs e)
		{
			if (e.Index < 0)
				return;

			GListItem item = lstItems.Items[e.Index] as GListItem;
			bool selected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;

			// It seems sometimes the Graphics is already translated
			// This might be an unknown bug of FlickFreeListBox
			// Anyway this hack works fine
			if (e.Graphics.VisibleClipBounds.Left == 0)
				e.Graphics.TranslateTransform(-lstItems.HScrollPosition, 0);

			// Background
			if (selected)
				e.Graphics.FillRectangle(new SolidBrush(this.SelectionBackgroundColor), new Rectangle(0, e.Bounds.Y, GetColumnEnd(this.Columns.Count - 1), e.Bounds.Height));

			int h = CalculateRowHeight(e.Graphics, e.Index);

			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
			{
				GListViewColumn column = this.Columns[iColumn];

				Rectangle rect = GetCellRect(e.Index, iColumn);
				Rectangle inner = rect;
				inner.Inflate(-CellPadding, -CellPadding);

				// Draw cell itself
				Columns[iColumn].DrawCell(this, e.Graphics, inner, item.Fields[iColumn], e.State);

				// Grid lines
				Pen gridPen = new Pen(GridLineColor);
				e.Graphics.DrawLine(gridPen, rect.Left, rect.Top, rect.Right, rect.Top);
				e.Graphics.DrawLine(gridPen, rect.Left, rect.Top, rect.Left, rect.Bottom);

				// Finalizing grid lines
				if (iColumn == this.Columns.Count - 1)
					e.Graphics.DrawLine(gridPen, rect.Right, rect.Top, rect.Right, rect.Bottom);

				if (e.Index == lstItems.Items.Count - 1)
					e.Graphics.DrawLine(gridPen, rect.Left, rect.Bottom, rect.Right, rect.Bottom);
			}

			e.Graphics.ResetTransform();
		}

		private int CalculateRowHeight(Graphics g, int index)
		{
			GListItem item = lstItems.Items[index] as GListItem;

			int h = this.MinRowHeight;

			for (int iColumn = 0; iColumn < this.Columns.Count; iColumn++)
			{
				int cellHeight = this.columns[iColumn].MeasureCellHeight(this, g, item.Fields[iColumn]); 
				h = Math.Max(h, cellHeight);
			}

			return h + 2 * CellPadding;
		}

		private void lstItems_Paint(object sender, PaintEventArgs e)
		{
			if (this.BorderStyle == BorderStyle.None)
				e.Graphics.DrawRectangle(SystemPens.ControlDark, new Rectangle(0, 0, lstItems.Width - 1, lstItems.Height - 1));

			// Dragged Splitter
			if (iDraggedSplitter >= 0)
			{
				e.Graphics.TranslateTransform(-lstItems.HScrollPosition, 0);
				e.Graphics.DrawLine(new Pen(DraggedSplitterColor), xDraggedSplitter, 0, xDraggedSplitter, lstItems.Height);
				e.Graphics.ResetTransform();
			}
		}

		private void lstItems_MouseClick(object sender, MouseEventArgs e)
		{
			int iItem, iColumn;

			GetCellFromPoint(e.X, e.Y, out iItem, out iColumn);

			if (iItem >= 0 && iColumn >= 0)
			{
				GListItem item = lstItems.Items[iItem] as GListItem;
				object data = item.Fields[iColumn];
				this.Columns[iColumn].OnCellMouseClick(this, ref data, e);
				item.Fields[iColumn] = data;

				if (iColumn == this.CheckColumnIndex)
					OnCheckedItemsChanged(EventArgs.Empty);
			}
		}

		private void lstItems_MouseUp(object sender, MouseEventArgs e)
		{
			int iItem, iColumn;

			GetCellFromPoint(e.X, e.Y, out iItem, out iColumn);

			if (iItem >= 0 && iColumn >= 0)
			{
				GListItem item = lstItems.Items[iItem] as GListItem;
				object data = item.Fields[iColumn];
				this.Columns[iColumn].OnCellMouseUp(this, ref data, e);
				item.Fields[iColumn] = data;
			}
		}

		private void lstItems_MouseDown(object sender, MouseEventArgs e)
		{
			int iItem, iColumn;

			GetCellFromPoint(e.X, e.Y, out iItem, out iColumn);

			if (iItem >= 0 && iColumn >= 0)
			{
				GListItem item = lstItems.Items[iItem] as GListItem;
				object data = item.Fields[iColumn];
				this.Columns[iColumn].OnCellMouseDown(this, ref data, e);
				item.Fields[iColumn] = data;
			}
		}

		private void GetCellFromPoint(int x, int y, out int iItem, out int iColumn)
		{
			iItem = IndexFromPoint(x, y);
			if (iItem == ListBox.NoMatches)
			{
				iColumn = -1;
				return;
			}

			for (int i = 0; i < this.Columns.Count; i++)
			{
				Rectangle rect = GetCellRect(iItem, i);
				if (rect.Contains(x, y))
				{
					iColumn = i;
					return;
				}
			}

			// This is possible
			iColumn = -1;
		}

		private int IndexFromPoint(int x, int y)
		{
			for (int iItem = 0; iItem < CountItems(); iItem++)
				if (lstItems.GetItemRectangle(iItem).Contains(x, y))
					return iItem;

			return -1;
		}

		private void lstItems_MouseWheel(object sender, MouseEventArgs e)
		{
			pnHeaders.Invalidate();
			lstItems.Invalidate();
		}

		private void lstItems_HorizontalScrolled(object sender, EventArgs e)
		{
			pnHeaders.Invalidate();
			lstItems.Invalidate();
		}

		void lstItems_Click(object sender, EventArgs e)
		{
			if (lstItems.SelectedIndices.Count > 0)
				OnItemClick(e);
		}

		void lstItems_DoubleClick(object sender, EventArgs e)
		{
			if (lstItems.SelectedIndices.Count > 0)
				OnItemDoubleClick(e);
		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			ValidateHScrollBar();
		}
	}
}

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
Software Developer (Senior)
Australia Australia
Master of IT, University of Technology Sydney

Bachelor Degree in Telecommunication, Hochiminh University of Technology, Vietnam

A few years of experience with .NET technology, computer graphics + animation, web development, algorithm design and many other things.

Comments and Discussions