2016-02-12 115 views
1

我有一個datagridview與綁定源作爲數據源,並且該綁定源具有作爲數據源的數據表。有些列是字符串,但我希望它們以特定方式排序。自定義比較器datagridview排序

網格將它們排序爲1,10,10,0 44a,6c。

但我希望他們排序:1,6c,10,44a,100好像我只會從數值中獲取數字並相應地對它們進行排序。

有沒有辦法在某些列正在排序時添加自定義比較器?如果grid,bindingsource,datatable schema沒有改變,任何其他soulutions都可以。

回答

1

Is there a way I can add a custom comparer是的!

當DGV綁定到DataSource時,您必須對源進行操作(排序)而不是DGV本身。這排除了一些選項,例如使用SortCompare事件。以下方法使用DataView

首先,我開始與Natural String Sorter from this answer,並提出了一些變化:

Imports System.Runtime.InteropServices 

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 

那的Comparer可以從鏈接的問題證明多種方式使用。它通常用於諸如文件名的List之類的東西。由於這裏的排序目標是DB數據,因此當遇到空數據時,幾行添加到Compare。 (OP,mvaculisteanu,當通過空值時發現它很慢)。

這也將工作,作爲一個單獨的步驟等邊緣處理的情況下,可以很容易地補充說:

Return (mySortFlipper * NativeMethods.NaturalStringCompare(If(x, ""), If(y,"")) 

我不知道你是如何使用的BindingSource,所以我不得不做出的一些猜測組態。我的測試DataTable有3列,#1設置爲編程實現比較器。表級對象變量使用(所以你明白我的配置 - 希望這是類似):

Private dgvDV As DataView 
Private dgvBS As BindingSource 

' config: 
dgvDV = New DataView(dgvDT) 

dgvBS = New BindingSource() 
dgvBS.DataMember = "myDT" 
dgvBS.DataSource = dgvDT 

dgv2.Columns(0).SortMode = DataGridViewColumnSortMode.Automatic 
dgv2.Columns(1).SortMode = DataGridViewColumnSortMode.Programmatic 
dgv2.Columns(2).SortMode = DataGridViewColumnSortMode.Automatic 

的魔法,比如它是發生在ColumnHeaderMouseClick事件:

Private SortO As SortOrder = SortOrder.Ascending 
Private Sub dgv2_ColumnHeaderMouseClick(sender As Object...etc 

    ' the special column we want to sort: 
    If e.ColumnIndex = 1 Then 
     ' create new DV 
     dgvDV = DGVNaturalColumnSort("Text", SortO) 

     ' reset the BindingSource: 
     dgvBS.DataSource = dgvDV 
     ' update glyph 
     dgv2.Columns(1).HeaderCell.SortGlyphDirection = SortO 

     ' flip order for next time: 
     SortO = If(SortO = SortOrder.Ascending, SortOrder.Descending, SortOrder.Ascending) 
    End If 
End Sub 

然後,助手功能它實現了排序,並創建一個新的DataView

Private Function DGVNaturalColumnSort(colName As String, sortt As SortOrder) As DataView 
    Dim NComparer As New NaturalStringComparer(sortt) 
    Dim tempDT = dgvDV.Table.AsEnumerable(). 
     OrderBy(Function(s) s.Field(Of String)(colName), NComparer). 
     CopyToDataTable 

    Return New DataView(tempDT) 
End Function 

因爲你通過列的名稱,它應該很容易當有MULTIP使用列這樣的列。結果:在頂部

enter image description here

排序無,然後排序ASC和DESC下面如順序和寬度

用戶改變到列(多個)被保留。這也工作得很好,沒有 a BindingSource

dgvBS.DataSource = dgvDV 

使用DataTableDataSource可能是有問題的,「重」,因爲你必須複製表:你的DataViewDataSource只需使用。 A DataView使這很簡單。


我也發現這個AlphaNumeric sorter for java。很好奇,我將它轉換爲.NET來比較它們。它運作良好,但不完全相同。鑑於同樣的起點,1000個序列的25-35通常會出來不同:

PInvoke: 03, 03, 03s, 3A 
Alphanum: 03, 3A...3RB, 03s, 3X 

它不是完全錯了,03s是在正確的區域,結果後同步備份一段時間。它也以不同的方式處理前導破折號,並且比PInvoke慢一點。它確實處理沒有值罰款。

+0

謝謝,我測試了你的方法,效果很好。我有一個觀察和一個問題。 O:如果列包含空值,那麼排序非常慢,因此我用'Return(mySortFlipper * NativeMethods.NaturalStringCompare(If(x,「」))替換了'Return(mySortFlipper * NativeMethods.NaturalStringCompare(x,y))'如果(y,「」)))'Q:我已經從[這裏]讀取(https://msdn.microsoft.com/en-us/library/windows/desktop/bb759947(v = vs.85).aspx )從Windows XP和Windows Server 2003開始,可以使用'StrCmpLogicalW'功能。它總是有空或有人可以禁用它 – mvaculisteanu

+0

啊,有趣!我沒有想過用空值來測試它 - 它通常會被用在列表和集合中,最糟糕的情況是你正在處理空字符串。我會檢查的。用戶不能禁用它 - 它是操作系統的一部分。 – Plutonix

0

是的,但是我認爲您必須另有一列,因爲計算機會讀取第一個數字,然後讀取第二個數字。這就是爲什麼當你有10個時,它會在6之前讀取它。(1低於6)我曾經做過的一個方法是有前導零。因此,在1個隱藏列中,您應該有前導零。例如:000006,000100,然後是用戶的可見列,您將擁有原始數據,但排序列將是您隱藏的列。

+0

我應該提到這種方法可以在我的項目中使用,我覺得它有點麻煩。它的工作原理雖然不如Plutonix那樣優雅。 – mvaculisteanu

0

這看起來相當簡單,然後真的很難和乏味,最後竟然是真的很簡單.. Hooray for LINQ!

先寫你的string列轉換爲數值的功能:

int noLetters(string text) 
{ 
    char c = text[text.Length - 1]; 
    if (c < '0' || c > '9') text = text.Substring(0, text.Length - 1); 
    int n = 0; 
    Int32.TryParse(text, out n); 
    return n; 
} 

注:此功能只測試的最後一個字母,並砍掉任何不是一個數字。請檢查您的規則並添加您的異常處理

現在通過EnumerableRowCollection與新功能的數據順序爲DataView

EnumerableRowCollection<DataRow> sortedQuery = 
    from row in dt.AsEnumerable() 
    orderby noLetters(row.Field<string>("yourColumnName")) 
    select row ; 

DataView sortedView = sortedQuery.AsDataView(); 
yourBindingSource.DataSource = sortedView ; 

要通過列標題的點擊觸發它只是代碼的ColumnHeaderMouseClick事件..

當然你可以添加邏輯來切換orderbyorderbydescending