对不含数据源的DataGridView实现自定义排序
我们知道如果对DataGridView直接设置数据源进行绑定,并且启用“排序”的话,直接点击列名就可以实现绑定。现在的问题在于如果这个DataGridView没有设定数据源(数据是动态添加的),如何对这样的数据进行排序呢?
[C#]
public partial class Form1 : Form { DataGridView dv = new DataGridView(); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { dv.Parent = this; dv.Dock = DockStyle.Fill; dv.Columns.Add("DSLL", "DSLL"); //Generate random numbers as String Random r = new Random(Guid.NewGuid().GetHashCode()); for (int i = 1; i < 101; i++) { dv.Rows.Add(r.Next(1, 101).ToString()); } } ……………………
[VB.NET]
Public Partial Class Form1 Inherits Form Private dv As New DataGridView() Public Sub New() InitializeComponent() End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) dv.Parent = Me dv.Dock = DockStyle.Fill dv.Columns.Add("DSLL", "DSLL") 'Generate random numbers as String Dim r As New Random(Guid.NewGuid().GetHashCode()) For i As Integer = 1 To 100 dv.Rows.Add(r.[Next](1, 101).ToString()) Next …………………… End Sub End Class
现在问题是:如果你这样直接点击DSLL列的话“数字列”根本不是按照原先的样子进行排序——究竟原因,是因为现有列(代码中)存储的是String类型,如果点击排序的话默认将直接按照String进行大小比较,而不是真实的数值型进行比较;倘若你使用反射工具就可以看到其内部工作原理,这里截取关键部分:
1)当点击某一列的时候,实际上程序内部调用公开的方法Sort——该方法有两个重载版本(一个是实现IComparer接口的自定义排序对象,另外一个是指定某列按照什么顺序进行排序的,稍后对他们进行深入讲解……).
Sort方法调用必须满足以下两个条件之一:
i)整个DataGridView不是VirtualMode模式。
ii)排序的该列已经绑定到数据源的某个对应的列中。
具体可以透过以下源码证明:
public virtual void Sort(DataGridViewColumn dataGridViewColumn, ListSortDirection direction) { if (dataGridViewColumn != null) { if (direction == ListSortDirection.Ascending || direction == ListSortDirection.Descending) { if (dataGridViewColumn.DataGridView == this) { if (!this.VirtualMode || dataGridViewColumn.IsDataBound) { this.SortInternal(null, dataGridViewColumn, direction); return; } else { throw new InvalidOperationException(SR.GetString("DataGridView_OperationDisabledInVirtualMode")); } } else { throw new ArgumentException(SR.GetString("DataGridView_ColumnDoesNotBelongToDataGridView")); } } else { throw new InvalidEnumArgumentException("direction", direction, typeof(ListSortDirection)); } } else { throw new ArgumentNullException("dataGridViewColumn"); } }
[VB.NET]
Public Overridable Sub Sort(ByVal dataGridViewColumn As DataGridViewColumn, ByVal direction As ListSortDirection) If (dataGridViewColumn <> Nothing) Then If (direction = ListSortDirection.Ascending OrElse direction = ListSortDirection.Descending) Then If (dataGridViewColumn.DataGridView = Me) Then If (Not Me.VirtualMode OrElse dataGridViewColumn.IsDataBound) Then Me.SortInternal(Nothing, dataGridViewColumn, direction) Return Else Throw New InvalidOperationException(SR.GetString("DataGridView_OperationDisabledInVirtualMode")) End If Else Throw New ArgumentException(SR.GetString("DataGridView_ColumnDoesNotBelongToDataGridView")) End If Else Throw New InvalidEnumArgumentException("direction", direction, GetType(ListSortDirection)) End If Else Throw New ArgumentNullException("dataGridViewColumn") End If End Sub
看得出内部调用了一个SortInternal方法,继续跟踪(此处代码极多,故显示主要部分):
[C#]
private void SortInternal(IComparer comparer, DataGridViewColumn dataGridViewColumn, ListSortDirection direction) { …………………… if (comparer != null) { this.sortedColumn = null; this.sortOrder = SortOrder.None; } else { this.sortedColumn = dataGridViewColumn; DataGridView dataGridView = this; if (direction == ListSortDirection.Ascending) { num = 1; } else { num = 2; } dataGridView.sortOrder = (SortOrder)num; if (dataGridViewColumn.SortMode == DataGridViewColumnSortMode.Automatic && dataGridViewColumn.HasHeaderCell) { dataGridViewColumn.HeaderCell.SortGlyphDirection = this.sortOrder; } } if (this.DataSource != null) { this.SortDataBoundDataGridView_PerformCheck(dataGridViewColumn); this.dataConnection.Sort(dataGridViewColumn, direction); } else { this.UpdateRowsDisplayedState(false); this.Rows.Sort(comparer, direction == ListSortDirection.Ascending); } ……………… }
[VB.NET]
Private Sub SortInternal(ByVal comparer As IComparer, ByVal dataGridViewColumn As DataGridViewColumn, ByVal direction As ListSortDirection) ……………… If (comparer <> Nothing) Then Me.sortedColumn = Nothing Me.sortOrder = SortOrder.None Else Me.sortedColumn = dataGridViewColumn Dim dataGridView As DataGridView = Me If (direction = ListSortDirection.Ascending) Then num = 1 Else num = 2 End If dataGridView.sortOrder = DirectCast(num, SortOrder) If (dataGridViewColumn.SortMode = DataGridViewColumnSortMode.Automatic AndAlso dataGridViewColumn.HasHeaderCell) Then dataGridViewColumn.HeaderCell.SortGlyphDirection = Me.sortOrder End If End If If (Me.DataSource <> Nothing) Then Me.SortDataBoundDataGridView_PerformCheck(dataGridViewColumn) Me.dataConnection.Sort(dataGridViewColumn, direction) Else Me.UpdateRowsDisplayedState(False) Me.Rows.Sort(comparer, direction = ListSortDirection.Ascending) End If …………………… End Sub
首先内部方法先判断你是否已经传入了一个实现了IComparer接口排序类,如果实现纯粹根据IComparer指定的列排序,没有必要指定排序列以及排序顺序(因为原则上实现IComparer只实现了针对一个列的一个方向的排序——要不升序,要不降序);此外,如果没有实现该接口,那么就做Else的部分——先判断该列是否为自动排序,并且有没有抬头单元格:如果都有,那么设置该列的排序方向图标(列抬头单元格旁边的小箭头)是“升”还是“降”;因此,像本示例不是“自动排序”的话,排序方向默认是不会显示的,需要你手动设置SortGlyhDirection属性。然后接着判断是否绑定了数据源:如果绑定了,则直接根据数据源进行排序;否则人工手动排序。
为了继续研究下去,看Rows.Sort方法:
[C#]
internal void Sort(IComparer customComparer, bool ascending) { if (this.items.Count > 0) { RowComparer rowComparer = new RowComparer(this, customComparer, ascending); this.items.CustomSort(rowComparer); } }
[VB.NET]
Friend Sub Sort(ByVal customComparer As IComparer, ByVal ascending As Boolean) If (Me.items.Count > 0) Then Dim rowComparer As RowComparer = New RowComparer(Me, customComparer, ascending) Me.items.CustomSort(rowComparer) End If End Sub
Rows的Sort方法先调用了一个RowComparer类进行排序,继续跟踪看其关键部分:
[C#]
private class RowComparer { ……………… internal int CompareObjects(object value1, object value2, int rowIndex1, int rowIndex2) { if (value1 as ComparedObjectMax == null) { if (value2 as ComparedObjectMax == null) { int num = 0; if (this.customComparer != null) { num = this.customComparer.Compare(value1, value2); } else { if (!this.dataGridView.OnSortCompare(this.dataGridViewSortedColumn, value1, value2, rowIndex1, rowIndex2, out num)) { if (value1 as IComparable != null || value2 as IComparable != null) { num = Comparer.Default.Compare(value1, value2); } else { if (value1 != null) { if (value2 != null) { num = Comparer.Default.Compare(value1.ToString(), value2.ToString()); } else { num = -1; } } else { if (value2 != null) { num = 1; } else { num = 0; } } } if (num == 0) { if (!this.@ascending) { num = rowIndex2 - rowIndex1; } else { num = rowIndex1 - rowIndex2; } } } } if (!this.@ascending) { return -num; } else { return num; } } else { return -1; } } else { return 1; } } ……………… }
[VB.NET]
Private Class RowComparer …………………… Friend Function CompareObjects(ByVal value1 As Object, ByVal value2 As Object, ByVal rowIndex1 As Integer, ByVal rowIndex2 As Integer) As Integer If (TryCast(value1, ComparedObjectMax) = Nothing) Then If (TryCast(value2, ComparedObjectMax) = Nothing) Then Dim num As Integer = 0 If (Me.customComparer <> Nothing) Then num = Me.customComparer.Compare(value1, value2) Else If (Not Me.dataGridView.OnSortCompare(Me.dataGridViewSortedColumn, value1, value2, rowIndex1, rowIndex2, num)) Then If (TryCast(value1, IComparable) <> Nothing OrElse TryCast(value2, IComparable) <> Nothing) Then num = Comparer.Default.Compare(value1, value2) Else If (value1 <> Nothing) Then If (value2 <> Nothing) Then num = Comparer.Default.Compare(value1.ToString(), value2.ToString()) Else num = -1 End If Else If (value2 <> Nothing) Then num = 1 Else num = 0 End If End If End If If (num = 0) Then If (Not Me.ascending) Then num = rowIndex2 - rowIndex1 Else num = rowIndex1 - rowIndex2 End If End If End If End If If (Not Me.ascending) Then Return -num Else Return num End If Else Return -1 End If Else Return 1 End If End Function ……………… End Class
这里就明确告诉了你——如果你已经实现了IComparer接口则直接使用此进行排序;否则先调用内部的OnSortCompare函数(此函数引发一个SortCompare事件)判断该事件是否被人为“Handle”了,如果是,那么直接按照事件中的代码进行排序处理——因为value1和value2都是字符串型,则排序按照字符串进行排列,自然不是我们预期的效果。
【解决方案】
既然绕了一大圈为了清楚了解DataGridView排序的内幕干了什么,我们自然顺理成章可以得出这个结论:
0)预备:VirtualMode=false,指定DSLL为Programmic(自定义排序)。
1)先引发Sort的第二个重载函数,指定要排序的列和排序顺序。
2)然后HandleSortCompare事件,进一步自己处理如何排序。
至于“引发”Sort函数,我们可以考虑在ColumnMouseHeaderClick事件中(此事件是点击“列”时候引发)处理——首先判断那个列是不是要排序的列,继而调用不同的升序、降序方法进行排列即可。
[C#]
namespace WinFormCSharp { public partial class Form1 : Form { DataGridView dv = new DataGridView(); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { dv.Parent = this; dv.AllowUserToAddRows = false; dv.Dock = DockStyle.Fill; dv.Columns.Add("DSLL", "DSLL"); //随机生成测试数据 Random r = new Random(Guid.NewGuid().GetHashCode()); for (int i = 1; i < 101; i++) { dv.Rows.Add(r.Next(1, 101).ToString()); } //设置该列为手动排序列 dv.Columns[0].SortMode = DataGridViewColumnSortMode.Programmatic; //设定点击列的事件以便处理排序 dv.ColumnHeaderMouseClick += dv_ColumnHeaderMouseClick; //默认先按照升序排列 dv.Sort(dv.Columns[0],ListSortDirection.Ascending); dv.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending; //处理自定义排序的事件 dv.SortCompare += dv_SortCompare; dv.VirtualMode = false; } void dv_SortCompare(object sender, DataGridViewSortCompareEventArgs e) { if (e.Column.Name.Equals("DSLL")) { int value1 = Convert.ToInt32(e.CellValue1); int value2 = Convert.ToInt32(e.CellValue2); e.SortResult = value1 > value2 ? 1 : (value1 == value2 ? 0 : -1); } e.Handled = true; } void dv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { //左键才处理 if (e.Button == MouseButtons.Left && e.ColumnIndex==0) { if (dv.SortOrder == SortOrder.Ascending) { dv.Sort(dv.Columns[0], ListSortDirection.Descending); dv.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Descending; } else { dv.Sort(dv.Columns[0], ListSortDirection.Ascending); dv.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending; } } } } }
[VB.NET]
Namespace WinFormCSharp Public Partial Class Form1 Inherits Form Private dv As New DataGridView() Public Sub New() InitializeComponent() End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) dv.Parent = Me dv.AllowUserToAddRows = False dv.Dock = DockStyle.Fill dv.Columns.Add("DSLL", "DSLL") '随机生成测试数据 Dim r As New Random(Guid.NewGuid().GetHashCode()) For i As Integer = 1 To 100 dv.Rows.Add(r.[Next](1, 101).ToString()) Next '设置该列为手动排序列 dv.Columns(0).SortMode = DataGridViewColumnSortMode.Programmatic '设定点击列的事件以便处理排序 AddHandler dv.ColumnHeaderMouseClick, AddressOf dv_ColumnHeaderMouseClick '默认先按照升序排列 dv.Sort(dv.Columns(0), ListSortDirection.Ascending) dv.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending '处理自定义排序的事件 AddHandler dv.SortCompare, AddressOf dv_SortCompare dv.VirtualMode = False End Sub Private Sub dv_SortCompare(sender As Object, e As DataGridViewSortCompareEventArgs) If e.Column.Name.Equals("DSLL") Then Dim value1 As Integer = Convert.ToInt32(e.CellValue1) Dim value2 As Integer = Convert.ToInt32(e.CellValue2) e.SortResult = IIf(value1 > value2, 1, (IIf(value1 = value2, 0, -1))) End If e.Handled = True End Sub Private Sub dv_ColumnHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) '左键才处理 If e.Button = MouseButtons.Left AndAlso e.ColumnIndex = 0 Then If dv.SortOrder = SortOrder.Ascending Then dv.Sort(dv.Columns(0), ListSortDirection.Descending) dv.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Descending Else dv.Sort(dv.Columns(0), ListSortDirection.Ascending) dv.SortedColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending End If End If End Sub End Class End Namespace