![](/img/trans.png)
[英]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.