Click here to Skip to main content
Click here to Skip to main content

DataGridView Multi-column Sort

By , 17 Apr 2007
 

Screenshot - SortedGrid.gif

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:

  1. Download the library using the link above and unzip the SortedDataGridView.dll somewhere convenient.
  2. 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..."
  3. 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:

  1. It fit better there.
  2. 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);
    }

    // These two rows are indistinguishable.
    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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

PTA_UK
Software Developer
United Kingdom United Kingdom
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionDataGridView with VirtualMode 'TRUE' and unbounded Colums. [modified]membersyynapse26 Oct '12 - 1:52 
Hi,
first of all, thanks for you project, very good.
I have a problem with my existing project. I modified it for to implement the sortable column. But when I click on header column I retrieve the follow error:
"Operation is not supported when VirtualMode is set to true".
Mu datagridview has unbound column with VirtualMode property set to TRUE.
 
Any Help please?
 
Crismer

modified 26 Oct '12 - 8:18.

GeneralMy vote of 5memberProEnggSoft29 Feb '12 - 19:23 
Nice article
GeneralThanksgroupYZK29 Mar '11 - 23:56 
Thanks
QuestionDate Col sort not workmemberackid3225 Feb '11 - 19:57 
Hi
 
Excellent usage. But i have one prob.
 
i have unbound date col in my grid. i was not able to sort the date col. any help?
 
Regards
AnswerRe: Date Col sort not workmemberPTA_UK26 Feb '11 - 2:35 
It does handle date columns no problem, but it doesn't yet handle bound data.
 
Handling bound data and a number beside the glyph to indicate the column order would be enough for me to call it version 1.0 rather than its current version 0.8. I estimate it'll take same time or more to do these two developments as I've spent on it already.
GeneralRe: Date Col sort not workmemberackid3227 Feb '11 - 18:02 
i add a column which is a datetime col. When i click on that col to sort, the result comes like text sort, not like date sort. Where i went wrong?
GeneralGreat toolmemberbujal28 Oct '10 - 22:44 
I love it ...really helps me Smile | :)
QuestionSort Column As Integer / Decimalmemberdaneasaur26 Apr '10 - 1:05 
I've got a column containing values between 1-175
When this column is sorted, it's sorted the data as though it were a string.
I'm using VB.NET, so the code you have posted doesn't really make sense to me ):
 
I had a working code to sort the column as though it is an integer, but it no longer works with this DGV.
 
Could you advise me how I would be able to do this?
AnswerRe: Sort Column As Integer / DecimalmemberPTA_UK30 Apr '10 - 4:30 
It's probably how you're adding the data to the grid. If you add a value as a string then the comparison will be done as a string and as an integer comparison if you add the value as an integer.
 
Compare in the example program how the Level of the person added is handled, it is added both as an enum and as a string: programmer.Level, programmer.Level.ToString(). This results in you being able to sort alphabetically (Advanced, Beginner, Guru, Intermediate) or in terms of the ability (Beginner, Intermediate, Advanced, Guru). The latter is most likely what is wanted.
QuestionErro in Sort(_columnSorter)memberBendito Santana14 Apr '10 - 9:20 
When trying to use the solution on a new project, this error occurs.
 
protected override void OnColumnHeaderMouseClick(DataGridViewCellMouseEventArgs e)
{
_columnSorter.SetSortColumn(e.ColumnIndex, ModifierKeys);
 
Sort(_columnSorter);
 
Columns[e.ColumnIndex].SortMode = DataGridViewColumnSortMode.Programmatic;
 
base.OnColumnHeaderMouseClick(e);
}
 

