自定义比较器数据网格视图排序

Custom comparer datagridview sort

我有一个数据网格视图,其中绑定源作为数据源,绑定源有一个数据表作为数据源。有些列是字符串,但我希望它们以特定方式排序。

网格将它们排序为 1, 10, 10,0 44a, 6c。

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

有没有一种方法可以在对某些列进行排序时添加自定义比较器?如果不更改网格、绑定源、数据表架构,任何其他解决方案都可以。

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

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

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

首先,我从 开始,做了一些改动:

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

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

在上面排序 None,然后在下面排序 Asc 和 Desc

保留用户对列的更改,例如顺序和宽度。这也 在没有 BindingSource 的情况下工作得很好。只需使用您的 DataView 作为 DataSource:

  dgvBS.DataSource = dgvDV

使用 DataTable 作为 DataSource 可能会有问题,并且 "heavier" 因为您必须复制 table。 DataView 使这变得非常简单。


我也找到了这个AlphaNumeric sorter for java。出于好奇,我将其转换为 .NET 以比较它们。它运作良好但不完全相同。给定相同的起点,1000 个序列中的 25-35 个通常会出现不同的结果:

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

它并非完全错误,03s 在正确的区域并且结果同步备份了一段时间。它还以不同方式处理前导破折号,并且比 PInvoke 慢一点。不过,它确实可以很好地处理 Nothing 值。

起初看起来很简单,然后真的很困难和乏味,最后证明真的很简单。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;
}

注意:该函数仅测试最后一个字母并删除任何非数字的内容。请检查您的规则并添加您的异常处理

现在通过 EnumerableRowCollection:

将具有新函数的数据排序到 DataView
EnumerableRowCollection<DataRow> sortedQuery =
    from row in dt.AsEnumerable()
    orderby noLetters(row.Field<string>("yourColumnName"))
    select row ;

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

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

当然可以添加逻辑在orderbyorderbydescending

之间切换