ListView Drucken - Sortierung geht verloren

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

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von Mitch.

    ListView Drucken - Sortierung geht verloren

    Hallo liebe Community,

    ich verzweifele gerade an dem Ausrucken einer ListView. Dieser werden zur Laufzeit mehrere Daten hinzugefügt, sie enthält die drei Spalten "ID, Firstname, Lastname". Die Liste enthält immer mindestens eine Gruppe.

    Nach dem Hinzufügen eines neuen Items, wird die Liste automatisch nach der letzten Spalte (also "Lastname") sortiert. Dies funktionier auch fehlerfrei, innerhalb aller Gruppen.

    Sobald ich diese ListView nun allerdings drucke, geht die Reihenfolge verloren. Stattdessen erscheinen die Items nun in der Reihenfolge auf dem Ausdruck, in der sie hinzugefügt wurden. Ich benutze den MSDN ListView Printer Code.

    ListViewKlasse

    VB.NET-Quellcode

    1. Public Class listViewPrinter
    2. Private lv As ListView
    3. Private location As Point
    4. Private border As Boolean
    5. Private hasGroups As Boolean
    6. Private title As String
    7. Dim titleHeight As Integer
    8. Public globaldialog As PrintDialog
    9. Public WithEvents pd As New Printing.PrintDocument
    10. Public Sub New(ByVal lv As ListView, ByVal location As Point, ByVal border As Boolean, ByVal hasGroups As Boolean, Optional ByVal title As String = "")
    11. Me.lv = lv
    12. Me.location = location
    13. Me.border = border
    14. Me.hasGroups = hasGroups
    15. Me.title = title
    16. titleHeight = If(title <> "", lv.FindForm.CreateGraphics.MeasureString(title, New Font(lv.Font.Name, 25)).ToSize.Height, 0)
    17. End Sub
    18. Public Sub print()
    19. Dim ppd As New PrintDialog
    20. ppd.Document = pd
    21. ppd.AllowPrintToFile = False
    22. If ppd.ShowDialog = DialogResult.OK Then
    23. pd.PrinterSettings = ppd.PrinterSettings
    24. pd.Print()
    25. End If
    26. End Sub
    27. Private Structure pageDetails
    28. Dim columns As Integer
    29. Dim rows As Integer
    30. Dim startCol As Integer
    31. Dim startRow As Integer
    32. Dim headerIndices As List(Of Integer)
    33. End Structure
    34. Private pages As Dictionary(Of Integer, pageDetails)
    35. Dim maxPagesWide As Integer
    36. Dim maxPagesTall As Integer
    37. Dim items() As ListViewItem
    38. Private Sub pd_BeginPrint(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintEventArgs) Handles pd.BeginPrint
    39. ''this removes the printed page margins
    40. pd.OriginAtMargins = True
    41. pd.DefaultPageSettings.Margins = New Drawing.Printing.Margins(0, 0, 0, 0)
    42. pages = New Dictionary(Of Integer, pageDetails)
    43. Dim maxWidth As Integer = CInt(pd.DefaultPageSettings.PrintableArea.Width) - 40
    44. Dim maxHeight As Integer = CInt(pd.DefaultPageSettings.PrintableArea.Height - (titleHeight + 12)) - 40
    45. Dim pageCounter As Integer = 0
    46. pages.Add(pageCounter, New pageDetails With {.headerIndices = New List(Of Integer)})
    47. Dim columnCounter As Integer = 0
    48. Dim columnSum As Integer = 0
    49. For c As Integer = 0 To lv.Columns.Count - 1
    50. If columnSum + lv.Columns(c).Width < maxWidth Then
    51. columnSum += lv.Columns(c).Width
    52. columnCounter += 1
    53. Else
    54. pages(pageCounter) = New pageDetails With {.columns = columnCounter, .rows = 0, .startCol = pages(pageCounter).startCol, .headerIndices = pages(pageCounter).headerIndices}
    55. columnSum = lv.Columns(c).Width
    56. columnCounter = 1
    57. pageCounter += 1
    58. pages.Add(pageCounter, New pageDetails With {.startCol = c, .headerIndices = New List(Of Integer)})
    59. End If
    60. If c = lv.Columns.Count - 1 Then
    61. If pages(pageCounter).columns = 0 Then
    62. pages(pageCounter) = New pageDetails With {.columns = columnCounter, .rows = 0, .startCol = pages(pageCounter).startCol, .headerIndices = pages(pageCounter).headerIndices}
    63. End If
    64. End If
    65. Next
    66. maxPagesWide = pages.Keys.Max + 1
    67. pageCounter = 0
    68. Dim rowCounter As Integer = 0
    69. Dim counter As Integer = 0
    70. Dim itemHeight As Integer = lv.GetItemRect(0).Height
    71. Dim rowSum As Integer = itemHeight
    72. If hasGroups Then
    73. For g As Integer = 0 To lv.Groups.Count - 1
    74. rowSum += itemHeight + 6
    75. pages(pageCounter).headerIndices.Add(counter)
    76. For r As Integer = 0 To lv.Groups(g).Items.Count - 1
    77. counter += 1
    78. If rowSum + itemHeight < maxHeight Then
    79. rowSum += itemHeight
    80. rowCounter += 1
    81. Else
    82. pages(pageCounter) = New pageDetails With {.columns = pages(pageCounter).columns, .rows = rowCounter, .startCol = pages(pageCounter).startCol, .startRow = pages(pageCounter).startRow, .headerIndices = pages(pageCounter).headerIndices}
    83. For x As Integer = 1 To maxPagesWide - 1
    84. pages(pageCounter + x) = New pageDetails With {.columns = pages(pageCounter + x).columns, .rows = rowCounter, .startCol = pages(pageCounter + x).startCol, .startRow = pages(pageCounter).startRow, .headerIndices = pages(pageCounter).headerIndices}
    85. Next
    86. pageCounter += maxPagesWide
    87. For x As Integer = 0 To maxPagesWide - 1
    88. pages.Add(pageCounter + x, New pageDetails With {.columns = pages(x).columns, .rows = 0, .startCol = pages(x).startCol, .startRow = counter - 1, .headerIndices = New List(Of Integer)})
    89. Next
    90. rowSum = itemHeight * 2 + itemHeight + 6
    91. rowCounter = 1
    92. End If
    93. If counter = lv.Items.Count Then
    94. For x As Integer = 0 To maxPagesWide - 1
    95. If pages(pageCounter + x).rows = 0 Then
    96. pages(pageCounter + x) = New pageDetails With {.columns = pages(pageCounter + x).columns, .rows = rowCounter, .startCol = pages(pageCounter + x).startCol, .startRow = pages(pageCounter + x).startRow, .headerIndices = pages(pageCounter + x).headerIndices}
    97. End If
    98. Next
    99. End If
    100. Next
    101. Next
    102. Else
    103. For r As Integer = 0 To lv.Items.Count - 1
    104. counter += 1
    105. If rowSum + itemHeight < maxHeight Then
    106. rowSum += itemHeight
    107. rowCounter += 1
    108. Else
    109. pages(pageCounter) = New pageDetails With {.columns = pages(pageCounter).columns, .rows = rowCounter, .startCol = pages(pageCounter).startCol, .startRow = pages(pageCounter).startRow}
    110. For x As Integer = 1 To maxPagesWide - 1
    111. pages(pageCounter + x) = New pageDetails With {.columns = pages(pageCounter + x).columns, .rows = rowCounter, .startCol = pages(pageCounter + x).startCol, .startRow = pages(pageCounter).startRow}
    112. Next
    113. pageCounter += maxPagesWide
    114. For x As Integer = 0 To maxPagesWide - 1
    115. pages.Add(pageCounter + x, New pageDetails With {.columns = pages(x).columns, .rows = 0, .startCol = pages(x).startCol, .startRow = counter - 1})
    116. Next
    117. rowSum = itemHeight * 2
    118. rowCounter = 1
    119. End If
    120. If counter = lv.Items.Count Then
    121. For x As Integer = 0 To maxPagesWide - 1
    122. If pages(pageCounter + x).rows = 0 Then
    123. pages(pageCounter + x) = New pageDetails With {.columns = pages(pageCounter + x).columns, .rows = rowCounter, .startCol = pages(pageCounter + x).startCol, .startRow = pages(pageCounter + x).startRow}
    124. End If
    125. Next
    126. End If
    127. Next
    128. End If
    129. maxPagesTall = pages.Count \ maxPagesWide
    130. If hasGroups Then
    131. items = New ListViewItem() {}
    132. For Each g As ListViewGroup In lv.Groups
    133. items = items.Concat(g.Items.Cast(Of ListViewItem).ToArray).ToArray
    134. Next
    135. Else
    136. items = lv.Items.Cast(Of ListViewItem).ToArray
    137. End If
    138. End Sub
    139. Private Sub pd_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles pd.PrintPage
    140. Dim sf As New StringFormat
    141. sf.Alignment = StringAlignment.Center
    142. sf.LineAlignment = StringAlignment.Center
    143. Dim r2 As New Rectangle(location, New Size(lv.Columns.Cast(Of ColumnHeader).Skip(pages(0).startCol).Take(pages(0).columns).Sum(Function(ch As ColumnHeader) ch.Width), titleHeight))
    144. e.Graphics.DrawString(title, New Font(lv.Font.Name, 25), Brushes.Black, r2, sf)
    145. sf.Alignment = StringAlignment.Near
    146. Dim startX As Integer = location.X
    147. Dim startY As Integer = location.Y + titleHeight + 12
    148. Static startPage As Integer = 0
    149. Dim itemHeight As Integer = lv.GetItemRect(0).Height
    150. Dim bottomRight As Point
    151. For p As Integer = startPage To pages.Count - 1
    152. startX = location.X
    153. startY = location.Y + titleHeight + 12
    154. Dim cell As Rectangle
    155. For c As Integer = pages(p).startCol To pages(p).startCol + pages(p).columns - 1
    156. cell = New Rectangle(startX, startY, lv.Columns(c).Width, itemHeight)
    157. e.Graphics.FillRectangle(New SolidBrush(SystemColors.ControlLight), cell)
    158. e.Graphics.DrawRectangle(Pens.Black, cell)
    159. e.Graphics.DrawString(lv.Columns(c).Text, lv.Font, Brushes.Black, cell, sf)
    160. startX += lv.Columns(c).Width
    161. Next
    162. startY += itemHeight
    163. startX = location.X
    164. For r As Integer = pages(p).startRow To pages(p).startRow + pages(p).rows - 1
    165. startX = location.X
    166. If hasGroups Then
    167. If r = pages(p).startRow Or pages(p).headerIndices.Contains(r) Then
    168. cell = New Rectangle(startX + 20, startY + 6, lv.Columns.Cast(Of ColumnHeader).Skip(pages(p).startCol).Take(pages(p).columns).Sum(Function(ch As ColumnHeader) ch.Width), itemHeight)
    169. e.Graphics.DrawString(items(r).Group.Header, New Font(lv.Font, FontStyle.Bold), Brushes.SteelBlue, cell, sf)
    170. e.Graphics.DrawLine(New Pen(Color.SteelBlue, 2), startX + 20 + e.Graphics.MeasureString(items(r).Group.Header, New Font(lv.Font, FontStyle.Bold)).Width + 12, cell.Top + 3 + cell.Height \ 2, cell.Right - 20, cell.Top + 3 + cell.Height \ 2)
    171. startY += itemHeight + 6
    172. End If
    173. End If
    174. For c As Integer = pages(p).startCol To pages(p).startCol + pages(p).columns - 1
    175. cell = New Rectangle(startX, startY, lv.Columns(c).Width, itemHeight)
    176. e.Graphics.DrawString(items(r).SubItems(c).Text, lv.Font, Brushes.Black, cell, sf)
    177. If lv.GridLines Then e.Graphics.DrawRectangle(Pens.Black, cell)
    178. startX += lv.Columns(c).Width
    179. Next
    180. startY += itemHeight
    181. If r = pages(p).startRow + pages(p).rows - 1 Then
    182. bottomRight = New Point(startX, startY)
    183. If border Then e.Graphics.DrawRectangle(Pens.Black, New Rectangle(location, New Size(bottomRight.X - location.X, bottomRight.Y - location.Y)))
    184. End If
    185. Next
    186. If p <> pages.Count - 1 Then
    187. startPage = p + 1
    188. e.HasMorePages = True
    189. Return
    190. Else
    191. startPage = 0
    192. End If
    193. Next
    194. End Sub
    195. End Class



    Spätestens ab Zeile 167 setzt hier das Problem ein, denn ab diesem Zeitpunkt sind die Items plötzlich nicht mehr alphabetisch sortiert, sonder vielmehr nach der hinzugefügten Reihenfolge. Ich habe den Code wiegesagt nicht selbstgeschrieben, sondern kopiert. Verstehe aber im Groben und Ganzen was passiert, sehe aber trotzdem nicht das Problem.

    Aufruf zum Ausdrucken

    VB.NET-Quellcode

    1. Dim printer As New listViewPrinter(lv_reservations, New Point(50, 50), False, True, CurrentDate)
    2. printer.print()


    ListView sortieren

    VB.NET-Quellcode

    1. Private cmpFileListViewComparer As ListViewComparer
    2. Public Sub New()
    3. InitializeComponent()
    4. cmpFileListViewComparer = New ListViewComparer(lv_reservations)
    5. End Sub
    6. Private Sub lv_reservations_ColumnClick(sender As Object, e As ColumnClickEventArgs) Handles lv_reservations.ColumnClick
    7. If e.Column = cmpFileListViewComparer.SortColumn Then
    8. If cmpFileListViewComparer.SortOrder = SortOrder.Ascending Then
    9. cmpFileListViewComparer.SortOrder = SortOrder.Descending
    10. Else
    11. cmpFileListViewComparer.SortOrder = SortOrder.Ascending
    12. End If
    13. Else
    14. cmpFileListViewComparer.SortOrder = SortOrder.Ascending
    15. End If
    16. cmpFileListViewComparer.SortColumn = e.Column
    17. lv_reservations.Sort()
    18. End Sub


    Ich hoffe einer von euch hat hier bessere Augen und mehr Erfahrung als ich. Bin für jegliche Hilfe und Heinweise dankbar!

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

    Moin,
    Versteh mich bitte aber durch 170Zeilen Source zu scrollen ist nicht wirklich spassig.
    Evtl fast du nächstes Mal nur die wichtigen Zeilen zusammen.

    Die Listview wird wahrscheinlich vor dem drücken Nach dem vergebenen ID's geordnet.
    Hast du das Mal einzeln durchgesteppt, und geschaut was unmittelbar vor dem Druck mit deiner Listview passiert?
    Desweiteren noch ein Tipp: Wenn du ein DGV nimmst geht das drucken viel leichter von der Hand, und die Sortierung fliegt dir auch nicht um die Ohren.
    Entschuldige bitte, das kann ich natürlich verstehen. Dies ist mein erster Post und ich dachte die gesamte Klasse wäre hilfreich um zu verstehen, welche Variablen überhaupt welche Bedeutung haben.

    Meinst du mit den IDs die Spalte/Columnheader "ID"? Falls du das meinst nein. Auch die IDs sind kreuz und quer und folgen keinem Muster. Oder werden von dem ListView Control selber IDs vergeben? Die Listview wird beim Drucken tatsächlich nicht angefasst, sie bleibt vor dem, während und nachdem Druck sortiert. Ein DataGridView ist bei der Anwendung aus Usability Gründen leider nicht möglich, trotzdem danke für den Tipp.

    Eventuell kann ich meine Frage etwas umstrukturieren um das Problem aufzuspüren. Was genau passiert hier?

    VB.NET-Quellcode

    1. items = New ListViewItem() {}
    2. For Each g As ListViewGroup In lv.Groups
    3. items = items.Concat(g.Items.Cast(Of ListViewItem).ToArray).ToArray
    4. Next


    Klar, hier wird jedes Item einer ListView-Gruppe nach und nach durchlaufen und in die items Variable gesteckt. Wenn ich mir danach den Items-Arry z.B. in einer Messagebox ausgeben lasse fällt auf, dass die Sortierung ab dort bereits verloren ist. Gibt es evtl. eine andere Möglichkeit die Items korrekt sortiert in den Array zu speichern? Oder bin ich hier auf der falschen Spur?
    Sofern ich mich komplett irre werden in deinem Snippet einfach alle ListviewItems zur einem Array zusammengefügt (.concat)

    Vergiss das was ich mit dem ID's gesagt habe, ich war gerade noch dem Trichter Datasets/DGV

    Was ist den wenn du deine sortierten ListviewItems einfach Mal während der Laufzeit (per Code) in ein neues Listview kopierst, und schaust ob dort auch deine Sortierung flöten geht.

    Edit: hat der ListViewComparer evtl noch eine Überladung die du beim drucken mit übergeben kannst?
    Evtl kannst du ja mal das Snippet versuchen:
    code.msdn.microsoft.com/ListView-Printer-7a9be0a7

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Gelöschter Benutzer“ ()

    @Mitch Willkommen im Forum. :thumbup:
    Mach mit einem Button einen Testdatensatz, der Deinen Effekt reproduziert, teste dies,
    und hängs Dein Projekt ohne Binaries als Zip an.
    Erweiterte Bearbeitung => Dateianhänge => Hochladen.
    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!
    @Kameo Vielen Dank für deine Hilfe weiterhin! Das verlinkte Snippet ist allerdings genau das, welches ich (minimal verändert) benutze. Das mit der seperaten LV könnte evtl. funktionieren, ich probiere das mal... Trotzdem will ich jetzt wirklich rausfinden was momentan schief läuft :D

    @RodFromGermany Danke für das herzliche Willkommen und deine Hilfe! Ich habe mal ein neues Projekt erstellt, mit den gleichen Controls, Aufbau etc., allerdings ohne den ganzen andere Krims-Krams, welcher für den Fehler irrelevant ist. Ich habe es getestet, und der gleiche Fehler tritt auch hier auf. Anbei die Projektmappe!
    Dateien
    • lv-print.zip

      (242,88 kB, 84 mal heruntergeladen, zuletzt: )
    Wenn man von der Verwendung eines ListViews statt eines DataGridViews absieht (denn die Daten riechen nach Datenbanktabelle), stellt sich die Frage, warum Du mit ListViewGroups arbeitest. Brauchst Du die? Oder ist das nur durch den kopierten Code in Dein Projekt gewandert? Mach aus Zeile#164-#171 einfach

    VB.NET-Quellcode

    1. items = lv.Items.Cast(Of ListViewItem).ToArray
    Und fertig ist. Denn "ListViewGroups": Wie der Name schon sagt. Da werden Einträge gruppiert - die Du so wohl gar nicht haben willst.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Die Gruppen brauche ich wie eingangs erwähnt tatsächlich, leider. Durch deinen Code-Vorschlag bleibt die Reihenfolge zwar auch nach dem Drucken erhalten, aber die Gruppenzuordnung geht damit natürlich dann leider verloren:

    VB.NET-Quellcode

    1. For Each g As ListViewGroup In lv.Groups
    2. 'items = items.Concat(g.Items.Cast(Of ListViewItem).ToArray).ToArray
    3. items = lv.Items.Cast(Of ListViewItem).ToArray
    4. Next

    Listview zeigt:

    ----Gruppe 1----
    Mike Anders
    Max Müller
    Agnes Zeiger

    ----Gruppe 2----
    Max Affe
    Max Zebra
    Druckergebnis zeigt:

    ----Gruppe 1----
    Max Affe
    Mike Anders
    Max Müller

    ----Gruppe 2----
    Max Zebra
    Agnes Zeiger


    Wiegesagt, ist zwar korrekt sortiert, aber leider offensichtlich nicht mehr in der richtigen Gruppen-Konstellation, weil ich die Zuordnung damit ja umgehe. Es muss doch möglich sein die Items als Gruppe, korrekt sortiert, zu casten. Ich glaube ich bin einfach nur zu blind oder blöd ?(
    Ja gut, Sortierung hin oder her. Die betrifft ja nur die ListView-Anzeige. Innerhalb der Gruppen wird's eben nicht sortiert. Das musst Du nachholen. Wo am besten, musst Du selber rausfinden. Eine Möglichkeit wäre eben genau in der Zeile:

    VB.NET-Quellcode

    1. items = items.Concat(g.Items.Cast(Of ListViewItem).OrderBy(Function(x) x.SubItems(0).Text).ToArray).ToArray

    Der Vorschlag ist insofern nicht gut, weil er nach einem String sortiert. Und da ist eben 501 vor 60. Aber das in-der-Gruppe-sortieren musst Du rechtzeitig durchführen - eben vor dem Druck. Ggf. die Gruppe mit dem ListViewSorter (oder natürlich einer abgewandelten Form davon) bearbeiten.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Der Vorschlag ist sogar sehr gut, da ich ja nicht nach der ID sortieren möchte sondern dem Nachnamen. Damit ist das Sortieren nach einem String komplett richtig und funktioniert optimal! Lediglich auf x.SubItems(2).text geändert und alles passt.

    Vielen herzlichen Dank für die schnelle und kompetente Hilfe! :)