我如何自然地对数据绑定到数据表的datagridview进行字符串排序 [英] How can I natural string sort a datagridview that is databound to a datatable

查看:39
本文介绍了我如何自然地对数据绑定到数据表的datagridview进行字符串排序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

样品列数据:

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

所需结果:

  XAB-1XAB-1AXAB-1BXAB-2XAB-3XAB-10XAB-11 

我尝试使用datagridview.sort方法传入一个自然字符串Icomparer,但是当datagridview被数据绑定时,不能使用sort函数.

我也在此线程链接中尝试了该解决方案,但涉及更改绑定源从数据表到数据视图.这是一个问题,因为数据正在数据表中更新,而没有反映在数据视图中.

是否有某种方式可以执行自然字符串排序,同时仍保持数据源的绑定源?还是有某种方法可以在上面的链接中将解决方案与dataview一起使用,但是让dataview以某种方式将数据与datatable同步?

这是我尝试使用的比较器:

 导入System.Runtime.InteropServices导入System.Text.RegularExpressions局部类NativeMethods< DllImport("shlwapi.dll",CharSet:= CharSet.Unicode)>私有共享函数StrCmpLogicalW(s1作为字符串,s2作为字符串)作为Int32结束功能朋友共享的函数NaturalStringCompare(str1作为字符串,str2作为字符串)作为Int32返回StrCmpLogicalW(str1,str2)结束功能末级公共类NaturalStringComparer实现IComparer(String)私有mySortFlipper为Int32 = 1公开子New()结束子公开子类别New(排序为SortOrder)mySortFlipper = If(sort = SortOrder.Ascending,1,-1)结束子公共函数Compare(x作为字符串,y作为字符串)作为整数_实现IComparer(Of String).Compare'将DBNull转换为空字符串Dim x1 = If(String.IsNullOrEmpty(x),String.Empty,x)Dim y1 = If(String.IsNullOrEmpty(y),String.Empty,y)返回(mySortFlipper * NativeMethods.NaturalStringCompare(x1,y1))结束功能末级公共类NumStrCmp实现IComparer公共函数Compare(ByVal x作为对象,ByVal y作为对象),因为Integer实现IComparer.CompareDim regex As Regex = New Regex((?< NumPart> \ d +)(?< StrPart> \ D *)",RegexOptions.Compiled)昏暗的mx = regex.Match(x.ToString)昏暗的我= regex.Match(y.ToString)点心= Integer.Parse(mx.Groups("NumPart").Value).CompareTo(Integer.Parse(my.Groups("NumPart").Value))如果ret<>0然后返回ret返回mx.Groups("StrPart").Value.CompareTo(my.Groups("StrPart").Value)结束功能末级 

解决方案

API声明:

 导入System.Runtime.InteropServices公共模块NativeMethods< DllImport("shlwapi.dll",CharSet:= CharSet.Unicode)>公共函数StrCmpLogicalW(x作为字符串,y作为字符串)作为整数结束功能终端模块 

自定义比较器:

 公共类NaturalStringComparer实现IComparer(String)公共函数Compare(x作为字符串,y作为字符串)作为整数实现IComparer(Of String).Compare返回NativeMethods.StrCmpLogicalW(x,y)结束功能末级 

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

 公共类Form1私有子Form1_Load(作为对象发送,作为EventArgs发送)处理MyBase.Load'创建标准表.昏暗表作为新数据表带table.Columns.Add("Id",GetType(Integer)).Add("Code",GetType(String))结束于带桌子.行.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")结束于'添加订单列.table.Columns.Add("Order",GetType(Integer))'设置行顺序.OrderTableRows(表格,代码",订单")'绑定并以适当的排序顺序显示.BindingSource1.DataSource =表BindingSource1.Sort =订单"DataGridView1.DataSource =绑定源1结束子私有子OrderTableRows(表作为数据表,sortColumnName作为字符串,orderColumnName作为字符串)暗行= table.Rows.Cast(Of DataRow)().ToArray()'获取要为每一行排序的值.Dim sortValues = Array.ConvertAll(行,Function(行)row.Field(字符串)(sortColumnName))'使用自然比较按排序值对行进行排序.Array.Sort(sortValues,rows,New NaturalStringComparer)'根据排序顺序对行进行编号.对于i = 0到rows.GetUpperBound(0)行(i] [orderColumnName)= i下一个结束子末级 

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

在实际应用中,您可能不希望显示该 Order 列,您可以通过显式隐藏该列或在设计器中添加网格列并忽略该列,然后设置<将code> AutoGenerateColumns 转换为 False .如果您希望能够单击网格列标题进行排序,则需要将 SortMode 设置为 Programmatic ,然后在幕后使用此排序方法.

我已经扩展了上面的示例,以在单击 Code 列标题单元格时启用排序.首先,我在设计器中添加了 Id Code 列.这是生成的代码:

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

您可以在设计器中添加列,然后相应地设置这些属性.然后,我禁用了自动生成列的功能,因此不会为 Order 表列生成任何网格列:

 '绑定并以适当的排序顺序显示.BindingSource1.DataSource =表BindingSource1.Sort =订单"DataGridView1.AutoGenerateColumns = FalseDataGridView1.DataSource =绑定源1 

最后,我检测到对 Code 列标题的点击,并按 Order 列对 BindingSource 进行了排序.如果当前是按其他列排序,则我将按升序排序,否则我会切换方向:

 <代码>私有子DataGridView1_ColumnHeaderMouseClick(作为对象发送,作为DataGridViewCellMouseEventArgs发送)处理DataGridView1.ColumnHeaderMouseClick昏暗的列= DataGridView1.Columns(NameOf(codeColumn))如果e.ColumnIndex = column.Index然后按订单排序,作为Code的代理.默认情况下使用升序.昏暗的排序=订单"暗淡的方向= SortOrder.Ascending如果DataGridView1.SortedColumn也没有BindingSource1.Sort?.StartsWith("Order",StringComparison.InvariantCultureIgnoreCase)And还不是BindingSource1.Sort?.EndsWith("DESC",StringComparison.InvariantCultureIgnoreCase)然后'已经按顺序以升序排序,作为Code的代理,因此方向相反.排序& ="DESC"方向= SortOrder.Descending万一BindingSource1.Sort =排序column.HeaderCell.SortGlyphDirection =方向万一结束子 

对于用户来说,看起来好像没有 Order 列,并且 Code 列是自然排序的.

In my program, I have a datagridview that is bound to a datatable using a binding source. What I would like to accomplish to be able to sort the datagridview by a column using a natural string sort.

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.

I have also tried the solution from this thread Link, but involves changing the bindingsource source from the datatable to a dataview. 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:

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:

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:

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, i.e. edit an existing row or add a new row, then you would need to call OrderTableRows again and the data would resort correctly.

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

EDIT:

I have extended the example above to enable sorting when clicking the Code column header cell. Firstly, I added Id and Code columns in the designer. 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:

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

这篇关于我如何自然地对数据绑定到数据表的datagridview进行字符串排序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