使用通用列表作为基础源的C#DataGridView排序

C# DataGridView sorting with Generic List as underlying source

我正在使用Windows Forms DataGridView来显示MyObject对象的常规列表。

首先,我将此集合包装为BindingSource集合,然后:

1
dataGridView.DataSource = myBindingSource;

我想做的是允许用户通过单击代表MyObject中具体属性的列标题对列进行排序。

我已经阅读了一些在绑定之前应该进行排序的文章。 但是,如果我想实时对列进行排序(对已经绑定的列称)不起作用。

问题是,我到底需要做什么,所以我可以在DataGridView中看到排序箭头,并且可以对每一列进行排序?


完整的代码对datagridview的列进行排序,该列的数据源是通用列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//-----------------------------------------------------------------------------------------
//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;
        }
    }


我很难相信网格不提供开箱即用的基本排序,不需要代码。毕竟,处理标题单击事件并调用DataGridView.Sort指示列(由单击的内容确定,由网格跟踪)和排序方向(由当前的排序状态确定,由网格跟踪)是很愚蠢的)。

为什么没有简单的SortMode或AllowUserToSort属性在默认情况下会做完全相同的事情?

我已经将网格绑定到列表,并且将列映射到的属性都是基本类型,例如string,int,DateTime等。所有这些都是IComparable。那么,为什么我到底应该只编写一行代码?特别是考虑到文档内容如下:

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

MSDN

那是Framework 3.0文档,我的目标是3.5,但是"其他版本"都是指Visual Studio的版本,而不是Framework的版本。微软到底在发生什么?!?


本文"呈现SortableBindingList"中的一个很好的解决方案:
http://www.timvw.be/2007/02/22/presenting-the-sortablebindinglistt/


这是一个使用Reflection和Linq按列进行排序的简单解决方案。 dataGridView1的DataSource设置为compareList,声明为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    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; }
    }
}


我的解决方案是这样的:

我自己使用myBindingSource进行工作,我进行排序,将..分组在单独的线程中。
然后,我简单地将结果绑定到DataGridView。

1
myDataGridView.DataSource = bindingSource;

为此,我将所有列设置为'Programatically'排序(在设计器中)
然后我手动添加箭头(升序/降序)
通过设置

1
cell.SortGlyphDirection = ... ;

在后面的代码中。


如果最好创建自己的用户控件,则可以使用以下代码创建自定义排序方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    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;
    }

然后,您可以覆盖标题click方法来调用您的sort函数:

1
2
3
4
5
6
7
8
9
10
    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);
    }

解决与列表绑定时DataGridView排序问题的另一种选择是,如果您不处理庞大的数据集,则可能可以尝试将List转换为DataTable,然后将所得的DataTable绑定到BindingSource / DataGridView。

这将需要自定义实现IComparer。就我而言,我处理的列表较小,但是要显示的字段更多。因此,实现IComparer意味着编写太多样板代码。

检查此内容以获取将列表转换为数据表的简洁方法:https://stackoverflow.com/a/34062898/4534493


您可能还想看一下这篇文章,在这里您可以获得两个有趣的链接,以实现自定义的SortableBindingList:

当数据源绑定到List(Of T)时对Datagridview列进行排序


看到这条

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

通过阅读它,我看到了"此方法通过比较指定列中的值对DataGridView的内容进行排序。默认情况下,排序操作将使用Compare方法使用DataGridViewCell .. ::比较列中的单元格对。重视财产。"

最好的祝福,
约旦