System.InvalidOperationException Was Unhandled
Message = "The DataGridView control is limited by data. The control can not use the comparer to perform the sorting operation."
Source = "System.Windows.Forms"
StackTrace:
in System.Windows.Forms.DataGridView.Sort (IComparer comparer)
in Mobius.Utility.SortedDataGridView.OnColumnHeaderMouseClick (DataGridViewCellMouseEventArgs e) in D: \ aaa \ DemoMultiColumnSort \ SortedDataGridView \ SortedDataGridView.cs: line 51
in System.Windows.Forms.DataGridView.OnMouseClick (MouseEventArgs e)
in System.Windows.Forms.Control.WmMouseUp (Message & m, MouseButtons button, Int32 clicks)
in System.Windows.Forms.Control.WndProc (Message & m)
in System.Windows.Forms.DataGridView.WndProc (Message & m)
in System.Windows.Forms.Control.ControlNativeWindow.OnMessage (Message & m)
in System.Windows.Forms.Control.ControlNativeWindow.WndProc (Message & m)
in System.Windows.Forms.NativeWindow.DebuggableCallback (IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
in System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW (MSG & msg)
in System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop (dwComponentID Int32, Int32 reason, Int32 pvLoopData)
in System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner (Int32 reason, ApplicationContext context)
in System.Windows.Forms.Application.ThreadContext.RunMessageLoop (Int32 reason, ApplicationContext context)
in System.Windows.Forms.Application.Run (Form MainForm)
in DemoMultiColumnSort.Program.Main () in D: \ aaa \ DemoMultiColumnSort \ DemoMultiColumnSort \ Program.cs: line 17
in System.AppDomain._nExecuteAssembly (Assembly assembly, String [] args)
in System.AppDomain.ExecuteAssembly (String assemblyFile, assemblySecurity Evidence, String [] args)
in Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly ()
in System.Threading.ThreadHelper.ThreadStart_Context (Object state)
in System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state)
in System.Threading.ThreadHelper.ThreadStart ()
InnerException:
AnswerRe: Erro in Sort(_columnSorter)memberPTA_UK30 Apr '10 - 4:32 
Are you using a bound data source? This code unfortunately does not yet support that.
Generalremove all sortmemberBendito Santana14 Apr '10 - 6:48 
How can I remove all sort?
 
Thanks
Bene
GeneralRe: remove all sortmemberPTA_UK30 Apr '10 - 4:09 
To remove all sort you need to clear the _sortedColumns List in the class DataGridComparer. However, just removing the sort will not reorder the rows into the order in which they were inserted.
GeneralShow Sort Glyph on each sorted columnmembergeneral.jesiu2 Oct '09 - 2:53 
The code to change:
 
	protected override void OnColumnHeaderMouseClick(DataGridViewCellMouseEventArgs e)
		{
            var direction = this.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection;
 
            if ((ModifierKeys & Keys.Control) != Keys.Control)
            {
                foreach (DataGridViewColumn column in this.Columns)
                {
                    column.HeaderCell.SortGlyphDirection = SortOrder.None;
                }
            }
 
            this.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = direction == SortOrder.Ascending && direction != SortOrder.None ? SortOrder.Descending : SortOrder.Ascending;
 
			_columnSorter.SetSortColumn(e.ColumnIndex, ModifierKeys);
 
			Sort(_columnSorter);
 
			Columns[e.ColumnIndex].SortMode = DataGridViewColumnSortMode.Programmatic;
 
			base.OnColumnHeaderMouseClick(e);
		}

GeneralRe: Show Sort Glyph on each sorted columnmembersoftboy993 Jan '11 - 19:10 
still only one column show the sort glyph. any ideas?
GeneralRe: Show Sort Glyph on each sorted columnmemberPTA_UK26 Feb '11 - 2:31 
You can change the code to show the correct up/down glyph for each sorted column, but I found it confusing as it doesn't indicate what order the columns are sorted in. As long as you don't order by too many columns, at present you can see what the first order is and it's not too difficult to remember the other one or two.
 
The solution would like to code is be to custom draw the header to show a number above/below/beside the glyph to indicate what the column sort order is. That would get the code another 0.1 closer to what I'd call version 1.0 release.
 
Something else I tried was to shift the column to the left as you click them, first sort column furthest left - it thought it was awful, especially with a lot of columns and the grid scrolled to the right.
QuestionCall simple method to sort multiple columns?memberjwittock8427 Jul '09 - 3:13 
Hi, I ready your article and downloaded the DLL. Works fine for me in manually clicking the headers. My question is how I can programmatically sort by multiple headers? I would like to be able to call something like 'dgv.sortOnMultiple(col1, col2)' rather than requiring the user to click the headers manually in the program. I'm not sure how I could add this in, if possible, I'm fairly new.

Thanks
AnswerRe: Call simple method to sort multiple columns?memberPTA_UK27 Jul '09 - 7:14 
Good question - one that I didn't have a good answer for when I posted this article, and unfortunately I still don't have a good clean solution. I didn't and still don't like the idea of exposing the underlying SortColDefn but I can't really rationalise why.
 
