83

I have a datagridview on my form and I populate it with this:

dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })
                                   .OrderBy(s => s.Apellidos)
                                   .ToList();

Now, I use the s.Apellidos as the default sort, but I'd also like to allow users to sort when clicking on the column header.

This sort will not modify the data in any way, it's just a client side bonus to allow for easier searching for information when scanning the screen with their eyes.

0

19 Answers 19

59

Set all the columns (which can be sortable by users) SortMode property to Automatic

dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })
   .OrderBy(s => s.Apellidos)
   .ToList();

foreach(DataGridViewColumn column in dataGridView1.Columns) {
    column.SortMode = DataGridViewColumnSortMode.Automatic;
}

As your datagridview is bound with a LINQ query, it will not be sorted. So please go through Presenting the Sortablebindinglist, by Tim Van Wassenhove, which explains how to create a sortable binding list and to then feed it as datasource to datagridview:

public Form1()
{
  InitializeComponent();

  SortableBindingList<person> persons = new SortableBindingList<person>();
  persons.Add(new Person(1, "timvw", new DateTime(1980, 04, 30)));
  persons.Add(new Person(2, "John Doe", DateTime.Now));

  this.dataGridView1.AutoGenerateColumns = false;
  this.ColumnId.DataPropertyName = "Id";
  this.ColumnName.DataPropertyName = "Name";
  this.ColumnBirthday.DataPropertyName = "Birthday";
  this.dataGridView1.DataSource = persons;
}
Sign up to request clarification or add additional context in comments.

1 Comment

Btw, your foreach could be simpler: foreach(DataGridViewColumn column in dataGridView1.Columns) column.SortMode = DataGridViewColumnSortMode.Automatic;
30

As Marshal suggested, use a SortableBindingList. I've used this very successfully with the DataGridView.

Here's a link to the updated code I used, also by Tim Van Wassenhove - Presenting the SortableBindingList - Take Two

Just add the two source files to your project, and you'll be in business.

1 Comment

This solution does not need LINQ and works in .net 2.0 and there are other useful functionality to be found in BindingList like change notification. Thanks for sharing.
15

One more way to do this is using "System.Linq.Dynamic" library. You can get this library from Nuget. No need of any custom implementations or sortable List :)

using System.Linq.Dynamic;
private bool sortAscending = false;

private void dataGridView_ColumnHeaderMouseClick ( object sender, DataGridViewCellMouseEventArgs e )
{
    if ( sortAscending )
        dataGridView.DataSource = list.OrderBy ( dataGridView.Columns [ e.ColumnIndex ].DataPropertyName ).ToList ( );
    else
        dataGridView.DataSource = list.OrderBy ( dataGridView.Columns [ e.ColumnIndex ].DataPropertyName ).Reverse ( ).ToList ( );
    sortAscending = !sortAscending;
}

2 Comments

Use OrderByDescending for the else case would be better.
works great! Dont forget to add dataGridView.ColumnHeaderMouseClick += dataGridView_ColumnHeaderMouseClick; And to implement this for BindingList which updates every time when data come in... you have to modify the code... dataGridView.DataSource = new BindingList<Data>(dataList.OrderBy(dataGridView.Columns[e.ColumnIndex].DataPropertyName).ToList());
11

You don't need to create a binding datasource. If you want to apply sorting for all of your columns, here is a more generic solution of mine;

private int _previousIndex;
private bool _sortDirection;

private void gridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.ColumnIndex == _previousIndex)
        _sortDirection ^= true; // toggle direction

    gridView.DataSource = SortData(
        (List<MainGridViewModel>)gridReview.DataSource, gridReview.Columns[e.ColumnIndex].Name, _sortDirection);

    _previousIndex = e.ColumnIndex;
}

public List<MainGridViewModel> SortData(List<MainGridViewModel> list, string column, bool ascending)
{
    return ascending ? 
        list.OrderBy(_ => _.GetType().GetProperty(column).GetValue(_)).ToList() :
        list.OrderByDescending(_ => _.GetType().GetProperty(column).GetValue(_)).ToList();
}

Make sure you subscribe your data grid to the event ColumnHeaderMouseClick. When the user clicks on the column it will sort by descending. If the same column header is clicked again, sorting will be applied by ascending.

Comments

6

your data grid needs to be bound to a sortable list in the first place.

Create this event handler:

    void MakeColumnsSortable_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
    {
        //Add this as an event on DataBindingComplete
        DataGridView dataGridView = sender as DataGridView;
        if (dataGridView == null)
        {
            var ex = new InvalidOperationException("This event is for a DataGridView type senders only.");
            ex.Data.Add("Sender type", sender.GetType().Name);
            throw ex;
        }

        foreach (DataGridViewColumn column in dataGridView.Columns)
            column.SortMode = DataGridViewColumnSortMode.Automatic;
    }

And initialize the event of each of your datragrids like this:

        dataGridView1.DataBindingComplete += MakeColumnsSortable_DataBindingComplete;

1 Comment

I liked the idea of using the DataBindingComplete event, but this doesn't address not having a sortable dataset. I've edited it to make it a bit more reusable
5

You can use DataGridViewColoumnHeaderMouseClick event like this :

Private string order = String.Empty;
private void dgvDepartment_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    if (order == "d")
{
        order = "a";                
dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })   .OrderBy(s => s.Apellidos).ToList();
    }
    else
    {
        order = "d";
        dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth }.OrderByDescending(s => s.Apellidos)  .ToList()
    }
}

2 Comments

i wanna use this idea, but how can i manage which column header clicked? to sort datagridview order by that specific column name? thanks
@mr.dev.eloper - The DataGridViewCellMouseEventArgs object contains the "ColumnIndex" property.
4


there is quite simply solution when using Entity Framework (version 6 in this case). I'm not sure but it seems to ObservableCollectionExtensions.ToBindingList<T> method returns implementation of sortable binding list. I haven't found source code to confirm this supposition but object returning from this method works with DataGridView very well especially when sorting columns by clicking on its headers.

The code is very simply and relies only on .net and entity framework classes:

using System.Data.Entity;

IEnumerable<Item> items = MethodCreatingItems();

var observableItems = new System.Collections.ObjectModel.ObservableCollection<Item>(items);
System.ComponentModel.BindingList<Item> source = observableItems.ToBindingList();

MyDataGridView.DataSource = source;

Comments

4

put this line in your windows form (on load or better in a public method like "binddata" ):

//
// bind the data and make the grid sortable 
//
this.datagridview1.MakeSortable( myenumerablecollection ); 

Put this code in a file called DataGridViewExtensions.cs (or similar)

// MakeSortable extension. 
// this will make any enumerable collection sortable on a datagrid view.  

//
// BEGIN MAKESORTABLE - Mark A. Lloyd
//
// Enables sort on all cols of a DatagridView 

//



    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    public static class DataGridViewExtensions
    {
    public static void MakeSortable<T>(
        this DataGridView dataGridView, 
        IEnumerable<T> dataSource,
        SortOrder defaultSort = SortOrder.Ascending, 
        SortOrder initialSort = SortOrder.None)
    {
        var sortProviderDictionary = new Dictionary<int, Func<SortOrder, IEnumerable<T>>>();
        var previousSortOrderDictionary = new Dictionary<int, SortOrder>();
        var itemType = typeof(T);
        dataGridView.DataSource = dataSource;
        foreach (DataGridViewColumn c in dataGridView.Columns)
        {
            object Provider(T info) => itemType.GetProperty(c.Name)?.GetValue(info);
            sortProviderDictionary[c.Index] = so => so != defaultSort ? 
                dataSource.OrderByDescending<T, object>(Provider) : 
                dataSource.OrderBy<T,object>(Provider);
            previousSortOrderDictionary[c.Index] = initialSort;
        }

        async Task DoSort(int index)
        {

            switch (previousSortOrderDictionary[index])
            {
                case SortOrder.Ascending:
                    previousSortOrderDictionary[index] = SortOrder.Descending;
                    break;
                case SortOrder.None:
                case SortOrder.Descending:
                    previousSortOrderDictionary[index] = SortOrder.Ascending;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            IEnumerable<T> sorted = null;
            dataGridView.Cursor = Cursors.WaitCursor;
            dataGridView.Enabled = false;
            await Task.Run(() => sorted = sortProviderDictionary[index](previousSortOrderDictionary[index]).ToList());
            dataGridView.DataSource = sorted;
            dataGridView.Enabled = true;
            dataGridView.Cursor = Cursors.Default;

        }

        dataGridView.ColumnHeaderMouseClick+= (object sender, DataGridViewCellMouseEventArgs e) => DoSort(index: e.ColumnIndex);
    }
}

Comments

3

I suggest using a DataTable.DefaultView as a DataSource. Then the line below.

foreach (DataGridViewColumn column in gridview.Columns)
    {
       column.SortMode = DataGridViewColumnSortMode.Automatic;
    }

After that the gridview itself will manage sorting(Ascending or Descending is supported.)

1 Comment

Just passing a DataTable did the trick for me. No fiddling with addition modules etc. Thanky you so much!
2

KISS : Keep it simple, stupid

Way A: Implement an own SortableBindingList class when like to use DataBinding and sorting.

Way B: Use a List<string> sorting works also but does not work with DataBinding.

Comments

1

If you get an error message like

An unhandled exception of type 'System.NullReferenceException' occurred in System.Windows.Forms.dll

if you work with SortableBindingList, your code probably uses some loops over DataGridView rows and also try to access the empty last row! (BindingSource = null)

If you don't need to allow the user to add new rows directly in the DataGridView this line of code easily solve the issue:

InitializeComponent();
m_dataGridView.AllowUserToAddRows = false; // after components initialized
...

Comments

1
  1. Create a class which contains all properties you need, and populate them in the constructor

    class Student
    {
        int _StudentId;
        public int StudentId {get;}
        string _Name;
        public string Name {get;}
        ...
    
        public Student(int studentId, string name ...)
        { _StudentId = studentId; _Name = name; ... }
    }
    
  2. Create an IComparer < Student > class, to be able to sort

    class StudentSorter : IComparer<Student>
    {
        public enum SField {StudentId, Name ... }
        SField _sField; SortOrder _sortOrder;
    
        public StudentSorder(SField field, SortOrder order)
        { _sField = field; _sortOrder = order;}
    
        public int Compare(Student x, Student y)
        {
            if (_SortOrder == SortOrder.Descending)
            {
                Student tmp = x;
                x = y;
                y = tmp;
            }
    
            if (x == null || y == null)
                return 0;
    
            int result = 0;
            switch (_sField)
            {
                case SField.StudentId:
                    result = x.StudentId.CompareTo(y.StudentId);
                    break;
                case SField.Name:
                    result = x.Name.CompareTo(y.Name);
                    break;
                    ...
            }
    
            return result;
        }
    }
    
  3. Within the form containing the datagrid add

    ListDictionary sortOrderLD = new ListDictionary(); //if less than 10 columns
    private SortOrder SetOrderDirection(string column)
    {
        if (sortOrderLD.Contains(column))
        {
            sortOrderLD[column] = (SortOrder)sortOrderLD[column] == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
        }
        else
        {
            sortOrderLD.Add(column, SortOrder.Ascending);
        }
    
        return (SortOrder)sortOrderLD[column];
    }
    
  4. Within datagridview_ColumnHeaderMouseClick event handler do something like this

    private void dgv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        StudentSorter sorter = null;
        string column = dGV.Columns[e.ColumnIndex].DataPropertyName; //Use column name if you set it
        if (column == "StudentId")
        {
            sorter = new StudentSorter(StudentSorter.SField.StudentId, SetOrderDirection(column));
        }
        else if (column == "Name")
        {
            sorter = new StudentSorter(StudentSorter.SField.Name, SetOrderDirection(column));
        }
    
        ...
    
        List<Student> lstFD = datagridview.DataSource as List<Student>;
        lstFD.Sort(sorter);
        datagridview.DataSource = lstFD;
        datagridview.Refresh();
    }
    

Hope this helps

Comments

1

In my case, the problem was that I had set my DataSource as an object, which is why it didn't get sorted. After changing from object to a DataTable it workd well without any code complement.

Comments

0

Just in case somebody still looks for it, I did it on VS 2008 C#.

On the Event ColumnHeaderMouseClick, add a databinding for the gridview, and send the order by field like a parameter. You can get the clicked field as follows:

dgView.Columns[e.ColumnIndex].Name

In my case the header's names are similar to view field names.

Comments

0

I have a BindingList<> object bind as a data source to dataGridView.

BindingList x1;
x1 = new BindingList<sourceObject>();
BindingSource bsx1 = new BindingSource();
bsx1.DataSource = x1;
dataGridView1.DataSource = bsx1;

When I clicked the column header, no sorting takes place. I used the SortableBindingList answer provided by Tom Bushell. Having included two source files into my project

  1. SortableBindingList.cs
  2. PropertyComparer.cs

Then this change is made to my code:

Be.Timvw.Framework.ComponentModel.SortableBindingList x1;                       // 1
x1 = new Be.Timvw.Framework.ComponentModel.SortableBindingList<sourceObject>(); // 2
BindingSource bsx1 = new BindingSource();
bsx1.DataSource = x1;
dataGridView1.DataSource = bsx1;

