Friday, March 13, 2009

Sortable Unbound DataGridView in C#

Stumble del.icio.us Reddit MyWeb! Facebook Google bookmark

I wrote a custom datagridview from some of my existing code. I had worked on some of my earlier projects and then every now and then I realised that the .Net datagridview has a lot of features but still it might be a good idea to have some more built in support. I truly believe that guys at Microsoft developed the current DataGridView perfectly the way they wanted covering most of the things. Still, if someone wants to customize the control, OOPs comes to rescue. So here it goes. This code implements generic sorting feature for bound (not required :) ) and unbound datagridview.


// Copyright © your's truly All rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

namespace Yours.Truly
{
//**************************************************************************
/// <summary>
/// Subclass of the <see cref="System.Windows.Forms.DataGridView"/> class.
/// </summary>
/// <remarks>
/// This class enhances the standard DataGridView control class by adding a
/// sort feature for unbound columns too :)
/// <para>$Id$</para>
/// <author>Yours truly</author>
/// </remarks>
public class CgDataGridView : DataGridView
{
private DataGridViewColumn m_sortedColumn;

//**********************************************************************
/// <summary>
/// Initializes a new instance of the <see cref="CgDataGridView"/>
/// class.
/// </summary>
/// <remarks>
/// The <see cref="System.Windows.Forms.DataGridView.ColumnHeadersDefaultCellStyle"/>
/// will be modified to have its
/// alignment set to <c>DataGridViewContentAlignment.MiddleCenter</c>.
/// </remarks>
public CgDataGridView()
{
// Set our unique default properties.
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

// Alter the default column header cell style.
DataGridViewCellStyle hdrStyle = this.ColumnHeadersDefaultCellStyle;
hdrStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
}

//**********************************************************************
/// <summary>
/// Occurs when the state of a cell changes in relation to a change in
/// its contents.
/// </summary>
/// <remarks>
/// <para>If the current cell is dirty, this will commit the change. We
/// need to do this so other code can see the change immediately.
/// Normally, changes are only committed when focus leaves the cell.</para>
/// </remarks>
/// <param name="e">An <see cref="System.EventArgs"/> that contains
/// the event data.</param>
protected override void OnCurrentCellDirtyStateChanged(EventArgs e)
{
base.OnCurrentCellDirtyStateChanged(e);
if (this.IsDisposed || null == this) return;

// This method assumes that any editable cells are of a type that
// should be committed immediately. If other writable cell types
// (e.g., DataGridViewTextBoxColumn) are added to the DataGridView,
// code should be added below to handle that.
if (this.IsCurrentCellDirty)
{
this.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}

// *********************************************************************
/// <summary>
/// Changes the selection state of the row with the specified index.
/// </summary>
/// <param name="rowIndex">The index of the row.</param>
/// <param name="selected">true to select the row; false to cancel the
/// selection of the row.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="rowIndex"/> is less than 0 or greater than the
/// number of rows in the control.</exception>
public void SetSelectedRow(int rowIndex, bool selected)
{
base.SetSelectedRowCore(rowIndex, selected);
}

// *********************************************************************
/// <summary>
/// Gets a string array of values contained in this
/// <see cref="DataGridView"/>.
/// </summary>
/// <returns>A string representation of data contained in the grid.</returns>
public List<string[]> ToArray()
{
List<string[]> rowValues = new List<string[]>();
if (this.Columns.Count > 0)
{
foreach (DataGridViewRow row in this.Rows)
{
string[] cellValues = new string[this.Columns.Count];
for (int cellCount = 0; cellCount < this.Columns.Count; cellCount++)
{
if (row.Cells[cellCount].Value != null)
{
cellValues[cellCount] = row.Cells[cellCount].Value.ToString();
}
}
rowValues.Add(cellValues);
}
}
return rowValues;
}

#region Metadata overrides

//**********************************************************************
/// <summary>
/// Gets or sets a value indicating how column widths are determined.
/// </summary>
/// <remarks>
/// This simply provides new metadata for the
/// <see cref="System.Windows.Forms.DataGridView.AutoSizeColumnsMode"/>
/// property.
/// </remarks>
/// <value>One of the <c>DataGridViewAutoSizeColumnsMode</c> values.
/// The default is <c>DataGridViewAutoSizeColumnsMode.Fill</c>.</value>
/// <seealso cref="System.Windows.Forms.DataGridViewColumn.AutoSizeMode"/>
/// <seealso cref="System.Windows.Forms.DataGridView.AutoResizeColumn(int,DataGridViewAutoSizeColumnMode)"/>
/// <seealso cref="System.Windows.Forms.DataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode)"/>
[DefaultValue(DataGridViewAutoSizeColumnsMode.Fill)]
public new DataGridViewAutoSizeColumnsMode AutoSizeColumnsMode
{
get { return base.AutoSizeColumnsMode; }
set { base.AutoSizeColumnsMode = value; }
}

#endregion Metadata overrides

#region Sorting and IComparer implementation

// *********************************************************************
/// <summary>
/// Gets the column by which the System.Windows.Forms.DataGridView
/// contents are currently sorted.
/// </summary>
/// <value>The <see cref="DataGridViewColumn"/> by which the
/// <see cref="DataGridView"/> contents are currently sorted.</value>
public DataGridViewColumn CurrentSortedColumn
{
get
{
if (this.SortedColumn != null)
{
// Datagridview supports sorting.
return this.SortedColumn;
}
else
{
// Our own sorting?
return m_sortedColumn;
}
}
private set { this.m_sortedColumn = value; }
}

// *********************************************************************
/// <summary>
/// Gets a value indicating whether the items in the
/// <see cref="DataGridView"/> control are sorted in ascending or
/// descending order, or are not sorted.
/// </summary>
/// <value>One of the <see cref="SortOrder"/> values.</value>
public SortOrder CurrentSortOrder
{
get
{
if (CurrentSortedColumn != null)
{
return CurrentSortedColumn.HeaderCell.SortGlyphDirection;
}
else
{
return SortOrder.None;
}
}
}

// *********************************************************************
/// <summary>
/// Sorts the contents of the <see cref="DataGridView"/> control
/// in ascending or descending order based on the contents of the
/// specified column.
/// </summary>
/// <typeparam name="T">The type of the underlying data in the
/// <see cref="DataGridView"/>.</typeparam>
/// <param name="dataGridViewColumn">The column by which to sort the
/// contents of the <see cref="DataGridView"/>.</param>
/// <param name="sortableArray"> The array of underlying data.</param>
public void DoSort<T>(DataGridViewColumn dataGridViewColumn,
ref T[] sortableArray)
{
List<T> list = new List<T>(sortableArray);
this.DoSort<T>(dataGridViewColumn, ref list);
sortableArray = list.ToArray();
}

// *********************************************************************
/// <summary>
/// Sorts the contents of the <see cref="DataGridView"/> control
/// in ascending or descending order based on the contents of the
/// specified column.
/// </summary>
/// <typeparam name="T">The type of the underlying data in the
/// <see cref="DataGridView"/>.</typeparam>
/// <param name="dataGridViewColumn">The column by which to sort the
/// contents of the <see cref="DataGridView"/>.</param>
/// <param name="sortableList"> The list of underlying data.</param>
public void DoSort<T>(DataGridViewColumn dataGridViewColumn,
ref List<T> sortableList)
{
DataGridViewColumn oldColumn = this.CurrentSortedColumn;
ListSortDirection direction;

// If oldColumn is null, then the DataGridView is not currently sorted.
if (oldColumn != null)
{
// Sort the same column again, reversing the SortOrder.
if (oldColumn == dataGridViewColumn &&
this.CurrentSortOrder == SortOrder.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
// Sort a new column and remove the old SortGlyph.
direction = ListSortDirection.Ascending;
oldColumn.HeaderCell.SortGlyphDirection = SortOrder.None;
}
}
else
{
direction = ListSortDirection.Ascending;
}

CgSortHelper<T> sh =
new CgSortHelper<T>(ref sortableList);

sh.Sort(dataGridViewColumn.DataPropertyName, direction);
sortableList = sh.SortableList;
// Check if the datagridview has a datasource. If yes, bind the data
if (this.DataSource != null && sh.IsSortedCore) this.DataSource = sortableList;

dataGridViewColumn.HeaderCell.SortGlyphDirection =
direction == ListSortDirection.Ascending ?
SortOrder.Ascending : SortOrder.Descending;
CurrentSortedColumn = dataGridViewColumn;

} // end DoSort

// *********************************************************************
/// <summary>
/// Sorts the array of <see cref="DataGridViewRow"/> objects by it's
/// RowIndex in the specified <paramref name="sortOrder"/>.
/// </summary>
/// <param name="array">An array of DataGridViewRow.</param>
/// <param name="sortOrder"><see cref="SortOrder"/>.</param>
public void SortSelectedRowsByIndex(DataGridViewRow[] array,
SortOrder sortOrder)
{
Array.Sort(array, new CgSortComparer(sortOrder));
}

// *************************************************************************
/// <summary>
/// A class to implement sorting for the underlying data.
/// </summary>
private class CgSortComparer : IComparer
{
private SortOrder m_sortOrder;

// *********************************************************************
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="sortOrder"> Sort order for comparison.</param>
public CgSortComparer(SortOrder sortOrder)
{
this.m_sortOrder = sortOrder;
}

// *****************************************************************
/// <summary>
/// Compares object <paramref name="x"/> to object
/// <paramref name="y"/> and returns an indication of their relative
/// values.
/// </summary>
/// <param name="x"> First object.</param>
/// <param name="y"> Second object to compare with first object.</param>
/// <returns>A signed number indicating the relative values of
/// <paramref name="x"/> and <paramref name="y"/>.
/// < 0 : The value of <paramref name="x"/> is less than
/// <paramref name="y"/>.
/// = 0 : The value of <paramref name="x"/> is equal to the value of
/// <paramref name="y"/>.
/// > 0: The value of <paramref name="x"/> is greater than
/// <paramref name="y"/> or y is null.
/// </returns>
/// <exception cref="ArgumentException"> <paramref name="x"/> and
/// <paramref name="y"/> are not the same type.</exception>
public int Compare(object x, object y)
{
switch (this.m_sortOrder)
{
case SortOrder.Descending:
return ((DataGridViewRow) y).Index.CompareTo(
((DataGridViewRow) x).Index);
case SortOrder.Ascending:
default:
return ((DataGridViewRow) x).Index.CompareTo(
((DataGridViewRow) y).Index);
}
}
} // end class CgSortComparer

#endregion Sorting and IComparer implementation

} // end class CgDataGridView

} // end namespace Yours.Truly



Here is the sorthelper class.


// Copyright © Yours.Truly 2009. All rights reserved.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

namespace Yours.Truly
{
//**************************************************************************
/// <summary>
/// A class to help sorting different kinds of object data.
/// </summary>
/// <remarks>
/// This class makes use of the CgPropertyComparer class to compare values
/// based on the type descriptor.
/// <para>$Id$</para>
/// <author>Your's truly</author>
/// </remarks>
public class CgSortHelper<T>
{
private bool m_isSorted;
private ListSortDirection m_direction = ListSortDirection.Ascending;
private List<T> m_sortableList;
[NonSerialized()]
private PropertyDescriptor m_sort = null;

//**********************************************************************
/// <summary>
/// Initializes a new instance of the CgSortHelper.
/// class.
/// </summary>
/// <param name="sortableList"> The list of underlying data.</param>
public CgSortHelper(ref List<T> sortableList)
{
m_sortableList = sortableList;
}

//**********************************************************************
/// <summary>
/// Initializes a new instance of the CgSortHelper.
/// class.
/// </summary>
/// <param name="sortableArray"> The array of underlying data.</param>
public CgSortHelper(ref T[] sortableArray)
{
m_sortableList = new List<T>(sortableArray);
}

#region Public Sorting API

// *********************************************************************
/// <summary>
/// Sorts the contents of the underlying data in ascending or descending
/// order.
/// </summary>
public void Sort()
{
ApplySortCore(m_sort, m_direction);
}

// *********************************************************************
/// <summary>
/// Sorts the contents of the underlying data in ascending or descending
/// order based on the contents of the specified <paramref name="property"/>.
/// </summary>
/// <param name="property"> The property based on which the sorting
/// needs to be performed.</param>
public void Sort(string property)
{
/* Get the PD */
m_sort = FindPropertyDescriptor(property);

/* Sort */
ApplySortCore(m_sort, m_direction);
}

// *********************************************************************
/// <summary>
/// Sorts the contents of the underlying data in ascending or descending
/// order based on the contents of the specified <paramref name="property"/>
/// and <paramref name="direction"/>.
/// </summary>
/// <param name="property"> The property based on which the sorting
/// needs to be performed.</param>
/// <param name="direction"> One of the <see cref="ListSortDirection"/>.
/// </param>
public void Sort(string property, ListSortDirection direction)
{
/* Get the sort property */
m_sort = FindPropertyDescriptor(property);
m_direction = direction;

/* Sort */
ApplySortCore(m_sort, m_direction);
}

#endregion

#region Sorting properties

// *********************************************************************
/// <summary>
/// Gets a value indicating whether the list supports sorting.
/// </summary>
/// <value>Always returns true.</value>
public bool SupportsSortingCore
{
get { return true; }
}

// *********************************************************************
/// <summary>
/// The underlying data to be sorted.
/// </summary>
/// <value> The list of objects of type T.</value>
public List<T> SortableList
{
get { return this.m_sortableList; }
}

// *********************************************************************
/// <summary>
/// Sorts the contents of the underlying data in ascending or descending
/// order based on the contents of the specified <paramref name="property"/>
/// and <paramref name="direction"/>.
/// </summary>
/// <param name="property"> The property based on which the sorting
/// needs to be performed.</param>
/// <param name="direction"> One of the <see cref="ListSortDirection"/>.
/// </param>
public void ApplySortCore(PropertyDescriptor property,
ListSortDirection direction)
{
List<T> items = this.SortableList as List<T>;

if ((null != items) && (null != property))
{
CgPropertyComparer<T> pc =
new CgPropertyComparer<T>(property, direction);
items.Sort(pc);

/* Set sorted */
m_isSorted = true;
}
else
{
/* Set sorted */
m_isSorted = false;
}
}

// *********************************************************************
/// <summary>
/// Gets a value indicating whether the list is sorted.
/// </summary>
/// <value>true if sorted; false otherwise.</value>
public bool IsSortedCore
{
get { return m_isSorted; }
}

// *********************************************************************
/// <summary>
/// Sets the <see cref="IsSortedCore"/> value to false without removing
/// sorting.
/// </summary>
public void RemoveSortCore()
{
m_isSorted = false;
}

#endregion

#region Private Sorting API

// *********************************************************************
/// <summary>
/// Gets the <see cref="PropertyDescriptor"/> with the given
/// <paramref name="property"/>.
/// </summary>
/// <param name="property"> The property based on which the sorting
/// needs to be performed.</param>
/// <returns><see cref="PropertyDescriptor"/>.</returns>
private PropertyDescriptor FindPropertyDescriptor(string property)
{
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor prop = null;

if (null != pdc)
{
prop = pdc.Find(property, true);
}
return prop;
}

#endregion

} // end CgSortHelper

//**************************************************************************
/// <summary>
/// A class to help sorting different kinds of object data.
/// </summary>
/// <remarks>
/// This class implements the IComparer interface and is based on
/// code implemented by Rockford Lhotka:
/// msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet
/// /html/vbnet01272004.asp" href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01272004.asp">
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01272004.asp
/// <para>$Id$</para>
/// <author>your's truly</author>
/// </remarks>
internal class CgPropertyComparer<T> : IComparer<T>
{
private ListSortDirection m_direction = ListSortDirection.Ascending;
private PropertyDescriptor m_property;

//**********************************************************************
/// <summary>
/// Initializes a new instance of the CgPropertyComparer.
/// class.
/// </summary>
/// <param name="property"> The property based on which the sorting
/// needs to be performed.</param>
/// <param name="direction"> One of the <see cref="ListSortDirection"/>.
/// </param>
public CgPropertyComparer(PropertyDescriptor property,
ListSortDirection direction)
{
m_property = property;
m_direction = direction;
}

// *****************************************************************
/// <summary>
/// Compares object <paramref name="x"/> to object
/// <paramref name="y"/> and returns an indication of their relative
/// values.
/// </summary>
/// <param name="x"> First object.</param>
/// <param name="y"> Second object to compare with first object.</param>
/// <returns>A signed number indicating the relative values of
/// <paramref name="x"/> and <paramref name="y"/>.
/// < 0 : The value of <paramref name="x"/> is less than
/// <paramref name="y"/>.
/// = 0 : The value of <paramref name="x"/> is equal to the value of
/// <paramref name="y"/>.
/// > 0: The value of <paramref name="x"/> is greater than
/// <paramref name="y"/> or y is null.
/// </returns>
/// <exception cref="ArgumentException"> <paramref name="x"/> and
/// <paramref name="y"/> are not the same type.</exception>
public int Compare(T x, T y)
{
/* Get property values */
object xValue = GetPropertyValue(x, m_property.Name);
object yValue = GetPropertyValue(y, m_property.Name);

/* Determine sort order */
if (m_direction == ListSortDirection.Ascending)
{
return CompareAscending(xValue, yValue);
}
else
{
return CompareDescending(xValue, yValue);
}
}

// *********************************************************************
/// <summary>
/// Determines whether the specified x object of type T is equal to the
/// other object y of type T.
/// </summary>
/// <param name="x"> First object.</param>
/// <param name="y"> Second object to compare with first object.</param>
/// <returns> true if the x is equal to y, otherwise false.</returns>
public bool Equals(T x, T y)
{
return x.Equals(y);
}

// *********************************************************************
/// <summary>
/// Serves as a hash function for a particular type. System.Object.GetHashCode()
/// is suitable for use in hashing algorithms and data structures like a hash
/// table.
/// </summary>
/// <param name="obj">The object for which hashcode is required.</param>
/// <returns>A hash code for the current object of type T.</returns>
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}

// *********************************************************************
/// <summary>
/// Compares two property values of any type
/// </summary>
/// <param name="x">First object.</param>
/// <param name="y">Second object.</param>
/// <returns>A signed number indicating the relative values of
/// <paramref name="x"/> and <paramref name="y"/> in ascending order.
/// < 0 : The value of <paramref name="x"/> is less than
/// <paramref name="y"/>.
/// = 0 : The value of <paramref name="x"/> is equal to the value of
/// <paramref name="y"/>.
/// > 0: The value of <paramref name="x"/> is greater than
/// <paramref name="y"/> or y is null.
/// </returns>
/// <exception cref="ArgumentException"> <paramref name="x"/> and
/// <paramref name="y"/> are not the same type.</exception>
private int CompareAscending(object x, object y)
{
int result;

/* If values implement IComparer */
if (x is IComparable)
{
result = ((IComparable) x).CompareTo(y);
}
/* If values don't implement IComparer but are equivalent */
else if (x.Equals(y))
{
result = 0;
}
/* Values don't implement IComparer and are not equivalent, so compare as string values */
else result = x.ToString().CompareTo(y.ToString());

/* Return result */
return result;
}

// *********************************************************************
/// <summary>
/// Compares two property values of any type
/// </summary>
/// <param name="x">First object.</param>
/// <param name="y">Second object.</param>
/// <returns>A signed number indicating the relative values of
/// <paramref name="x"/> and <paramref name="y"/> in descending order.
/// < 0 : The value of <paramref name="x"/> is less than
/// <paramref name="y"/>.
/// = 0 : The value of <paramref name="x"/> is equal to the value of
/// <paramref name="y"/>.
/// > 0: The value of <paramref name="x"/> is greater than
/// <paramref name="y"/> or y is null.
/// </returns>
/// <exception cref="ArgumentException"> <paramref name="x"/> and
/// <paramref name="y"/> are not the same type.</exception>
private int CompareDescending(object x, object y)
{
/* Return result adjusted for ascending or descending sort order ie
multiplied by 1 for ascending or -1 for descending */
return CompareAscending(x, y) * -1;
}

// *********************************************************************
/// <summary>
/// Gets the property value for the specified property and type.
/// </summary>
/// <param name="value">The type of object.</param>
/// <param name="property">The property for which the value is required.
/// </param>
/// <returns>An object containing the property value.</returns>
private object GetPropertyValue(T value, string property)
{
/* Get property */
PropertyInfo propertyInfo = value.GetType().GetProperty(property);

/* Return value */
return propertyInfo.GetValue(value, null);
}

} // end class CgPropertyComparer

} // end namespace Yours.Truly



Hope this helps someone.

1 comment:

  1. I recently came accross your blog and have been reading along. I thought I would leave my first comment. I dont know what to say except that I have enjoyed reading. Nice blog. I will keep visiting this blog very often.


    Betty

    http://laptopprocessor.info

    ReplyDelete

Please leave your opinion...