自定义比较器datagridview sort

时间:2016-02-12 11:07:15

标签: c# vb.net sorting datagridview bindingsource

我有一个带有bindingsource作为数据源的datagridview,而bindingsource有一个数据表作为数据源。有些列是字符串,但我希望它们以特定方式排序。

网格将它们分类为1,10,10,04a,6c。

但我希望他们排序:1,6c,10,44a,100,好像我只从值中取数字并相应地对它们进行排序。

有些方法可以在某些列进行排序时添加自定义比较器吗?如果不更改grid,bindingsource,datatable架构,那么任何其他的元素都可以。

3 个答案:

答案 0 :(得分:1)

Is there a way I can add a custom comparer是的!

当DGV绑定到DataSource时,您必须对源进行操作(排序)而不是DGV本身。这排除了使用SortCompare事件等一些选项。以下方法使用DataView

首先,我从Natural String Sorter from this answer开始并进行了一些更改:

Imports System.Runtime.InteropServices

Partial Class NativeMethods
    <DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
    Private Shared Function StrCmpLogicalW(s1 As String, s2 As String) As Int32
    End Function

    Friend Shared Function NaturalStringCompare(str1 As String, str2 As String) As Int32
        Return StrCmpLogicalW(str1, str2)
    End Function
End Class

Public Class NaturalStringComparer
    Implements IComparer(Of String)
    Private mySortFlipper As Int32 = 1

    Public Sub New()

    End Sub

    Public Sub New(sort As SortOrder)
        mySortFlipper = If(sort = SortOrder.Ascending, 1, -1)
    End Sub

   Public Function Compare(x As String, y As String) As Integer _
            Implements IComparer(Of String).Compare

        ' convert DBNull to empty string
        Dim x1 = If(String.IsNullOrEmpty(x), String.Empty, x)
        Dim y1 = If(String.IsNullOrEmpty(y), String.Empty, y)

        Return (mySortFlipper * NativeMethods.NaturalStringCompare(x1, y1))
    End Function
End Class

可以通过各种方式使用Comparer,如链接问题所示。它通常用于List个文件名之类的内容。由于此处的排序目标是DB数据,因此在遇到空数据时会向Compare添加几行。 (OP,mvaculisteanu,发现传递空值时速度很慢)。

这也可行,作为一个单独的步骤处理,可以轻松添加其他边缘情况:

Return (mySortFlipper * NativeMethods.NaturalStringCompare(If(x, ""), If(y,""))

我不知道你是如何使用BindingSource的,所以我不得不对配置做出一些猜测。我的测试DataTable有3列,#1设置为编程实现比较器。使用的表单级对象变量(因此您了解我的配置 - 希望它类似):

Private dgvDV As DataView
Private dgvBS As BindingSource

' config:
dgvDV = New DataView(dgvDT)

dgvBS = New BindingSource()
dgvBS.DataMember = "myDT"
dgvBS.DataSource = dgvDT

dgv2.Columns(0).SortMode = DataGridViewColumnSortMode.Automatic
dgv2.Columns(1).SortMode = DataGridViewColumnSortMode.Programmatic
dgv2.Columns(2).SortMode = DataGridViewColumnSortMode.Automatic

魔术,例如它,发生在ColumnHeaderMouseClick事件中:

Private SortO As SortOrder = SortOrder.Ascending
Private Sub dgv2_ColumnHeaderMouseClick(sender As Object...etc

    ' the special column we want to sort:
    If e.ColumnIndex = 1 Then
        ' create new DV
        dgvDV = DGVNaturalColumnSort("Text", SortO)

        ' reset the BindingSource:
        dgvBS.DataSource = dgvDV
        ' update glyph
        dgv2.Columns(1).HeaderCell.SortGlyphDirection = SortO

        ' flip order for next time:
        SortO = If(SortO = SortOrder.Ascending, SortOrder.Descending, SortOrder.Ascending)
    End If
End Sub

然后,一个辅助函数实现排序并创建一个新的DataView

Private Function DGVNaturalColumnSort(colName As String, sortt As SortOrder) As DataView
    Dim NComparer As New NaturalStringComparer(sortt)
    Dim tempDT = dgvDV.Table.AsEnumerable().
        OrderBy(Function(s) s.Field(Of String)(colName), NComparer).
        CopyToDataTable

    Return New DataView(tempDT)
End Function

因为您传递了列的名称,所以当有多个这样的列时,它应该很容易使用。结果:

enter image description here

在顶部排序无,然后在下面排序Asc和Desc

保留用户对列(例如订单和宽度)的更改。这也在没有 a BindingSource的情况下正常工作。只需使用DataView作为DataSource

  dgvBS.DataSource = dgvDV

使用DataTable作为DataSource可能会出现问题,并且“#34}更重”&#34;因为你必须复制表。 DataView使这很简单。

我也发现了AlphaNumeric sorter for java。好奇,我把它转换成.NET来比较它们。它运作良好但不完全相同。给定相同的起点,1000个序列中的25-35个通常会以不同的方式出现:

 PInvoke:  03, 03, 03s, 3A
Alphanum:  03, 3A...3RB, 03s, 3X 

它并非完全错误,03s位于正确的区域,结果会在此之后同步恢复一段时间。它还以不同的方式处理前导破折号,并且比PInvoke慢一点。它确实处理了没有任何值。

答案 1 :(得分:0)

是的,但我认为您必须拥有另一列,因为计算机会读取第一个数字,然后是第二个数字。这就是为什么当你有10时它会在6之前读出它。(1低于6)我过去做过的另一种方法就是拥有前导零。因此,在1个隐藏列中,您将有前导零例如:000006,000100,然后是用户的可见列,您将获得原始数据,但排序列将是您隐藏的列。

答案 2 :(得分:0)

一开始看起来相当简单,然后真的很辛苦乏味,最后结果真的很简单......对于LINQ来说很好!

首先编写一个将string列转换为数值的函数:

int noLetters(string text)
{
    char c = text[text.Length - 1];
    if (c < '0' || c > '9') text = text.Substring(0, text.Length - 1);
    int n = 0;
    Int32.TryParse(text, out n);
    return n;
}

注意:该功能仅测试最后一个字母,并切断任何非数字的字符。请检查此您的规则并添加您的异常处理

现在通过DataView将带有新功能的数据整理到EnumerableRowCollection

EnumerableRowCollection<DataRow> sortedQuery =
    from row in dt.AsEnumerable()
    orderby noLetters(row.Field<string>("yourColumnName"))
    select row ;

DataView sortedView = sortedQuery.AsDataView();
yourBindingSource.DataSource = sortedView ;

要通过点击列标题触发它,只需编写ColumnHeaderMouseClick事件代码..

当然,您可以添加逻辑以在orderbyorderbydescending

之间切换