Объявление 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
, и данные будут исправлены правильно.
В реальном app, вы можете захотеть не отображать этот столбец Order
, что вы можете сделать, явно скрывая его, или же добавьте столбцы сетки в конструктор и опустите его, а затем установите AutoGenerateColumns
на False
в коде. Если вы хотите иметь возможность щелкнуть заголовок столбца сетки для сортировки, вам нужно будет установить SortMode
на Programmatic
, а затем использовать этот метод сортировки за кулисами.
EDIT:
Я расширил приведенный выше пример, чтобы включить сортировку при щелчке по ячейке заголовка столбца Code
. Во-первых, я добавил в дизайнер столбцы 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
Вы можете добавить столбцы в конструктор, а затем соответствующим образом установить эти свойства. Затем я отключил автоматическое создание c столбцов, чтобы столбец сетки не создавался для столбца Order
таблицы:
'Bind and display in appropriate sort order.
BindingSource1.DataSource = table
BindingSource1.Sort = "Order"
DataGridView1.AutoGenerateColumns = False
DataGridView1.DataSource = BindingSource1
Наконец, я обнаружил щелчки по заголовку столбца Code
и отсортировал BindingSource
по столбцу Order
. Если сортировка в настоящее время производилась по другому столбцу, я отсортировал его в порядке возрастания, в противном случае я изменил направление:
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
столбец автоматически сортируется естественным образом.