简体   繁体   English

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

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

In my program, I have a datagridview that is bound to a datatable using a binding source.在我的程序中,我有一个使用绑定源绑定到数据表的 datagridview。 What I would like to accomplish to be able to sort the datagridview by a column using a natural string sort.我想要完成的是能够使用自然字符串排序按列对 datagridview 进行排序。

Sample Column Data:示例列数据:

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

Desired Result:期望的结果:

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

I have tried using the datagridview.sort method passing in a natural string Icomparer, but the sort function cannot be used when the datagridview is databound.我曾尝试使用传入自然字符串 Icomparer 的 datagridview.sort 方法,但是当 datagridview 为数据绑定时,无法使用排序 function。

I have also tried the solution from this thread Link , but involves changing the bindingsource source from the datatable to a dataview.我也尝试了这个线程Link的解决方案,但涉及将 bindingsource 源从数据表更改为数据视图。 This is an issue as data is being updated in the datatable and is not being reflected in the dataview.这是一个问题,因为数据正在数据表中更新,并且没有反映在数据视图中。

Is there some way that I can perform a natural string sort while still maintain the binding source to the datatable?有什么方法可以执行自然字符串排序,同时仍然保持绑定源到数据表? Or is there some way I could use the solution in the link above with the dataview, but have the dataview somehow sync the data with the datatable?或者有什么方法可以将上面链接中的解决方案与数据视图一起使用,但是让数据视图以某种方式将数据与数据表同步?

Here is the Icomparer I was trying to use:这是我尝试使用的 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 declaration: 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

Custom comparer:自定义比较器:

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

The following test code requires a form with a DataGridView and a BindingSource with default names:以下测试代码需要具有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

That will display the records in the order you want.这将按您想要的顺序显示记录。 If you ever make any changes to the Code column, ie edit an existing row or add a new row, then you would need to call OrderTableRows again and the data would resort correctly.如果您对Code列进行了任何更改,即编辑现有行或添加新行,那么您将需要再次调用OrderTableRows并且数据将正确使用。

In a real app, you may want to not display that Order column, which you can do by explicitly hiding it or else add your grid columns in the designer and omit that one, then set AutoGenerateColumns to False in code.在真实的应用程序中,您可能不想显示Order列,您可以通过显式隐藏它来实现,或者在设计器中添加网格列并省略该列,然后在代码中将AutoGenerateColumns设置为False If you want to be able to click a grid column header to sort then you will need to set the SortMode to Programmatic and then use this sorting method behind the scenes.如果您希望能够单击网格列 header 进行排序,则需要将SortMode设置为Programmatic ,然后在后台使用此排序方法。

EDIT:编辑:

I have extended the example above to enable sorting when clicking the Code column header cell.我扩展了上面的示例,以便在单击Code列 header 单元格时启用排序。 Firstly, I added Id and Code columns in the designer.首先,我在设计器中添加了IdCode列。 Here's the code that that generated:这是生成的代码:

'
'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

You can add the columns in the designer and then set those properties accordingly.您可以在设计器中添加列,然后相应地设置这些属性。 I then disabled automatic generation of columns, so that no grid column would be generated for the Order table column:然后我禁用了自动生成列,这样就不会为Order表列生成网格列:

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

Finally, I detected clicks on the Code column header and sorted the BindingSource by the Order column.最后,我检测到对Code列 header 的点击,并按Order列对BindingSource进行了排序。 If sorting was currently by a different column, I sorted in ascending order, otherwise I switched the direction:如果排序当前是按不同的列,我按升序排序,否则我切换方向:

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

To the user, it looks just like there is no Order column and that the Code column is automatically sorted naturally.对用户来说,它看起来就像没有Order列,而Code列自然是自动排序的。

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

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