我如何自然地对数据绑定到数据表的datagridview进行字符串排序 [英] How can I natural string sort a datagridview that is databound to a datatable
问题描述
在我的程序中,我有一个使用绑定源绑定到数据表的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屋!