ListView nach Spalten sortieren

    • VB.NET

    Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      ListView nach Spalten sortieren

      Hi
      Hier mal eine Möglichkeit ein ListView nach Spalten zu sortieren:

      Als erstes benötigen wir einen Comparer, der später die ListViewItems vergleicht:

      VB.NET-Quellcode

      1. Public Class ListViewComparer
      2. Implements IComparer
      3. Private intColumn As Integer
      4. Private soSortOrder As SortOrder
      5. Private cicComparer As CaseInsensitiveComparer
      6. Public Sub New(ByVal ParentListView As ListView)
      7. intColumn = 0
      8. soSortOrder = SortOrder.None
      9. cicComparer = New CaseInsensitiveComparer()
      10. ParentListView.ListViewItemSorter = Me
      11. End Sub
      12. Public Property SortOrder() As SortOrder
      13. Get
      14. Return soSortOrder
      15. End Get
      16. Set(ByVal value As SortOrder)
      17. soSortOrder = value
      18. End Set
      19. End Property
      20. Public Property SortColumn() As Integer
      21. Get
      22. Return intColumn
      23. End Get
      24. Set(ByVal value As Integer)
      25. intColumn = value
      26. End Set
      27. End Property
      28. Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
      29. Dim compareResult As Integer
      30. Dim listviewX As ListViewItem, listviewY As ListViewItem
      31. listviewX = CType(x, ListViewItem)
      32. listviewY = CType(y, ListViewItem)
      33. compareResult = cicComparer.Compare(listviewX.SubItems(intColumn).Text, listviewY.SubItems(intColumn).Text)
      34. If soSortOrder = SortOrder.Ascending Then
      35. Return compareResult
      36. ElseIf soSortOrder = SortOrder.Descending Then
      37. Return compareResult * -1
      38. Else
      39. Return 0
      40. End If
      41. End Function
      42. End Class


      Bei einer Neu-Generierung wird der ListViewComparer instantiiert(Im SourceCode cmpFileListViewComparer):

      VB.NET-Quellcode

      1. Private cmpFileListViewComparer As ListViewComparer
      2. Public Sub New()
      3. ' This call is required by the Windows Form Designer.
      4. InitializeComponent()
      5. ' Add any initialization after the InitializeComponent() call.
      6. cmpFileListViewComparer = New ListViewComparer(ListViewFiles)
      7. End Sub

      Bei ListViewFiles übergibt man das ListView, das sortiert werden soll.

      Zum Schluss nimmt man das ListView und schreibt in das ColumnClick-Event den Code, der zum sortieren gebraucht wird:

      VB.NET-Quellcode

      1. If e.Column = cmpFileListViewComparer.SortColumn Then
      2. If cmpFileListViewComparer.SortOrder = SortOrder.Ascending Then
      3. cmpFileListViewComparer.SortOrder = SortOrder.Descending
      4. Else
      5. cmpFileListViewComparer.SortOrder = SortOrder.Ascending
      6. End If
      7. Else
      8. cmpFileListViewComparer.SortOrder = SortOrder.Ascending
      9. End If
      10. cmpFileListViewComparer.SortColumn = e.Column
      11. ListViewFiles.Sort()


      Gruß
      ~blaze~
      Ich setze jetzt meinen auch noch rein. Der gefällt mir sowieso am Besten.
      Für die Typen String, Integer, Long, Date.

      VB.NET-Quellcode

      1. Private colIndex As Int32 = -1
      2. Private sortord As SortOrder = SortOrder.Ascending


      VB.NET-Quellcode

      1. Private Sub LvMain_ColumnClick(ByVal sender As Object, ByVal e As ColumnClickEventArgs) Handles LvMain.ColumnClick
      2. Me.ColumnSort(e.Column)
      3. End Sub


      VB.NET-Quellcode

      1. Private Sub ColumnSort(ByVal columnindex As Int32)
      2. Dim idx As Int32 = columnindex
      3. If Not idx = colIndex Then
      4. colIndex = idx
      5. sortord = SortOrder.Descending
      6. End If
      7. 'Sortierung umkehren
      8. If Not sortord = SortOrder.Ascending Then
      9. sortord = SortOrder.Ascending
      10. Else : sortord = SortOrder.Descending
      11. End If
      12. Dim typ As Type = Me.GetColumnType(idx)
      13. Me.LvMain.ListViewItemSorter = New ListViewItemComparer(idx, typ, sortord)
      14. End Sub
      15. Private Function GetColumnType(ByVal idx As Int32) As Type
      16. 'Den Type per Designer (oder per Code) in das Tag-Property des ColumnHeader
      17. 'schreiben, dann kann man so drauf greifen.
      18. Return DirectCast(Me.LvMain.Columns(idx).Tag, Type)
      19. End Function


      ListViewItemComparer

      VB.NET-Quellcode

      1. Public Class ListViewItemComparer
      2. Implements IComparer
      3. Private sorttype As Type, index As Int32, sortord As SortOrder
      4. Public Function Compare(ByVal x As Object, ByVal y As Object) As Int32 Implements IComparer.Compare
      5. If GetType(String) = Me.sorttype Then
      6. If Me.sortord = SortOrder.Ascending Then
      7. Return [String].Compare(CType(x, ListViewItem).SubItems(Me.index).Text, CType(y, ListViewItem).SubItems(Me.index).Text)
      8. Else : Return [String].Compare(CType(y, ListViewItem).SubItems(Me.index).Text, CType(x, ListViewItem).SubItems(Me.index).Text)
      9. End If
      10. End If
      11. If GetType(Decimal) = Me.sorttype Then
      12. If Me.sortord = SortOrder.Ascending Then
      13. Return Decimal.Compare(CDec(CType(x, ListViewItem).SubItems(Me.index).Text), CDec(CType(y, ListViewItem).SubItems(Me.index).Text))
      14. Else : Return Decimal.Compare(CDec(CType(y, ListViewItem).SubItems(Me.index).Text), CDec(CType(x, ListViewItem).SubItems(Me.index).Text))
      15. End If
      16. End If
      17. If GetType(Date) = Me.sorttype Then
      18. If Me.sortord = SortOrder.Ascending Then
      19. Return Date.Compare(CDate(CType(x, ListViewItem).SubItems(Me.index).Text), CDate(CType(y, ListViewItem).SubItems(Me.index).Text))
      20. Else : Return Date.Compare(CDate(CType(y, ListViewItem).SubItems(Me.index).Text), CDate(CType(x, ListViewItem).SubItems(Me.index).Text))
      21. End If
      22. End If
      23. Return -1
      24. End Function
      25. Private Function IsNumericType(ByVal typ As Type) As Boolean
      26. If typ Is Nothing Then Return False
      27. Select Case Type.GetTypeCode(typ)
      28. Case TypeCode.Byte, TypeCode.Decimal, TypeCode.Double, TypeCode.Int16, TypeCode.Int32, TypeCode.Int64, _
      29. TypeCode.SByte, TypeCode.Single, TypeCode.UInt16, TypeCode.UInt32, TypeCode.UInt64
      30. Return True
      31. Case TypeCode.Object
      32. If typ.IsGenericType AndAlso typ.GetGenericTypeDefinition() = GetType(Nullable(Of )) Then
      33. Return IsNumericType(Nullable.GetUnderlyingType(typ))
      34. End If
      35. Return False
      36. End Select
      37. Return False
      38. End Function
      39. Public Sub New(ByVal idx As Int32, ByVal typ As Type, ByVal sortorde As SortOrder)
      40. Me.index = idx : Me.sorttype = typ : Me.sortord = sortorde
      41. If IsNumericType(typ) Then
      42. Me.sorttype = GetType(Decimal)
      43. End If
      44. End Sub
      45. End Class


      Frendliche Grüsse

      exc-jdbi

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „exc-jdbi“ ()

      Hey, das Thema ist schon ur-alt aber gerade interessant für mich.
      Ich hab das Handling mal mit in die Klasse gepackt und wollte das so vorbereiten, dass ich mehrere ListViews übergeben kann,
      leider sortiert er mir immer nur das zuletzt angegebene. Hat jemand Rat?

      Im Beispiel hier sortiert er mir "lv3", die anderen beiden nicht. Man sieht aber am "Flackern", dass eigentlich was passieren sollte - es tut' sich aber nix

      Aufruf auf Form_Load:
      Dim cmpListViewComparer = New ListViewComparer(lv1, lv2, lv3)
      sieht mit dem Dim auch ein bisschen komisch aus... aber ich kenn' keinen anderen Weg eine Klasse aufzurufen, die bereits alles beinhaltet ;)

      Die Klasse:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Imports System.Collections
      2. Public Class ListViewComparer
      3. Implements IComparer
      4. Private intColumn As Integer = 0
      5. Private soSortOrder As SortOrder = SortOrder.None
      6. Private cicComparer As CaseInsensitiveComparer
      7. Private _lv As ListView
      8. 'TODO klappt nicht für mehrere ListView's
      9. Public Sub New(ParamArray lvS As ListView())
      10. For Each lv In lvS
      11. cicComparer = New CaseInsensitiveComparer()
      12. lv.ListViewItemSorter = Me
      13. _lv = lv
      14. AddHandler lv.ColumnClick, AddressOf colSort
      15. Next
      16. End Sub
      17. Private Sub colSort(sender As Object, e As ColumnClickEventArgs)
      18. If e.Column = Me.SortColumn Then
      19. If Me.SortOrder = SortOrder.Ascending Then
      20. Me.SortOrder = SortOrder.Descending
      21. Else
      22. Me.SortOrder = SortOrder.Ascending
      23. End If
      24. Else
      25. Me.SortOrder = SortOrder.Ascending
      26. End If
      27. Me.SortColumn = e.Column
      28. _lv.Sort()
      29. End Sub
      30. Private Property SortOrder() As SortOrder
      31. Get
      32. Return soSortOrder
      33. End Get
      34. Set(ByVal value As SortOrder)
      35. soSortOrder = value
      36. End Set
      37. End Property
      38. Private Property SortColumn() As Integer
      39. Get
      40. Return intColumn
      41. End Get
      42. Set(ByVal value As Integer)
      43. intColumn = value
      44. End Set
      45. End Property
      46. Private Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
      47. Dim compareResult As Integer
      48. Dim listviewX As ListViewItem, listviewY As ListViewItem
      49. listviewX = CType(x, ListViewItem)
      50. listviewY = CType(y, ListViewItem)
      51. compareResult = cicComparer.Compare(listviewX.SubItems(intColumn).Text, listviewY.SubItems(intColumn).Text)
      52. If soSortOrder = SortOrder.Ascending Then
      53. Return compareResult
      54. ElseIf soSortOrder = SortOrder.Descending Then
      55. Return compareResult * -1
      56. Else
      57. Return 0
      58. End If
      59. End Function
      60. End Class


      Edit: Hab's. Auf der Form braucht's folgendes Handling: (Das von oben [/b]Dim cmpListViewComparer = New ListViewComparer(lv1, lv2, lv3)[b] aus Form_Load entfernen!)

      VB.NET-Quellcode

      1. Private Sub lv_GotFocus(sender As Object, e As EventArgs) Handles lv1.GotFocus, lv2.GotFocus, lv3.GotFocus
      2. Dim cmp As ListViewComparer
      3. Dim lv = DirectCast(sender, ListView)
      4. cmp = New ListViewComparer(lv)
      5. End Sub


      Dann die Klasse wie folgt:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Imports System.Collections
      2. Public Class ListViewComparer
      3. Implements IComparer
      4. Private intColumn As Integer = 0
      5. Private soSortOrder As SortOrder = SortOrder.None
      6. Private cicComparer As CaseInsensitiveComparer
      7. Private _lv As ListView
      8. Public Sub New(lv As ListView)
      9. cicComparer = New CaseInsensitiveComparer()
      10. lv.ListViewItemSorter = Me
      11. _lv = lv
      12. AddHandler lv.ColumnClick, AddressOf colSort
      13. End Sub
      14. Private Sub colSort(sender As Object, e As ColumnClickEventArgs)
      15. If e.Column = Me.SortColumn Then
      16. If Me.SortOrder = SortOrder.Ascending Then
      17. Me.SortOrder = SortOrder.Descending
      18. Else
      19. Me.SortOrder = SortOrder.Ascending
      20. End If
      21. Else
      22. Me.SortOrder = SortOrder.Ascending
      23. End If
      24. Me.SortColumn = e.Column
      25. _lv.Sort()
      26. End Sub
      27. Private Property SortOrder() As SortOrder
      28. Get
      29. Return soSortOrder
      30. End Get
      31. Set(ByVal value As SortOrder)
      32. soSortOrder = value
      33. End Set
      34. End Property
      35. Private Property SortColumn() As Integer
      36. Get
      37. Return intColumn
      38. End Get
      39. Set(ByVal value As Integer)
      40. intColumn = value
      41. End Set
      42. End Property
      43. Private Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
      44. Dim compareResult As Integer
      45. Dim listviewX As ListViewItem, listviewY As ListViewItem
      46. listviewX = CType(x, ListViewItem)
      47. listviewY = CType(y, ListViewItem)
      48. compareResult = cicComparer.Compare(listviewX.SubItems(intColumn).Text, listviewY.SubItems(intColumn).Text)
      49. If soSortOrder = SortOrder.Ascending Then
      50. Return compareResult
      51. ElseIf soSortOrder = SortOrder.Descending Then
      52. Return compareResult * -1
      53. Else
      54. Return 0
      55. End If
      56. End Function
      57. End Class


      vielleicht kann das ja jemand gebrauchen :thumbup:
      "Na, wie ist das Wetter bei dir?"
      "Caps Lock."
      "Hä?"
      "Shift ohne Ende!" :thumbsup:

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

      ErfinderDesRades schrieb:

      und jedes mal, wenn ein Listview den Focus bekommt, bekommt es einen weiteren Sorter?

      was gibt's für ne sinnvolle Alternative Aktion? Paint?

      ErfinderDesRades schrieb:

      Nein, was du geschrieben hast, ist ein "Behavior". Und das sollte man ein einziges ma dranmachen ans Control und gut - etwa im Form_Load.

      hatte ich vorher mit dem Grundgerüst vom TE probiert, da hat's dann aber auch nur mit dem zuletzt angegebenen Funktioniert :(
      "Na, wie ist das Wetter bei dir?"
      "Caps Lock."
      "Hä?"
      "Shift ohne Ende!" :thumbsup:

      tragl schrieb:

      was gibt's für ne sinnvolle Alternative Aktion?
      Nach dem Instanziieren des Controls.
      Der Sorter wird dem Control genau ein Mal zugewiesen, und wenn sortiert werden muss, wird genau diese eine einzige Instanz verwendet.
      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!

      RodFromGermany schrieb:

      Nach dem Instanziieren des Controls.

      jo und jetzt steh' ich mit meinem LaienWissen mal wieder vor der Wand:
      Wann werden genau die 13 ListView's instanziiert, die ich im Form-Designer erstellt hab' ? :/
      Etwa im Form_Load mit For Each jedes ListView durchgehen?
      "Na, wie ist das Wetter bei dir?"
      "Caps Lock."
      "Hä?"
      "Shift ohne Ende!" :thumbsup:

      tragl schrieb:

      Wann werden genau die 13 ListView's instanziiert
      Füge Deiner Klasse den Standard-Konstruktor hinzu (kannst Du hinterher wieder löschen, wird vom Framework aufgerufen):

      In der Prozedur InitializeComponents(), sie liegt in der Datei FormX.Designer.vb:

      VB.NET-Quellcode

      1. Public Sub New()
      2. ' Dieser Aufruf ist für den Designer erforderlich.
      3. InitializeComponent() ' <== Hier
      4. ' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
      5. End Sub

      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!

      RodFromGermany schrieb:

      Füge Deiner Klasse den Standard-Konstruktor hinzu (kannst Du hinterher wieder löschen, wird vom Framework aufgerufen):

      Ich muss mich dringend mal schlau machen, wo genau Im Fall der Form-Classe der Unterschied zwischen Form_Load und Sub New() liegt - bewirkt nach meinem Wissen nämlich
      das Gleiche.
      Edit: also mir ist schon bewusst, dass Sub New() der Konstruktor für eine neue Instanz der Form ist, aber Form_Load sollte in dem Fall das gleiche tun, so war das gemeint ;)

      Ich hab das inzwischen so gelöst:

      VB.NET-Quellcode

      1. Private Sub frm_Load(sender As Object, e As EventArgs) Handles Me.Load
      2. Dim cmp As ListViewComparer
      3. Dim getChilds As Func(Of Control, IEnumerable) = Function(ctl) ctl.Controls
      4. For Each ctl In getChilds.All(Me)
      5. Dim lv = TryCast(ctl, ListView)
      6. If lv.NotNull Then
      7. cmp = New ListViewComparer(lv)
      8. End If
      9. Next
      10. End Sub

      "Na, wie ist das Wetter bei dir?"
      "Caps Lock."
      "Hä?"
      "Shift ohne Ende!" :thumbsup:

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

      tragl schrieb:

      Unterschied zwischen Form_Load und Sub New()
      ist ein riesiger.
      New() instanziiert die Form, wie jede andere Klasse auch, nur dass in VB.NET der Konstruktor per Default nicht angezeigt, wohl aber aufgerufen wird.
      Setze einen Haltepunkt in die Prozedur InitializeComponents(), dann wird dort angehalten.
      Die Form_Load wird vom Framework aufgerufen als letzte Prozedur, wo die Form noch nicht dargestellt, also unsichtbar, ist.
      Diese Prozedur kann man getrost weglassen, zumal das Framework dort etwas rumspinnt und einige (nicht alle) Exceptions verschluckt.
      Was passiert eigentlich mit Deiner ListViewComparer-Instanz?
      Die wird erstens für jedes weitere ListView überschrieben und ist zweitens nach Beendigung von Form_Load weg.
      Und:
      Verstehe dieses Snippet:

      VB.NET-Quellcode

      1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      2. For Each pb In Panel1.Controls.OfType(Of PictureBox)()
      3. pb.BackColor = Color.Black
      4. Next
      5. End Sub
      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!

      RodFromGermany schrieb:

      Was passiert eigentlich mit Deiner ListViewComparer-Instanz?
      Die wird erstens für jedes weitere ListView überschrieben und ist zweitens nach Beendigung von Form_Load weg.


      Die Sortierung lässt sich trotzdem auf jedem ListView durchführen... :whistling:

      RodFromGermany schrieb:

      Verstehe dieses Snippet:


      wird wohl bei jeder PictureBox den Hintergrund schwarz färben, aber was willst du mir damit sagen? 8| ah, dass ich den Code noch verkürzen kann, in etwa so:

      VB.NET-Quellcode

      1. For Each lv In Me.Controls.OfType(Of ListView)
      2. cmp = New ListViewComparer(lv)
      3. Next


      Damit klappt aber keine Sortierung mehr
      "Na, wie ist das Wetter bei dir?"
      "Caps Lock."
      "Hä?"
      "Shift ohne Ende!" :thumbsup:
      Jou, die Verkürzung Deines Codes.

      tragl schrieb:

      Damit klappt aber keine Sortierung mehr
      Kann ich nicht nachvollziehen.
      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!
      Wenn wir uns mal den Designer anschauen:

      VB.NET-Quellcode

      1. Me.Controls.Add(Me.GroupBox1)
      2. Me.Controls.Add(Me.tabMonate)
      3. Me.Controls.Add(Me.msTabbedMonatsweise)
      4. Me.tabMonate.Controls.Add(Me.tpMitarbeiter)
      5. Me.tpMitarbeiter.Controls.Add(Me.lvMitarbeiter)


      Der Aufruf Me.Controls.OfType(Of ListView) erfragt aus der Auflistung Me.Controls alle Objekte vom Typ ListView. Die Listviews die du ansprechen möchtest, befinden sich allerdings nicht direkt auf der Form, sondern sind verschachtelt in anderen Controls. lvMitarbeiter befindet sich beispielsweise an Postion Me.Controls -> Me.tabMonate -> Me.tpMitarbeiter -> Me.lvMitarbeiter. Also musst du - wie in Post 11 richtig erkannt - die Controls rekursiv nach ihren Untercontrols befragen.

      kleine Verbesserung:

      VB.NET-Quellcode

      1. Dim getChilds As Func(Of Control, IEnumerable) = Function(ctl) ctl.Controls
      2. For Each lv In getChilds.All(Me).OfType(Of ListView)
      3. Dim cmp = New ListViewComparer(lv)
      4. Next



      Die Controls die du berücksichtigen möchtest, stehen ja sowieso schon fest. Warum dann den Aufwand mit dem rekursiven Durchsuchen?

      VB.NET-Quellcode

      1. Dim lvs = {Me.lvMitarbeiter,
      2. Me.lvJanuar}
      3. For Each lv In lvs
      4. Dim cmp = New ListViewComparer(lv)
      5. Next

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

      ChristianT. schrieb:

      Die Controls die du berücksichtigen möchtest, stehen ja sowieso schon fest. Warum dann den Aufwand mit dem rekursiven Durchsuchen?

      Weil das insgesamt 13 ListView's (1x Übersicht Mitarbeiter und dann pro Monat 1x) sind, dann wäre der Code länger als die aktuelle Variante ;)
      Aber ja, ist richtig. Die ListViews sitzen auf einem TabControl in unterschiedlichen TabPages - daran hab' ich dann aber auch nicht mehr gedacht.
      "Na, wie ist das Wetter bei dir?"
      "Caps Lock."
      "Hä?"
      "Shift ohne Ende!" :thumbsup: