20
votes

I'm using a Windows Forms DataGridView to display a generic list of MyObject objects.

First of all I wrap this collection into a BindingSource Collection, then:

dataGridView.DataSource = myBindingSource;

What I want to do is allow the user to sort the columns by clickin on the header of the column representing a concrete Property in MyObject.

I've read some articles that I should do sorting before binding. But it does not help me if I want to sort the columns in real time, being said when it's already binded.

The question is, what exactly do I need to do, so I could see the sorting arrows in DataGridView and I could sort every column ?

10
See stackoverflow.com/questions/3770857/…. It's as simple as dataGridView.DataSource = new BindingListView<MyObject>(myObjects.ToList()) with the library at sourceforge.net/projects/blw.Pat

10 Answers

30
votes

Complete code to sort the column of datagridview whose datasource is a generic List

//-----------------------------------------------------------------------------------------
//In the form - In constructor or form load, populate the grid.
//--------------------------------------------------------------------------------------------

    List<student> students;

    private void PopulateList()
    {
        student std1 = new student("sss", 15, "Female");
        student std2 = new student("ddd", 12, "Male");
        student std3 = new student("zzz", 16, "Male");
        student std4 = new student("qqq", 14, "Female");
        student std5 = new student("aaa", 11, "Male");
        student std6 = new student("lll", 13, "Female");

        students = new List<student>();
        students.Add(std1);
        students.Add(std2);
        students.Add(std3);
        students.Add(std4);
        students.Add(std5);
        students.Add(std6);

        dataGridView1.DataSource = students;
    }


//---------------------------------------------------------------------------------------------
//Comparer class to perform sorting based on column name and sort order
//---------------------------------------------------------------------------------------------


class StudentComparer : IComparer<Student>
{
    string memberName = string.Empty; // specifies the member name to be sorted
    SortOrder sortOrder = SortOrder.None; // Specifies the SortOrder.

    /// <summary>
    /// constructor to set the sort column and sort order.
    /// </summary>
    /// <param name="strMemberName"></param>
    /// <param name="sortingOrder"></param>
    public StudentComparer(string strMemberName, SortOrder sortingOrder)
    {
        memberName = strMemberName;
        sortOrder = sortingOrder;
    }

    /// <summary>
    /// Compares two Students based on member name and sort order
    /// and return the result.
    /// </summary>
    /// <param name="Student1"></param>
    /// <param name="Student2"></param>
    /// <returns></returns>
    public int Compare(Student Student1, Student Student2)
    {
        int returnValue = 1;
        switch (memberName)
        {
            case "Name" :
                if (sortOrder == SortOrder.Ascending)
                {
                    returnValue = Student1.Name.CompareTo(Student2.Name);
                }
                else
                {
                    returnValue = Student2.Name.CompareTo(Student1.Name);
                }

                break;
            case "Sex":
                if (sortOrder == SortOrder.Ascending)
                {
                    returnValue = Student1.Sex.CompareTo(Student2.Sex);
                }
                else
                {
                    returnValue = Student2.Sex.CompareTo(Student1.Sex);
                }
                break;
            default:
                if (sortOrder == SortOrder.Ascending)
                {
                    returnValue = Student1.Name.CompareTo(Student2.Name);
                }
                else
                {
                    returnValue = Student2.Name.CompareTo(Student1.StudentId);
                }
                break;
        }
        return returnValue;
    }
}



//---------------------------------------------------------------------------------------------
// Performing sort on click on Column Header
//---------------------------------------------------------------------------------------------

    private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        //get the current column details
        string strColumnName = dataGridView1.Columns[e.ColumnIndex].Name;
        SortOrder strSortOrder = getSortOrder(e.ColumnIndex);

        students.Sort(new StudentComparer(strColumnName, strSortOrder));
        dataGridView1.DataSource = null;
        dataGridView1.DataSource = students;
        customizeDataGridView();
        dataGridView1.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = strSortOrder;
    }

   /// <summary>
    /// Get the current sort order of the column and return it
    /// set the new SortOrder to the columns.
    /// </summary>
    /// <param name="columnIndex"></param>
    /// <returns>SortOrder of the current column</returns>
    private SortOrder getSortOrder(int columnIndex)
    {
        if (dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.None ||
            dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.Descending)
        {
            dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
            return SortOrder.Ascending;
        }
        else
        {
            dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Descending;
            return SortOrder.Descending;
        }
    }
9
votes

I find it hard to believe the grid doesn't provide basic sorting out of the box, no code needed. After all, it is pretty silly to have to handle a header click event and call DataGridView.Sort indicating the column (determined by what was clicked, tracked by the grid) and the sort direction (determined by current sort state, tracked by the grid).

Why isn't there simply a SortMode or an AllowUserToSort property that does exactly the same thing by default?

I've bound my grid to a List and the properties I've mapped columns to are all basic types like string, int, DateTime and so on. All of which are IComparable. So why on earth should I need to write even one line of code? Especially considering that the documentation reads:

By default, users can sort the data in a DataGridView control by clicking the header of a text box column.

MSDN

That's the Framework 3.0 doc, and I'm targeting 3.5, but "other versions" all refer to versions of Visual Studio, not versions of the Framework. What on earth is going on here Microsoft?!?

6
votes

A good Solution in this article "Presenting the SortableBindingList": http://www.timvw.be/2007/02/22/presenting-the-sortablebindinglistt/

2
votes

Here is a simpler solution to sort by column using Reflection and Linq. dataGridView1's DataSource is set to compareList which is declared as:

    private List<CompareInfo> compareList;


    private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        string strColumnName = dataGridView1.Columns[e.ColumnIndex].Name;
        SortOrder strSortOrder = getSortOrder(e.ColumnIndex);

        if (strSortOrder == SortOrder.Ascending)
        {
            compareList = compareList.OrderBy(x => typeof(CompareInfo).GetProperty(strColumnName).GetValue(x, null)).ToList();
        }
        else
        {
            compareList = compareList.OrderByDescending(x => typeof(CompareInfo).GetProperty(strColumnName).GetValue(x, null)).ToList();
        }
        dataGridView1.DataSource = compareList;
        dataGridView1.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = strSortOrder;
    }

    private SortOrder getSortOrder(int columnIndex)
    {
        if (dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.None ||
            dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.Descending)
        {
            dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
            return SortOrder.Ascending;
        }
        else
        {
            dataGridView1.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Descending;
            return SortOrder.Descending;
        }
    }


public class CompareInfo
{
    public string FileName { get; set; }

    public string UAT_Folder { get; set; }

    public string UAT_Path
    {
        get { return UAT_Folder + FileName; }
    }

    public string PROD_Folder { get; set; }

    public string PROD_Path
    {
        get { return PROD_Folder + FileName; }
    }
}
1
votes

My Solution is this:

I work with myBindingSource at my own, I do sorting, grouping ..whatever in a separate thread. Then I simply bind the result to the DataGridView.

myDataGridView.DataSource = bindingSource;

For this purpose I've setted all the columns to be sorted 'Programatically' (in designer) Then I manually add the arrow (ASCENDING / DESCENDING) by setting

cell.SortGlyphDirection = ... ; 

in code behind.

1
votes

If creating your own user control is preferable, you can make a custom sort method using the code below:

    private string _lastSortColumn;
    private ListSortDirection _lastSortDirection;

    public void Sort(DataGridViewColumn column)
    {
        // Flip sort direction, if the column chosen was the same as last time
        if (column.Name == _lastSortColumn)
            _lastSortDirection = 1 - _lastSortDirection;
        // Otherwise, reset the sort direction to its default, ascending
        else
        {
            _lastSortColumn = column.Name;
            _lastSortDirection = ListSortDirection.Ascending;
        }

        // Prep data for sorting
        var data = (IEnumerable<dynamic>)DataSource;
        var orderProperty = column.DataPropertyName;

        // Sort data
        if (_lastSortDirection == ListSortDirection.Ascending)
            DataSource = data.OrderBy(x => x.GetType().GetProperty(orderProperty).GetValue(x, null)).ToList();
        else
            DataSource = data.OrderByDescending(x => x.GetType().GetProperty(orderProperty).GetValue(x, null)).ToList();

        // Set direction of the glyph
        Columns[column.Index].HeaderCell.SortGlyphDirection
            = _lastSortDirection == ListSortDirection.Ascending
            ? SortOrder.Ascending : SortOrder.Descending;
    }

You can then override the header click method to call your sort function:

    protected override void OnColumnHeaderMouseClick(DataGridViewCellMouseEventArgs e)
    {
        base.OnColumnHeaderMouseClick(e);

        var column = Columns[e.ColumnIndex];

        if (column.SortMode == DataGridViewColumnSortMode.Automatic
            || column.SortMode == DataGridViewColumnSortMode.NotSortable)
            Sort(column);
    }
0
votes

You may also want to take a look to this post where you can get two interesting links to implement a customized SortableBindingList:

Sort Datagridview columns when datasource binded to List(Of T)

0
votes

see this artice

http://msdn.microsoft.com/en-us/library/0868ft3z.aspx

by reading it i saw this "This method sorts the contents of the DataGridView by comparing values in the specified column. By default, the sort operation will use the Compare method to compare pairs of cells in the column using the DataGridViewCell..::.Value property."

Best Regards, iordan

0
votes

Another option to solve the sorting issue with DataGridView when binding to List is, if you are not dealing with huge data set then probably you can attempt to convert the List to DataTable, and then bind the resulting DataTable to the BindingSource/DataGridView.

This would the need for a custom implementation of IComparer. In my case, I was dealing with a smaller list but there were more fields to display. So implementing IComparer meant writing too much boiler plate code.

Check this for terse way of converting the List to DataTable: https://stackoverflow.com/a/34062898/4534493

0
votes

First of all I used System.Reflection; then: write this method

 public DataTable ToDataTable<T>(List<T> items)
    {
        DataTable dataTable = new DataTable(typeof(T).Name);
        //Get all the properties
        PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | 
        BindingFlags.Instance);

        foreach (PropertyInfo prop in Props)
        {
            //Setting column names as Property names

            dataTable.Columns.Add(prop.Name);
        }
        foreach (T item in items)
        {
            var values = new object[Props.Length];

            for (int i = 0; i < Props.Length; i++)
            {
                //inserting property values to datatable rows

                values[i] = Props[i].GetValue(item, null);
            }
            dataTable.Rows.Add(values);
        }
           //put a breakpoint here and check datatable
        return dataTable;
    }

then call method : DataTable dt = ToDataTable(lst.ToList());