[英]DataGridView custom sort not on DataMember (Databound Grid)
[英]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 单元格时启用排序。 首先,我在设计器中添加了Id
和Code
列。 这是生成的代码:
'
'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.