Как я могу естественной строкой отсортировать datagridview, который привязан к данным - PullRequest
1 голос
/ 07 мая 2020

В моей программе у меня есть datagridview, привязанный к datatable с помощью источника привязки. Что я хотел бы выполнить sh, чтобы иметь возможность сортировать 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

Я пробовал использовать метод datagridview.sort, передавая естественную строку Icomparer, но функцию сортировки нельзя использовать, если datagridview привязан к данным.

Я также пробовал решение из этого потока Ссылка , но требует изменения источника привязки с datatable на dataview. Это проблема, поскольку данные обновляются в таблице данных и не отражаются в представлении данных.

Есть ли способ, которым я могу выполнить естественную сортировку строк, сохраняя при этом источник привязки к таблице данных? Или есть способ использовать решение в приведенной выше ссылке с представлением данных, но при этом представление данных каким-то образом синхронизирует c данные с данными?

Вот 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

1 Ответ

2 голосов
/ 07 мая 2020

Объявление 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 столбец автоматически сортируется естественным образом.

...