After these changes I performed a build on my program. I am now able to sort by clicking the column headers. Only two lines need changing, they are highlighted in the code snippet above by trailing comments.

Comments

0

If using a DataTable: dgv.DataSource = (DataTable)table

You can automatically enable Sorting for objects that contain the IComparable Interface. After creating the DataTable, when adding the columns be sure to set the type also to at least object: table.Columns.Add("ColumnName", typeof(object))

Otherwise, if you do Not specifically give it a type, it converts the object to a string.

I spent a fair amount of time creating a dgv_ColumnHeaderMouseClick() event because it was Not sorting the DataGridView correctly, then to find that all you need to do is specify the type for the column name, and it sorts properly. And the reason it was not sorting correctly previously was because without specifying the type for DataTable columns, it will convert objects to strings.

Comments

0

Just instead of passing a list to the datagrid, you store the search result as a datatable.

dataGridView1.DataSource = students
  .Select(s => new { 
       ID = s.StudentId, 
       RUDE = s.RUDE, 
       Nombre = s.Name, 
       Apellidos = s.LastNameFather + " " + s.LastNameMother, 
       Nacido = s.DateOfBirth })
  .OrderBy(s => s.Apellidos)
  .ToDataTable();

Comments

0

Totally "generic" version, no binding needed, type-free, just a little reflection. Copy-paste and enjoy.

        bool sortDirection;

        void DataGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            var dataGrid = (DataGridView)sender;
            var colName = dataGrid.Columns[e.ColumnIndex].Name;
            var dataSource = ((IEnumerable)dataGrid.DataSource).Cast<object>().ToList();

            sortDirection = !sortDirection;

            if (sortDirection)
                this.dataGrid.DataSource = dataSource.OrderBy(x => x.GetType().GetProperty(colName).GetValue(x, null)).ToList();
            else
                this.dataGrid.DataSource = dataSource.OrderByDescending(x => x.GetType().GetProperty(colName).GetValue(x, null)).ToList();
        }

Comments

0

This is all that you need to do in order to achieve sorting on DataGrid.

[Note: Replace the Data Source from "GenericReportDetail" to the data source (list of items) that you want to bind with your data grid and dataGridView1 to your dataGridView name]

private int _previousIndex;
private bool _sortDirection;

private void dataGridView1_ColumnHeaderMouseClick(global::System.Object sender, global::System.Windows.Forms.DataGridViewCellMouseEventArgs e)
    {
        try
        {
            if (e.ColumnIndex == _previousIndex)
            {
                // Toggle the sort direction if the same column is clicked again
                _sortDirection = !_sortDirection;
            }
            else
            {
                // Reset the sort direction if a different column is clicked
                _sortDirection = true;
                dataGridView1.Columns[_previousIndex].HeaderCell.SortGlyphDirection = SortOrder.None;
            }

            // Sort the data and set the sorted list as the data source for 
    the DataGridView
    List<GenericReportDetail> sortedList = 
    SortData((List<GenericReportDetail>)dataGridView1.DataSource, 
    dataGridView1.Columns[e.ColumnIndex].Name, _sortDirection);
                dataGridView1.DataSource = sortedList;

                // Set the sort glyph direction for the sorted column
            dataGridView1.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = _sortDirection ? SortOrder.Ascending : SortOrder.Descending;

            // Store the current column index as the previous index
            _previousIndex = e.ColumnIndex;
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error : "+ex.Message, "Error");
        }
    }
    public List<GenericReportDetail> SortData(List<GenericReportDetail> list, string column, bool ascending)
    {
        return ascending ?
            list.OrderBy(_ => _.GetType().GetProperty(column).GetValue(_)).ToList() :
            list.OrderByDescending(_ => _.GetType().GetProperty(column).GetValue(_)).ToList();
    }

//And add the following in your page load event, 
//it will sort you first column of the dataGrid and display an arrow sign (sort glyph direction) on the column header.

 string firstColumnName = dataGridView1.Columns[0].DataPropertyName;
 List<GenericReportDetail> sortedList = 
SortData((List<GenericReportDetail>)dataGridView1.DataSource, firstColumnName, true);
 dataGridView1.DataSource = sortedList;

 // Set the sort glyph direction for the first column
 dataGridView1.Columns[0].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
 _previousIndex = 0;
 _sortDirection = true;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.