Is there a way I can add a custom comparer
Yes!
当 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
正如链接问题所证明的那样,该比较器可以以多种方式使用。它通常用于诸如List
文件名。由于这里的排序目标是数据库数据,因此添加了几行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
event:
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 排序在下面
用户对列的更改(例如顺序和宽度)将被保留。这也是工作得很好,没有 a BindingSource
。只需使用你的DataView
as the DataSource
:
dgvBS.DataSource = dgvDV
Using a DataTable
as the DataSource
可能会出现问题并且“更重”,因为您必须复制表格。 ADataView
让这变得非常简单。
我也发现了这个java 的字母数字排序器。出于好奇,我将其转换为.NET 进行比较。它运作良好,但不完全相同。给定相同的起点,1000 个序列中的 25-35 个通常会出现不同的结果:
PInvoke: 03, 03, 03s, 3A
Alphanum: 03, 3A...3RB, 03s, 3X
这并不完全错误,03s
位于正确的区域,之后结果会同步备份一段时间。它还以不同的方式处理前导破折号,并且比 PInvoke 慢一些。但它确实可以很好地处理 Nothing 值。