I think the best of a bad bunch of solutions I've come up with is a function that you pass an array of the columns that you want sorted by. It mimics you clicking the column headers in the reverse order you supply them. The reverse because it seems to make more sense to me. For example, in the example code if you want to sort by skill level, last name then first name, you would supply the array {5,2,1}. If you wanted to sort by skill level descending, then just add another simulated header click and supply the array {5,5,2,1}.
 
Add this function to the SortedDataGridView class.
public void SetSortColumns(int[] columnNums)
{
    if (columnNums == null)
        // Could either throw exception:
        //throw new ArgumentNullException();
        // or just ignore it:
        return;
 
    int loop = columnNums.Length;
    while(--loop >= 0)
    {
        int colNum = columnNums[loop];
        if (colNum < 0 || colNum >= Columns.Count)
            throw new ArgumentOutOfRangeException("Illegal column number: " + colNum.ToString());
 
        _columnSorter.SetSortColumn(colNum, Keys.None);
        Columns[colNum].SortMode = DataGridViewColumnSortMode.Programmatic;
    }
 
    Sort(_columnSorter);
}
The function call needs to be after you have added the data, or the final Sort() line in the function above has no effect.
 
In the supplied sample code add the following function call as the last line in the constructor of the DemoMultiColumnSort class after the call to PopulateGrid().
PersonGrid.SetSortColumns(new int[] { 5, 5, 2, 1 });
In your case you will probably be creating the array dynamically depending on the columns in the grid.
 
It's not a great solution and if you come up with something better let me know.
Questionselected valuememberpra g30 Jan '09 - 1:39 
how to get the selected value in text box
 
deep

GeneralNot sortable columnsmember123kadda10 Nov '08 - 21:48 
As it is today applications fail if we try to sort a column thats set to NotSortable, eg. ImageColumn. I fixed this by inserting this code in SortedDataGridView and the MouseClick event:
 
protected override void OnColumnHeaderMouseClick(DataGridViewCellMouseEventArgs e)
{
if (Columns[e.ColumnIndex].SortMode != DataGridViewColumnSortMode.NotSortable)
{
_columnSorter.SetSortColumn(e.ColumnIndex, ModifierKeys);
Sort(_columnSorter);
Columns[e.ColumnIndex].SortMode = DataGridViewColumnSortMode.Programmatic;
}
base.OnColumnHeaderMouseClick(e);
}
GeneralDatabound GridmemberMarcCodeMan23 Jun '08 - 13:03 
Hi,
 
I implemented the dll per your instructions and took an existing DataGridView and changed it to you sorted version. My problem is that my existing control is databound and when I click on a column header, I get an error.
 
I would like to sort multiple columns programmatically, instead of by clicking on the column header. Currently, I am programmatically sorting on just one column:
 
dgvLocations.Sort(dgvLocations.Columns[3], ListSortDirection.Ascending);
 
Is it possible to programmatically sort on multiple columns if the DataGridView is databound?
 
Thanks in advance for your help!
GeneralRe: Databound GridmemberMarcCodeMan23 Jun '08 - 13:22 
Hey,
 
I found an answer to my problem. I changed the control back to a normal DataGridView and sorted my binding source for the control with:
 
locationBindingSource.Sort = "[Street Name], [Street Direction], [Street Number]";
 
It's funny because I had been Googling an answer for a while last week, but just came across this just after I posted my question. Big Grin | :-D
 
Anyhow, nice article and nice custom control - which I'll probably end up using in the future.
GeneralMaxSortColumnsmemberRed Flying Pig5 Jun '08 - 6:39 
I tried your code, works great! The number of sort columns kept increasing so I modified MaxSortColumns to set _maxSortColumns from the value:
 
public int MaxSortColumns
   {
      get { return _sortedColumns.Capacity; }
      set
      {
         if (_sortedColumns.Count > value)
            _sortedColumns.RemoveRange(value - 1, _sortedColumns.Count);
         _sortedColumns.Capacity = value;
         _maxSortColumns = value;
      }
   }

GeneralRe: MaxSortColumnsmemberPTA_UK5 Jun '08 - 8:06 
Well spotted - thanks.
QuestionLicense?memberRGFindlay3 Apr '08 - 6:09 
Under what license is this code available?
 
Thanks

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 17 Apr 2007
Article Copyright 2007 by PTA_UK
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid