DataGridView - SortVerhalten - mit und ohne DataBinding

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 22 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    DataGridView - SortVerhalten - mit und ohne DataBinding

    Hi,

    Nehmen wir an, wir haben eine Datagridview wie folgt befüllt:

    VB.NET-Quellcode

    1. DataGridView1.Rows.Add("A3", "B4", "C1")
    2. DataGridView1.Rows.Add("A4", "B3", "C2")
    3. DataGridView1.Rows.Add("A2", "B2", "C3")
    4. DataGridView1.Rows.Add("A1", "B1", "C4")


    Und nehmen wir an die dritte Zeile (also die mit "A2") sei ausgewählt.

    Wenn ich jetzt auf den SpaltenHeader der ersten Spalte klicke, dann wandert "A2" von der dritten in die zweite Zeile. Und der blaue Auswahlbalken WANDERT MIT. Und genauso verhalten sich auch die Sort Clicks auf die beiden anderen Spalten.

    Jetzt machen wir das gleiche mit DataBinding:

    VB.NET-Quellcode

    1. DataSet1.DataTable1.AddDataTable1Row("A3", "B4", "C1")
    2. DataSet1.DataTable1.AddDataTable1Row("A4", "B3", "C2")
    3. DataSet1.DataTable1.AddDataTable1Row("A2", "B2", "C3")
    4. DataSet1.DataTable1.AddDataTable1Row("A1", "B1", "C4")


    Wir wählen wieder die Zeile mit "A2" aus.

    Wenn ich jetzt auf den Spaltenheader der ersten Spalte klicke wird korrekt sortiert. Aber die Zeilenauswahl WANDERT NICHT MIT ! Es bleibt nach wie vor die dritte Zeile ausgewählt! Obwohl "A2" sich nun in der ZWEITEN Zeile befindet. Und genauso verhalten sich auch Sorts der anderen Spalten ... die Zeilenauswahl ist wie festgewurzelt !

    Das ist blöde!

    Ich hätte gern, dass auch beim Datenbinding die Zeilenauswahl mitwandert. So wie ich das ohne Databinding habe.

    Kann mir jemand sagen, wie ich das am einfachsten erreichen kann ?

    LG
    Peter
    Jau das hatten wir. Aber das ist halt leichter gesagt als getan!

    Wenn SortMode "AUTOMATIC" ist, dann wird der Sort unmittelbar nach dem Klicken des Column Headers ausgelöst. Und damit ist die Position VOR dem Sort weg !

    Die Ereignisroutine FileListBindingSource.CurrentChanged ist aus dem gleichen Grund keine Alternative.

    Bleibt nur den SortMode auf "PROGRAMMATIC" zu stellen ... und dann muss ich Dinge wie etwa die alternierende Sortierreihenfolge selbst kodieren.

    Sehe ich das richtig?
    Jou.

    Peter329 schrieb:

    Aber die Zeilenauswahl WANDERT NICHT MIT !
    Die Selektierung des DGV weiß nichts von der BindingSource, umgekehrt schon.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @Peter329
    Versuche es einmal wie folgt:

    VB.NET-Quellcode

    1. Private CurrentRowID As Integer
    2. ''' <summary>
    3. ''' ausgewählte Zeile PrimeKey merken
    4. ''' </summary>
    5. Private Sub BS_PositionChanged(sender As Object, e As EventArgs) Handles DeineBindingSource.PositionChanged
    6. CurrentRowID = DirectCast(DirectCast(DeineBindingSource.Current, DataRowView).Row, DeineTabelleRow).ID 'PrimeKey
    7. End Sub
    8. ''' <summary>
    9. ''' aktive Zeile PrimeKey merken (ist nur dann notwendig, wenn mehrere DGV behandelt werden müssen)
    10. ''' </summary>
    11. Private Sub DGV_Enter(sender As Object, e As EventArgs) Handles DeinDataGridView.Enter
    12. CurrentRowID = DirectCast(DirectCast(DeineBindingSource.Current, DataRowView).Row, DeineTabelleRow).ID 'PrimeKey
    13. End Sub
    14. ''' <summary>
    15. ''' Nach Sortiervorgang wieder die zuvor ausgewählte PrimeKey auswählen
    16. ''' </summary>
    17. Private Sub DGV_Sorted(sender As Object, e As EventArgs) Handles DeinDataGridView.Sorted
    18. DeineBindingSource.Position = DeineBindingSource.Find("ID", CurrentRowID)
    19. End Sub

    ...ist aber ungetestet

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VB1963“ ()

    Erst mal herzlichen Dank an die Ratgeber.

    Jau, wenn das denn alles so einfach wäre.

    @RFG
    Die Selektierung des DGV weiß nichts von der BindingSource, umgekehrt schon.


    Na, wie immer du das gemeint hast ... aber ich denke, die Verhältnisse sind ein bissl komplexer:

    Wenn ich die BindingSource.Position ändere, dann ändert sich auch die Auswahl der DataGridView entsprechend.

    Wenn ich eine Zeile in der DataGridView auswähle, dann wird auch die BindingSource.Position entsprechend geändert.

    Soweit ist das ja auch alles ganz vernünftig.

    Wenn ich aber in der DataGridView die Auswahl AUFHEBE, also etwa mit DataTable1DataGridView.ClearSelection() dann bleibt die BindingSource.Position ERHALTEN.

    Die Tatsache, dass in der DatagridView keine Zeile ausgewählt ist, wird schlicht und ergreifend in der BindingSource NICHT reflektiert. Das sollte man vielleicht besser wissen, wenn man die Kiste richtig bedienen will.

    @EDR
    nö (was immer du damit meinst) - du musst nur BindingSource.Sort setzen.


    Ein gesundes Sortierverhalten sieht doch wie folgt aus:

    Beim erstmaligen Anzeigen der DataGridView werden die Zeilen so angezeigt, wie sie geladen wurden, also ohne Sortierung.

    Klickt man auf den ColumnHeader dann wird aufsteigend sortiert.

    Klickt man nochmal auf den gleichen ColumnHeader, dann wird absteigend sortiert. Danach wieder aufsteigend ... usw. Die Sortierreihenfolge "toggles".

    Klickt man auf einen anderen ColumnHeader wird wieder mit der aufsteigenden Sortierreihenfolge begonnen unabhängig davon, welche Reihenfolge die zuvor gewählte Spalte hatte.

    So wird das übrigens bei DataGridViews ohne DataBinding standardmäßig abgehandelt. Und deshalb sollte das Verhalten bei DataGridViews MIT DataBinding ganz genauso sein !

    Ich hab das jetzt mal wie folgt kodiert:

    VB.NET-Quellcode

    1. 'Get current key
    2. Dim HoldKey As String = Nothing 'no row selected
    3. If DataTable1DataGridView.SelectedRows.Count > 0 Then 'row selected
    4. Dim rwFile = DirectCast(DirectCast(DataTable1BindingSource.Current, DataRowView).Row, DataTable1Row)
    5. HoldKey = rwFile.DataColumn1
    6. End If
    7. 'Get current sort column and sort order
    8. Dim CurrentColumnName As String = "" 'not sorted yet
    9. Dim CurrentSortOrder As String = ""
    10. If Not DataTable1BindingSource.Sort = Nothing Then 'already sorted
    11. Dim strSort As String = DataTable1BindingSource.Sort
    12. Dim p As Integer = strSort.LastIndexOf(" ")
    13. CurrentColumnName = strSort.Substring(0, p)
    14. CurrentSortOrder = strSort.Substring(p + 1)
    15. End If
    16. 'Get actual column name
    17. Dim ColumnName As String = Nothing
    18. Select Case e.ColumnIndex 'Das geht auch eleganter ... nur zum Testen
    19. Case 0 : ColumnName = "DataColumn1"
    20. Case 1 : ColumnName = "DataColumn2"
    21. Case 2 : ColumnName = "DataColumn3"
    22. End Select
    23. 'Determine sort order
    24. Dim SortOrder As String = Nothing
    25. If CurrentColumnName = ColumnName Then 'same column sorted - toggle sort order
    26. If CurrentSortOrder = "DESC" Then
    27. SortOrder = "ASC"
    28. Else
    29. SortOrder = "DESC"
    30. End If
    31. Else 'sort column change - start with ascending order
    32. SortOrder = "ASC"
    33. End If
    34. 'Sort DataTable
    35. DataTable1BindingSource.Sort = ColumnName & " " & SortOrder
    36. 'Restore selected row from key
    37. If HoldKey = Nothing Then
    38. DataTable1DataGridView.ClearSelection() 'no row selected
    39. Else 'row selected
    40. Dim rw As DataSet1.DataTable1Row
    41. For k As Integer = 0 To DataTable1BindingSource.Count() - 1
    42. rw = DirectCast(DirectCast(DataTable1BindingSource.Item(k), DataRowView).Row, DataTable1Row)
    43. If rw.DataColumn1 = HoldKey Then
    44. DataTable1BindingSource.Position = k
    45. Exit For
    46. End If
    47. Next
    48. End If
    49. End Sub


    Ganz schön wuchtig. Aber es funktioniert hervorragend - soweit ich das bisher getestet habe. Mir gefällt vor allem, dass die Sache ohne Save-Felder außerhalb der Prozedur auskommt, dass man sich also nix "merken" muss. Vielleicht hilft dieses Code Beispiel ja anderen, die das gleiche Problem mit dem Sort haben. :)

    Na, vielleicht habt ihr ja noch Kommentare dazu.

    @VB1963
    Vielen Dank für dein Code Beispiel. Ich hab das jetzt zwar anders gelöst ... aber ausprobieren werde ich deinen Vorschlag natürlich trotzdem.

    LG
    Peter

    Peter329 schrieb:

    die Verhältnisse sind ein bissl komplexer
    Kanst Du mal die Tabelle posten und sagen, was Du wie sortiert haben möchtest?
    In Ergänzung zum Code vom EDR müsste da was schnuckeliges rauskommen. ;)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Na, da ich jetzt aber gespannt ... schließlich bin ich nicht nur lernwillig, sondern auch lernbegierig. :)

    Das angehängte Projekt enthält eine DataGridView, die über DataBinding befüllt wird.

    Klickt auf die ColumnHeader, dann seht ihr, dass das SortVerhalten genau so ist, wie ich es oben ausgeführt habe.

    Mit einem MouseRechtsklick, kann man die Selektierung aufheben. Und auch dann funktioniert der Sort wie beschrieben.

    Wenn ihr die Event Routine dgvMitDataBinding.ColumnHeaderMouseClick deaktiviert und den SortMode der Spalten auf AUTOMATIC setzt, dann seht ihr ja, wie das standardmäßig aussieht.

    Na, mal sehen, ob ihr das einfacher hinbekommt.

    LG
    Peter
    Dateien
    • TestDgv.zip

      (207,66 kB, 158 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Peter329“ ()

    Peter329 schrieb:

    Ein gesundes Sortierverhalten
    Lol - "gesundes Sortierverhalten" - gut formuliert.

    Aber kann man auch anders definieren - guck - so mal auf die Schnelle hab ich ein TripleSort-Behavior (beim 3.Klick wieder unsortiert) eingebastelt.
    Auszug daraus

    VB.NET-Quellcode

    1. Private Shared Sub Grid_ColumnHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles Grid.ColumnHeaderMouseClick
    2. Const asc = "Asc"
    3. Dim grd = DirectCast(sender, DataGridView)
    4. Dim bs = DirectCast(grd.DataSource, BindingSource)
    5. Dim sort = If(bs.Sort, "").Split
    6. Select Case True
    7. Case sort.Length < 2
    8. sort = {grd.Columns(e.ColumnIndex).DataPropertyName, asc}
    9. Case sort(1) = asc
    10. sort(1) = "Desc"
    11. Case Else
    12. sort = {}
    13. End Select
    14. bs.Sort = String.Join(" ", sort)
    15. End Sub

    Da mach ich jetzt noch weitere Behaviors dranne, und das kann dann als Tipp durchgehen.
    Dateien

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Peter329 schrieb:

    Ein gesundes Sortierverhalten sieht doch wie folgt aus:
    Beim erstmaligen Anzeigen der DataGridView werden die Zeilen so angezeigt, wie sie geladen wurden, also ohne Sortierung.
    Klickt man auf den ColumnHeader dann wird aufsteigend sortiert.
    Klickt man nochmal auf den gleichen ColumnHeader, dann wird absteigend sortiert. Danach wieder aufsteigend ... usw. Die Sortierreihenfolge "toggles".
    Klickt man auf einen anderen ColumnHeader wird wieder mit der aufsteigenden Sortierreihenfolge begonnen unabhängig davon, welche Reihenfolge die zuvor gewählte Spalte hatte.
    So wird das übrigens bei DataGridViews ohne DataBinding standardmäßig abgehandelt. Und deshalb sollte das Verhalten bei DataGridViews MIT DataBinding ganz genauso sein !
    Das ist doch bei gebunden DGv's auch so, nur das die aktuelle Position halt nicht mitzieht?

    Hab den Anhang halt mit meinen obigen Vorschlag angehängt...

    @ErfinderDesRades
    Ich kann deinen obigen Codeauszug nicht im angehängten Sample finden - hast du etwa ein falsches erwischt?
    Edit: hab's schon gesichtet...

    Dateien
    • TestDgv00.zip

      (19,92 kB, 158 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „VB1963“ ()

    jo, und jetzt hab ich auch das Restaurieren der Selection eingearbeitet - update in post#11

    Wie gesagt, das wird ein Tipp/Tut, und da kommt noch hierarchische Sortierung zu, und gruppiertes OwnerDrawing.

    Und angelegt ists auf Erweiterbarkeit, sodass man zB auch ein RedNumberPainting dafür entwickeln und zufügen kann, oder ein HideZero-Painting, damit etwa Minuszahlen rot dargestellt werden, oder Nullen als als leere Zellen und sowas.
    Aber bisher nur TripleSort und SelectionRestore.
    @VB1963
    Ich hab mir dein Code Beispiel angeschaut und WOWS, das Ding funktioniert! Das sieht wirklich KLASSE aus ! Allerdings gibt es einen kleinen Haken:

    Das ist doch bei gebunden DGv's auch so, nur das die aktuelle Position halt nicht mitzieht?


    Das ist eben nicht ganz richtig. Denn die Sache muss auch funktionieren, wenn KEINE Zeile in der DataGridView ausgewählt ist.

    Ich hab jetzt mal an dein Beispiel die folgende Even Routine zum Löschen der Auswahl angefügt:

    VB.NET-Quellcode

    1. Private Sub DataGridView1_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseDown
    2. If e.Button = Windows.Forms.MouseButtons.Right Then DataGridView1.ClearSelection()
    3. End Sub


    Na, und dann klicke doch mal die rechte Maus Taste ... und dann irgendeine Header Column ... surprise surprise ...

    Wenn man das noch in den Griff bekommen könnte, wäre deine Lösung super !

    @EDR

    Also erst mal ganz herzlichen Dank für dein Code Beispiel, dass natürlich weit über meine Anforderungen hinaus geht.

    Die Sache mit dem dreiwertigen Toggle ist natürlich eine gute Idee. Aber bei aller Wertschätzung ist das halt ein Nebenkriegsschauplatz.

    Ich hab jetzt mal deine Lösung getested. Und ich hoffe, dass du es einem Landei nachsiehst, aber so einige Sachen sind nicht koscher:

    Wenn ich etwa Article28_4_0 auswähle und dann auf Price klicke, dann wird ID = -19 mit Article18_3_5 ausgewählt. Das is m.E. halt ein bissl "schweinisch", wenn die Auswahl irgendwo in die Gegend springt!

    Wenn ich nach Name aufsteigend sortiere und dann auf Price klicke, dann wird nach Name absteigend sortiert. Ist das ein Bug oder ein Feature? :)

    Wenn ich nach CategoryID aufsteigend sortiere ... dann die erste Category6 auswähle ... und dann auf CategoryID klicke ... dann wird zwar nach CategoryID (gemäß toggle) absteigend sortiert aber markiert wird ID = -2 mit Category0 ...

    Also bei aller Wertschätzung ... aber das ist nicht was ich unter einem "gesunden" Sortierverhalten verstehe.

    Na ... ich hoffe, ich habe einige Denkanstöße für dein Tut liefern können ... für das ich natürlich sehr dankbar wäre.

    LG
    Peter

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Peter329“ ()

    @EDR

    Jau, jetzt sieht das Sort Verhalten richtig "gesund" aus. Super, so geht das in Ordnung ! Erstaunlich mit wie wenigen Zeilen dein Basis Code auskommt ... na einiges ist wohl im Helper Projekt versteckt ...

    Vielleicht eine kleine Anmerkung: du nimmst zwar die sortierte Zeile jetzt korrekt mit. Aber du verschiebst die ausgewählte Zelle prinzipiell auf die erste Spalte.

    Das mag für einige Anwendungen erwünscht sein. Aber i.a. sollte die Auswahl des Anwenders NICHT geändert werden. Es ist nämlich verdammt lästig, wenn der Anwender ständig irgendwelche Eingaben oder Auswahlen wiederholen muss, nur weil das Programm die blöderweise verbiegt! Ich fände es besser, wenn die ausgewählte Zelle einfach beibehalten wird. Dann kann der Anwender selbst entscheiden, was er machen will. Wenn ich dein Coding richtig verstehe, dann sollte das eigentlich keine großer Aufwand sein.

    Ansonsten bin ich auf den Tut gespannt ... wo du uns dann erklärst, wie dein geniales Coding im Detail funktioniert! :)

    LG
    Peter
    hmm.
    Ausnahmsweise ist das glaub ganz ohne Helpers gemacht.
    Die Helpers sind nur zum Daten-Laden da.

    Aber ich bin inzwischen drauf gekommen, dass das ja eiglich nix mit dem DGV zu tun hat - Sortierung und Kontrolle der CurrentPosition ist ja Sache der BindingSource.
    Was darauf hinausläuft, dasses auf ein BindingSource-Behavior hinausläuft statt eines DgvBehaviors. Und das integriert sich dann wieder fett in die Helpers.
    Und komischerweise verliere ich da die Lust, ein Tut draus zu machen, weil ich denke, dann müsste man die ganzen Helpers erklären, und das wird so länglich, dasses eh keiner liest.

    Ausserdem, dass du jetzt auch die Spalte restauriert haben möchtest, ist dann doch wieder ein Dgv-Concern. Womöglich willst du auch eine eventuelle Multi-Selektion restauriert haben, nach einem Umsortier-Vorgang?

    Also ich würd vorschlagen, lasses einfach so. Eine Spalten-Restaurierung müsstest du auch selbst noch hinkriegen, dafür muss man sich ja nur zusätzlich noch Dgv.CurrentCell.ColumnIndex merken und restaurieren.
    Ausserdem, dass du jetzt auch die Spalte restauriert haben möchtest, ist dann doch wieder ein Dgv-Concern.


    Mit anderen Worten, die BindingSource verwaltet zwar die ausgewählte Zeile, nicht aber die ausgewählte Zelle. Ok, das hab ich verstanden.

    In meinem Fall ist das kein Problem, weil ich immer einen "FullRowSelect" vereinbare. Das scheint mir die richtige Vorgehensweise zu sein, da es nur die BindingSource.Position als Pendant dazu gibt.

    Womöglich willst du auch eine eventuelle Multi-Selektion restauriert haben, nach einem Umsortier-Vorgang?


    Über den "MultiSelect" habe ich auch schon nachgedacht. Denn manchmal möchte man ja ganze Gruppen von Dateien mit einer Operation kopieren oder verschieben. Allerdings lasse ich dann einfach keinen Sort mehr zu ... weil der ja möglicherweise die ausgewählten Einträge wild verstreuen könnte. Der Anwender muss halt erst sortieren und dann auswählen - ich denke, das ist eine vernünftige Einschränkung mit der man leben kann.

    Bei der Gelegenheit recht herzlichen Dank an alle super geduldigen Ratgeber. Ihr hab mir sehr geholfen und ich verstehe jetzt deutlich mehr vom Databinding als vorher.

    LG
    Peter
    Okie dokie ... das sieht super aus. Vielen Dank für deine Mühe!

    Die Lösung hat den Charme, dass sie mit SortMode=AUTOMATIC arbeitet. Man braucht halt ein "Gedächtsnis" (LeftSortMode und CurrentRowID), sowie eine ID-Spalte um die Methode .Find verwenden zu können.

    Ich hab deine Idee verstanden. Allerdings ist die Sache noch nicht so ganz rund. Denn LeftSortMode wird auf True gesetzt und erhält dann nie wieder den Wert False. Nach dem ersten RechtsKlick werden deshalb fortan alle weiteren Zeilenauswahlen geschluckt. Da müsste man sich noch Gedanken machen, wie man das steuert.