对不含数据源的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
posted @ 2012-04-10 09:55  Serviceboy  阅读(1866)  评论(0编辑  收藏  举报