繁体   English   中英

如何自然字符串对数据绑定到数据表的 datagridview 进行排序

[英]How can I natural string sort a datagridview that is databound to a datatable

在我的程序中,我有一个使用绑定源绑定到数据表的 datagridview。 我想要完成的是能够使用自然字符串排序按列对 datagridview 进行排序。

示例列数据:

XAB-1
XAB-2
XAB-11
XAB-3
XAB-1A
XAB-10
XAB-1B

期望的结果:

XAB-1
XAB-1A
XAB-1B
XAB-2
XAB-3
XAB-10
XAB-11

我曾尝试使用传入自然字符串 Icomparer 的 datagridview.sort 方法,但是当 datagridview 为数据绑定时,无法使用排序 function。

我也尝试了这个线程Link的解决方案,但涉及将 bindingsource 源从数据表更改为数据视图。 这是一个问题,因为数据正在数据表中更新,并且没有反映在数据视图中。

有什么方法可以执行自然字符串排序,同时仍然保持绑定源到数据表? 或者有什么方法可以将上面链接中的解决方案与数据视图一起使用,但是让数据视图以某种方式将数据与数据表同步?

这是我尝试使用的 Icomparer:

Imports System.Runtime.InteropServices
Imports System.Text.RegularExpressions

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

Public Class NumStrCmp
    Implements IComparer

    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
        Dim regex As Regex = New Regex("(?<NumPart>\d+)(?<StrPart>\D*)", RegexOptions.Compiled)
        Dim mx = regex.Match(x.ToString)
        Dim my = regex.Match(y.ToString)
        Dim ret = Integer.Parse(mx.Groups("NumPart").Value).CompareTo(Integer.Parse(my.Groups("NumPart").Value))
        If ret <> 0 Then Return ret
        Return mx.Groups("StrPart").Value.CompareTo(my.Groups("StrPart").Value)
    End Function
End Class

API 声明:

Imports System.Runtime.InteropServices

Public Module NativeMethods

    <DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
    Public Function StrCmpLogicalW(x As String, y As String) As Integer
    End Function

End Module

自定义比较器:

Public Class NaturalStringComparer
    Implements IComparer(Of String)

    Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
        Return NativeMethods.StrCmpLogicalW(x, y)
    End Function

End Class

以下测试代码需要具有DataGridView和具有默认名称的BindingSource的表单:

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'Create standard table.
        Dim table As New DataTable

        With table.Columns
            .Add("Id", GetType(Integer))
            .Add("Code", GetType(String))
        End With

        With table.Rows
            .Add(1, "XAB-1")
            .Add(2, "XAB-2")
            .Add(3, "XAB-11")
            .Add(4, "XAB-3")
            .Add(5, "XAB-1A")
            .Add(6, "XAB-10")
            .Add(7, "XAB-1B")
        End With

        'Add order column.
        table.Columns.Add("Order", GetType(Integer))

        'Set the row order.
        OrderTableRows(table, "Code", "Order")

        'Bind and display in appropriate sort order.
        BindingSource1.DataSource = table
        BindingSource1.Sort = "Order"
        DataGridView1.DataSource = BindingSource1
    End Sub

    Private Sub OrderTableRows(table As DataTable, sortColumnName As String, orderColumnName As String)
        Dim rows = table.Rows.Cast(Of DataRow)().ToArray()

        'Get the value to sort by for each row.
        Dim sortValues = Array.ConvertAll(rows, Function(row) row.Field(Of String)(sortColumnName))

        'Sort the rows by the sort values using a natural comparison.
        Array.Sort(sortValues, rows, New NaturalStringComparer)

        'Number the rows sequentially based on the sort order.
        For i = 0 To rows.GetUpperBound(0)
            rows(i)(orderColumnName) = i
        Next
    End Sub

End Class

这将按您想要的顺序显示记录。 如果您对Code列进行了任何更改,即编辑现有行或添加新行,那么您将需要再次调用OrderTableRows并且数据将正确使用。

在真实的应用程序中,您可能不想显示Order列,您可以通过显式隐藏它来实现,或者在设计器中添加网格列并省略该列,然后在代码中将AutoGenerateColumns设置为False 如果您希望能够单击网格列 header 进行排序,则需要将SortMode设置为Programmatic ,然后在后台使用此排序方法。

编辑:

我扩展了上面的示例,以便在单击Code列 header 单元格时启用排序。 首先,我在设计器中添加了IdCode列。 这是生成的代码:

'
'idColumn
'
Me.idColumn.DataPropertyName = "Id"
Me.idColumn.HeaderText = "Id"
Me.idColumn.Name = "idColumn"
'
'codeColumn
'
Me.codeColumn.DataPropertyName = "Code"
Me.codeColumn.HeaderText = "Code"
Me.codeColumn.Name = "codeColumn"
Me.codeColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic

您可以在设计器中添加列,然后相应地设置这些属性。 然后我禁用了自动生成列,这样就不会为Order表列生成网格列:

'Bind and display in appropriate sort order.
BindingSource1.DataSource = table
BindingSource1.Sort = "Order"
DataGridView1.AutoGenerateColumns = False
DataGridView1.DataSource = BindingSource1

最后,我检测到对Code列 header 的点击,并按Order列对BindingSource进行了排序。 如果排序当前是按不同的列,我按升序排序,否则我切换方向:

Private Sub DataGridView1_ColumnHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles DataGridView1.ColumnHeaderMouseClick
    Dim column = DataGridView1.Columns(NameOf(codeColumn))

    If e.ColumnIndex = column.Index Then
        'Sort by Order as a proxy for Code. Use ascending order by default.
        Dim sort = "Order"
        Dim direction = SortOrder.Ascending

        If DataGridView1.SortedColumn Is Nothing AndAlso
           BindingSource1.Sort?.StartsWith("Order", StringComparison.InvariantCultureIgnoreCase) AndAlso
           Not BindingSource1.Sort?.EndsWith("DESC", StringComparison.InvariantCultureIgnoreCase) Then
            'Already sorted in ascending direction by Order as a proxy for Code so reverse direction.
            sort &= " DESC"
            direction = SortOrder.Descending
        End If

        BindingSource1.Sort = sort
        column.HeaderCell.SortGlyphDirection = direction
    End If
End Sub

对用户来说,它看起来就像没有Order列,而Code列自然是自动排序的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM