Introduction
Out of the box, the DataGridView only allows sorting by a single column.
This article presents code that enables users to sort their data in a
DataGridView by multiple columns. The DataGridView derived class may be used
for any data types that support the IComparable interface (which includes all
the basic .NET types).
This project was built using Visual C# 2005 Express Edition.
Using the code
Normal Code Use
Add a sorted DataGrid view to your project:
- Download the library using the link above and unzip the SortedDataGridView.dll
somewhere convenient.
- Add a reference to SortedDataGridView.dll that you have just extracted to your
toolbox by right-clicking the toolbox
and selecting "Choose Items..." then "Browse..."
- The Toolbox
should now show the SortedDataGridView as a new component which you can directly
add to your forms and configured in the same manner as a normal DataGridView.
There is an extra property the SortedDataGridView exposes: MaxSortColumns. This
is set to the maximum number of columns you allow users to sort the grid by, or 0
for no limit. I have found that sorting by more than 3 columns confusing, and
tend to limit the number of columns to 3 or 4, depending on the data displayed.
I find it easy to get confused when using the advanced user interface and
lose track of the order of sorting.
Advanced Code Use
If you are converting an existing project to use this code then you will
probably want to use this method.
Add a reference to the library SortedDataGridView.dll to your project.
Manually edit the
Designer.cs or Designer.vb for your form to change the definition of your
DataGridView from System.Windows.Forms.DataGridView
to
Mobius.Utility.SortedDataGridView
. Change the instantiation of the
variable in the same manner.
private void InitializeComponent()
{
this.PersonGrid = new Mobius.Utility.SortedDataGridView();
}
private Mobius.Utility.SortedDataGridView PersonGrid;
That's it, you now have a DataGridView sorted by multiple columns.
User Interface
Normal Use
To sort by columns A, B and C click on the header of the
columns in reverse order, C then B then A.
Advanced Use
When using the grid I found that it was useful to be able to reverse the sort
order of one of the columns without changing it's priority, so I added code to
do this. It is accessible by holding down the control key.
Having sorted by A, B, C, to reverse the sort order of one of the columns click
the column header while holding down the control key.
A side effect of this is that you can order by A, B, C by clicking the
columns in that order while pressing the control key, because if a column is
not so far sorted, it is added to the sort list.
If you have chosen to limit the number of columns that you sort by, when
you click a column that is not in the sort and you are currently sorting by the
maximum number of columns, the last column is replaced by the one just clicked.
Points of Interest
Interface to DataGridView
The code overrides the OnColumnHeaderMouseClick
function to start the sort and
the DataGridView.Sort
function with a custom sort class that implements the
IComparer interface.
I put as much of the functionality into the sort class as possible for two reasons:
- It fit better there.
- It keeps the DataGridView class clean.
Reason 2 means that it is a lot easier to add the functionality here to another
existing DataGridView derived class.
Sort Order Description
The grid exposes a property named SortOrderDescription
which returns
a description of the columns sorted which is suitable for use as a tooltip for the
column headers or in a status bar. The demo project shows it's use in a status bar.
Sorting
The sorting uses an IComparer derived class, DataGridComparer
, that sorts by
each column in turn.
The code is quite simple. Firstly I cast the object parameters from the
IComparer interface, then do the comparison in a separate function:
public int Compare(object x, object y)
{
DataGridViewRow lhs = x as DataGridViewRow;
DataGridViewRow rhs = y as DataGridViewRow;
return Compare(lhs.Cells, rhs.Cells);
}
public int Compare(DataGridViewCellCollection lhs, DataGridViewCellCollection rhs)
{
foreach (SortColDefn colDefn in _sortedColumns)
{
int retval = Comparer.Default.Compare(
lhs[colDefn.colNum].Value,
rhs[colDefn.colNum].Value);
if (retval != 0)
return (colDefn.ascending ? retval : -retval);
}
return 0;
}
Anonymous delegate
When a column is requested by the user to be sorted, the code needs to determine if
that column is already being sorted. If it is then it is promoted to be the primary
sort column (in basic user interface or order swapped if using the Control key).
The structure which holds the sort order is a SortColDefn
:
private struct SortColDefn
{
internal Int16 colNum;
internal bool ascending;
internal SortColDefn(int columnNum, SortOrder sortOrder)
{
colNum = Convert.ToInt16(columnNum);
ascending = (sortOrder != SortOrder.Descending);
}
}
The columns that are currently sorted are stored in a simple array:
List<SortColDefn> _sortedColumns;
When you come to find out if a supplied column index is in the array, you could
simply loop through the array and check if the SortColDefn.colNum
was equal to the passed column index.
However, it's more fun to use the List.FindIndex
function and an
anonymous delegate:
int sortPriority = _sortedColumns.FindIndex(
delegate(SortColDefn cd) { return cd.colNum == columnIndex; });
On return, sortPriority
will be -1
if the columnIndex
is not found or set equal to the index into the _sortedColumns
array of the SortColDefn
with the same columnIndex
.
History
Version 0.8 - 11 April 2007 - first release, half way there!