Click here to Skip to main content
15,891,136 members
Articles / Programming Languages / C#

ControlExtender Library

Rate me:
Please Sign up or sign in to vote.
4.63/5 (12 votes)
5 Jul 20032 min read 110.6K   1.5K   49  
A component that enhances standard ListView and ListBox controls. (full designer support)
using System;
using System.Drawing;
using System.Windows.Forms;

using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.CodeDom;
using System.Collections;
using System.Diagnostics;

namespace RR.Windows.Forms
{

	/// <summary>
	/// Interface which specialized sorters must implement.
	/// </summary>
	public interface IListViewComparer 
	{
		int OnCompare(string x, string y);
	}

	/// <summary>
	/// Provides a standard Winform ListView with sorting capabilities.
	/// </summary>
	[DesignTimeVisible(true),ToolboxItem(true),]
	public class ListViewExSort : System.ComponentModel.Component
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public ListViewExSort(System.ComponentModel.IContainer container)
		{
			///
			/// Required for Windows.Forms Class Composition Designer support
			///
			container.Add(this);
			InitializeComponent();

		}

		public ListViewExSort()
		{
			///
			/// Required for Windows.Forms Class Composition Designer support
			///
			InitializeComponent();

		}

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}


		#region Component Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{

		}
		#endregion

		#region private 

		private IListViewComparer[] comparer=null;
		private ListView listView;
		private ListViewWindow window;

		private static StringComparer defaultComparer=new StringComparer();

		// accessing the sorting property from ListView resets column ordering to default.
		// thats why we work with a copy here.
		private SortOrder sorting=SortOrder.None;

		private int sortColumn =  -1;
		private Color sortBkgnColor = Color.WhiteSmoke;
		private bool sortShaded=true;

		private void DrawSortGlyph(int column, SortOrder sortOrder) 
		{
			if (this.listView==null) return;

			IntPtr header = Win32.User32.GetDlgItem(this.listView.Handle, 0);

			Win32.HDITEM hdItem = new Win32.HDITEM();

			// only the format member is valid
			hdItem.mask = Win32.HDI.FORMAT;

			// get header item info for column
			Win32.User32.SendMessage(header, Win32.HDM.GETITEMW, column, ref hdItem);

			hdItem.fmt &= Win32.HDF.NOSORT;
			if (sortOrder == SortOrder.Ascending) 
			{
				hdItem.fmt |=  Win32.HDF.SORTUP;
			} 
			else if (sortOrder == SortOrder.Descending) 
			{
				hdItem.fmt |=  Win32.HDF.SORTDOWN;
			}

			// write header info back with changed formatting flags
			Win32.User32.SendMessage(header, Win32.HDM.SETITEMW, column, ref hdItem);
		}

		private Win32.CDRF NotifyCustomDraw( Win32.NMLVCUSTOMDRAW d )
		{
			// determine the draw stage and set return value
			switch ( d.nmcd.dwDrawStage )
			{
					// first request, ask for each item notification if we shade columns
				case (int)Win32.CDDS.PREPAINT:
					if ( sortShaded && ( this.listView.View == View.Details ) )
						return Win32.CDRF.NOTIFYITEMDRAW;
					break;

					// next request, ask for each sub item notification for this item
				case (int)Win32.CDDS.ITEMPREPAINT:
					return Win32.CDRF.NOTIFYSUBITEMDRAW;

					// here is the real work, and it is simply ensuring that the
					// correct backcolor is set for the shaded column.  we let the
					// regular windows control drawing do the real work.
				case (int)Win32.CDDS.SUBITEMPREPAINT:

					// ensure that this is a valid item/subitem request
					if ( d.nmcd.dwItemSpec < this.listView.Items.Count )
					{
						// get a reference to the item to be rendered
						ListViewItem item = this.listView.Items[d.nmcd.dwItemSpec];

						// is this for a a base item set it's backcolor
						if ( d.iSubItem == 0 ) 
						{
							item.BackColor = ( d.iSubItem == sortColumn ) ? sortBkgnColor : this.listView.BackColor;
						}
							// ensure that the subitem exits before changing it
						else if ((d.iSubItem < item.SubItems.Count) && (item.SubItems[d.iSubItem]!=null)) 
						{
							item.SubItems[d.iSubItem].BackColor = (d.iSubItem==sortColumn)? sortBkgnColor:this.listView.BackColor;
						}

					}
					break;
			}

			// let default drawing do the actual rendering
			return Win32.CDRF.DODEFAULT;
		}


		private void WndProc(ref Message m)
		{
			if (m.Msg==(int)Win32.WM.ERASEBKGND) 
			{
				// shade background if in details mode
				if (sortShaded && (this.listView.View == View.Details) && (this.sortColumn>=0))
				{
					// get bounds of sorted column (only left/right used)
					Win32.RECT rect = new Win32.RECT();
					Win32.User32.SendMessage(Win32.User32.GetDlgItem(this.listView.Handle, 0), Win32.HDM.GETITEMRECT, this.sortColumn, ref rect);
					Rectangle r=new Rectangle(rect.left, this.listView.Top, (rect.right - rect.left), this.listView.Height);

					// shift-x with bounds of first item (may be negative if y-scroll)
					r.X=r.X+((this.listView.Items.Count>0)?this.listView.GetItemRect(0).X:0);

					// and fill using the selected background color
					using (Brush b = new SolidBrush(sortBkgnColor))
					{
						Graphics.FromHdc(m.WParam).FillRectangle(b, r);
					}
				}
			}
			else if (m.Msg==(int)Win32.OCM.NOTIFY) 
			{
				Win32.NMHEADER nmHdr = (Win32.NMHEADER)m.GetLParam(typeof(Win32.NMHEADER));
				if ((nmHdr.hdr.hwndFrom == this.listView.Handle) && (nmHdr.hdr.code== (int)Win32.NM.CUSTOMDRAW)) 
				{
					Win32.NMLVCUSTOMDRAW d = (Win32.NMLVCUSTOMDRAW)m.GetLParam(typeof(Win32.NMLVCUSTOMDRAW));
					m.Result = (IntPtr)NotifyCustomDraw(d);
				}
			}
		}
		#endregion

		#region public 

		[Description("The listview this extender works with. If the listview is not set, the extender has no function.")]
		public ListView ListView
		{
			get 
			{
				return listView;
			}
			set 
			{
				listView=value;

				if (listView!=null) 
				{
					window=new ListViewWindow(this);
					listView.ColumnClick += new ColumnClickEventHandler(ColumnClick);
					listView.ListViewItemSorter = new ListViewItemComparer(this);
				}
			}
		}

		[Browsable(false)]
		public IListViewComparer[] Comparer 
		{
			get
			{
				return comparer;
			}
			set 
			{
				comparer=value;
			}
		}

		[Description("The column on which sorting takes place."), Category("Behavior"), DefaultValue(-1)]
		public int SortColumn 
		{
			get 
			{
				return this.sortColumn;
			}
			set 
			{
				if (this.sortColumn!=value) 
				{
					DrawSortGlyph(this.sortColumn, SortOrder.None);
					this.sortColumn=value;
					DrawSortGlyph(this.sortColumn, this.sorting);

					if (this.listView!=null) 
					{
						this.listView.Invalidate();
					}
				}
			}
		}


		[Description("Indicates the manner in which items are to be sorted"), Category("Behavior"), DefaultValue(SortOrder.None)]
		public SortOrder Sorting
		{
			get 
			{
				return this.sorting;
			}
			set 
			{
				sorting=value;
				DrawSortGlyph(this.sortColumn, this.Sorting);

				this.listView.Sort();
				this.listView.Refresh();   // paints selected column background
				this.listView.Invalidate();
			}
		}


		[Description("Color of the shaded column in details mode"), Category("Appearance")]
		public Color ShadeColor
		{
			get
			{
				return sortBkgnColor;
			}
			set
			{
				sortBkgnColor = value;
				if (this.listView!=null)  this.listView.Refresh();
			}
		}


		[Description("If true, the selected column will be shaded when in detail mode."), Category("Appearance"), DefaultValue(true)]
		public bool Shaded
		{
			get
			{
				return sortShaded;
			}
			set
			{
				sortShaded = value;
				this.listView.Refresh();
			}
		}

		#endregion

		#region listview event handler
		private void ColumnClick(object sender, ColumnClickEventArgs e)
		{
			// Determine whether the column is the same as the last column clicked.
			if (e.Column != sortColumn)
			{
				DrawSortGlyph(sortColumn, SortOrder.None);

				// Set the sort column to the new column.
				sortColumn = e.Column;
				// Set the sort order to ascending by default.
				this.Sorting = SortOrder.Ascending;
			}
			else
			{
				// Determine what the last sort order was and change it.
				this.Sorting=(this.Sorting == SortOrder.Ascending)?SortOrder.Descending:SortOrder.Ascending;
			}
			this.listView.Sort();
		}

		#endregion

		#region private classes
		private class StringComparer : IListViewComparer 
		{
			public int OnCompare(string x, string y)
			{
				return string.Compare(x, y);
			}
		}


		private class ListViewItemComparer : IComparer 
		{
			private ListViewExSort listView;
			public ListViewItemComparer(ListViewExSort listView) 
			{
				this.listView=listView;
			}
			public int Compare(object x, object y) 
			{
				if (listView.SortColumn<0) return -1;

				IListViewComparer comparer;
				if ((listView.comparer!=null)&&(listView.SortColumn<listView.comparer.Length)&&(listView.comparer[listView.SortColumn]!=null)) 
				{
					comparer=listView.comparer[listView.SortColumn];
				} 
				else 
				{
					comparer=ListViewExSort.defaultComparer;
				}
				Debug.Assert((x as ListViewItem)!=null);
				Debug.Assert((y as ListViewItem)!=null);

				if (listView.SortColumn>=((ListViewItem)x).SubItems.Count) return -1;
				if (listView.SortColumn>=((ListViewItem)y).SubItems.Count) return -1;

				int result=comparer.OnCompare(((ListViewItem)x).SubItems[listView.SortColumn].Text, ((ListViewItem)y).SubItems[listView.SortColumn].Text);
				return (listView.Sorting==SortOrder.Descending)?-result:result;
			}
		}


		private class ListViewWindow : NativeWindow 
		{
			public ListViewWindow (ListViewExSort parent) 
			{
				this.parent=parent;
				this.parent.listView.HandleCreated += new EventHandler(this.OnHandleCreated);
				this.parent.listView.HandleDestroyed+= new EventHandler(this.OnHandleDestroyed);
			}
			private ListViewExSort parent;

			internal void OnHandleCreated(object sender, EventArgs e)
			{
				// Window is now created, assign handle to NativeWindow.
				AssignHandle(this.parent.listView.Handle);
			}
			internal void OnHandleDestroyed(object sender, EventArgs e) 
			{
				// Window was destroyed, release hook.
				ReleaseHandle();
			}
	
			protected override void WndProc(ref Message m)
			{
				base.WndProc (ref m);
				this.parent.WndProc (ref m);
			}
		}
		#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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions