Probleme beim Drucken von DGV-Inhalt

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

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

    Probleme beim Drucken von DGV-Inhalt

    Mahlzeit.
    So langsam muss ich auch die Drucken-Thematik in meinem Programm mal angehen. Ich dachte mir, ich bastel'
    was, womit man jedes beliebige DGV ausdrucken kann. (Ja, extra DGV-Inhalt und nicht DataTable - ich erweitere das später
    um eine Vorabauswahl für Spalten und Rows)

    Ich muss zugeben, das ganze Gedöns mit dem Drucken (alles muss "gemalt" werden, neue Seite erstellen etc.) ist für mich komplexer als die ganze
    Datenverarbeitungs-Sachen die ich inzwischen betreibe.. :S

    Die Druckvorschau sieht soweit schon ganz ordentlich aus, allerdings:

    - schießen manche "Zellinhalte" (FormattedValue.ToString) über die Rectangles hinaus, obwohl ich die per MeasureString ausgemessen habe
    - ich hab's hinbekommen, dass wenn die Header erstellt werden, er weitere Seiten erstellt und die restlichen Header dahin verteilt. Außerdem
    erstelle ich für jeden Header direkt die entsprechenden Zellinhalte mit.
    Was passiert jetzt aber, wenn die Zeilen mehr werden, als es die Seite zu lässt? Hier müsste wieder eine neue Seite erstellt werden und die Header von der 1.
    wieder mit angedruckt werden.

    - ich will mir nen eigenen PrintPreview-Dialog bauen, dazu hab ich erstmal nen einfachen Dialog, da das PrintPreviewControl drauf gezogen und
    2 Radiobuttons erstellt für die Einstellung Hochformat/Querformat. Dazu ein entsprechendes Handling geschrieben:

    VB.NET-Quellcode

    1. Private Sub rbtn_CheckedChanged(sender As Object, e As EventArgs) Handles rbtnHochformat.CheckedChanged,
    2. rbtnQuerformat.CheckedChanged
    3. If rbtnHochformat.Checked Then
    4. Me.PrintPreviewControl1.Document.DefaultPageSettings.Landscape = False
    5. Else
    6. Me.PrintPreviewControl1.Document.DefaultPageSettings.Landscape = True
    7. End If
    8. End Sub


    Da tut sich allerdings nix, wenn ich die Radiobuttons anklicke (also die Ansicht ändert sich im PrintPreview nicht).
    Ich hätte gedacht, dass sich dort die Ansicht ändert und auch die Seiten "neu erstellt" werden. Denn im Querformat hätte man ja z.B. mehr Platz für die Header.
    Könnt ihr mir sagen, wie ich das am besten anstelle?

    Anbei mein bisheriger Print-Code (erstmal nur Druckvorschau, Rest kommt später wenn das alles passt):

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Drawing, System.Drawing.Printing
    2. Public Module Print
    3. Private WithEvents _PrintDoc As PrintDocument
    4. Private _dgv As DataGridView
    5. Private _myfont As Font = New Font("Arial", 11, FontStyle.Regular)
    6. Private _activeColIndex As Integer
    7. Private _totalWidth As Integer
    8. Private _penGray As New Pen(Brushes.LightGray, 0.5)
    9. 'TODO: Abfangen, wenn Headertext im DGV mehrzeilig angezeigt wird (ist dann soll er auch mehrzeilig gedruckt werden
    10. Public Sub PrintDgv(DocumentName As String, dgv As DataGridView)
    11. _PrintDoc = New PrintDocument With {.DocumentName = DocumentName}
    12. _dgv = dgv
    13. _activeColIndex = 0
    14. _totalWidth = 0
    15. Using dlgPreview As New dlgPrintPreview
    16. 'dlgPreview.WindowState = FormWindowState.Maximized
    17. dlgPreview.PrintPreviewControl1.Document = _PrintDoc
    18. dlgPreview.ShowDialog()
    19. End Using
    20. End Sub
    21. Private Sub PrintDoc_PrintPage(sender As Object, e As PrintPageEventArgs) Handles _PrintDoc.PrintPage
    22. Dim g = e.Graphics
    23. Dim margin = _PrintDoc.DefaultPageSettings.Margins
    24. Dim X = margin.Left
    25. Dim Y = margin.Top
    26. Dim drawText As Action(Of String, Integer, Integer) = Sub(s, dx, dy) g.DrawString(s, _myfont, Brushes.Black, X + dx, Y + dy)
    27. drawText(_PrintDoc.DocumentName, 0, 0) 'Titel
    28. Y += 30
    29. Dim kvpOfColsAndMaxWidth = GetVisibleColAndMaxWidth(g)
    30. Dim maxPrintWidth = _PrintDoc.DefaultPageSettings.PaperSize.Width - _PrintDoc.DefaultPageSettings.Margins.Right 'die maximale Breite nach rechts
    31. For Each colAndMaxWidth In kvpOfColsAndMaxWidth
    32. If colAndMaxWidth.Key.Index < _activeColIndex Then Continue For
    33. If _totalWidth + colAndMaxWidth.Value > maxPrintWidth Then 'wenn die Breite überschritten würde, dann hier abbrechen und nach return mit der Col weitermachen
    34. If Not e.HasMorePages Then e.HasMorePages = True
    35. _activeColIndex = colAndMaxWidth.Key.Index
    36. _totalWidth = 0 'totalWidth muss für eine neue Seite natürlich zurückgesetzt werden
    37. Return
    38. End If
    39. DrawHeader(g, X, Y, _totalWidth, colAndMaxWidth)
    40. DrawFormattedValue(g, X, Y, _totalWidth, colAndMaxWidth)
    41. _totalWidth += colAndMaxWidth.Value
    42. Next
    43. If e.HasMorePages Then e.HasMorePages = False
    44. End Sub
    45. ''' <summary>Speichert alle sichtbaren DGV-Columns, sowie die maximale Schriftbreite der Spalte (oder HeaderCell-Breite, falls größer) in einem Dictionary</summary>
    46. Private Function GetVisibleColAndMaxWidth(g As Graphics) As Dictionary(Of DataGridViewColumn, Integer)
    47. Dim dic As New Dictionary(Of DataGridViewColumn, Integer)
    48. For i = 0 To _dgv.ColumnsX.Where(Function(col) col.Visible).Count - 1
    49. Dim colIndex = i
    50. 'Dim maxCellWidth = _dgv.RowsX.Select(Function(rw) rw.Cells(colIndex).Size.Width).Max
    51. Dim maxCellContentWidth = g.MeasureString(_dgv.RowsX.Select(Function(rw) rw.Cells(colIndex).FormattedValue.ToString).Max, _myfont) 'TODO: klappt noch nicht zu 100%
    52. Dim colWidth = _dgv.Columns(i).Width
    53. If colWidth > maxCellContentWidth.Width Then
    54. dic.Add(_dgv.Columns(colIndex), colWidth)
    55. Else
    56. dic.Add(_dgv.Columns(colIndex), CInt(maxCellContentWidth.Width))
    57. End If
    58. Next
    59. Return dic
    60. End Function
    61. Private Sub DrawHeader(g As Graphics, x As Integer, y As Integer, totalWidth As Integer, colAndMaxWidth As KeyValuePair(Of DataGridViewColumn, Integer))
    62. Dim drawHeaderText As Action(Of String, Integer) = Sub(s, dx) g.DrawString(s, _myfont, Brushes.Black, x + dx, y + 5)
    63. g.FillRectangle(Brushes.LightGray, x + totalWidth, y, colAndMaxWidth.Value, 30)
    64. drawHeaderText(colAndMaxWidth.Key.HeaderText, totalWidth)
    65. End Sub
    66. Private Sub DrawFormattedValue(g As Graphics, x As Integer, y As Integer, totalWidth As Integer, colAndMaxWidth As KeyValuePair(Of DataGridViewColumn, Integer))
    67. Dim drawRowText As Action(Of String, Integer, Integer) = Sub(s, dx, dy) g.DrawString(s, _myfont, Brushes.Black, x + dx, y + dy)
    68. Dim rowPos = 35
    69. For Each rw In _dgv.RowsX
    70. Dim formattedValue = rw.Cells(colAndMaxWidth.Key.Index).FormattedValue.ToString
    71. If formattedValue = "True" Then formattedValue = "Ja"
    72. If formattedValue = "False" Then formattedValue = "Nein"
    73. drawRowText(formattedValue, totalWidth, rowPos)
    74. g.DrawRectangle(_penGray, x + totalWidth, y + rowPos, colAndMaxWidth.Value, 30) 'x,x + rowPos - 5, totalWidth + colAndMaxWidth.Value, x + rowPos - 5) 'zu weit rechts, teilweise unterschiedlich lang
    75. rowPos += 30
    76. Next
    77. End Sub
    78. End Module


    PS: Ich hab mich ein bisschen an dem Code von diesem Thread bedient, allerdings muss es bei mir flexibel sein, da sich die Spalten ja auch je nach DGV ändern ;) : vb-paradise.de/index.php/Threa…ostID=1173341#post1173341

    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Jo, die Idee, ein DGV as is zu drucken kam mir auch, und ob man davon ein Tut machen sollte.
    aber grad keine Zeit.

    ich hab übrigens den Code aus dem anderen Thread noch weiterentwickelt:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub PrintDocument1_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
    2. Dim g = e.Graphics
    3. Dim margin = PrintDocument1.DefaultPageSettings.Margins
    4. Dim X = margin.Left
    5. Dim Y = margin.Top
    6. Dim drawText As Action(Of String, Integer) = Sub(s, dx) g.DrawString(s, myfont, Brushes.Black, X + dx, Y)
    7. If _pageNr = 0 Then
    8. Y += 240 : drawText("Auftragsnummer" & vbTab & vbTab & ":", 10)
    9. Y += 20 : drawText("Bauvorhaben" & vbTab & vbTab & ":", 10)
    10. End If
    11. Y += 80
    12. DrawColumnHeaders(g, Y)
    13. Y += 35
    14. e.HasMorePages = True
    15. Try
    16. For i = _printRow To bsFkAuftragsdetail.Count - 1
    17. Dim row = bsFkAuftragsdetail.At(Of AuftragsdetailRow)(i)
    18. Dim sUnprinted = If(row.IsLeistungNull, "", row.Leistung.Substring(_printedLeistungChars))
    19. If sUnprinted = "" Then sUnprinted = " "
    20. Dim layoutRect = e.MarginBounds
    21. layoutRect.Offset(X + 60, Y - margin.Top)
    22. layoutRect.Width = 380
    23. layoutRect.Height -= Y
    24. Dim charactersfitted, linesfilled As Integer
    25. Dim sz = g.MeasureString(sUnprinted, myfont, layoutRect.Size, _strformat, charactersfitted, linesfilled)
    26. If charactersfitted = 0 Then Return
    27. If _printedLeistungChars = 0 Then
    28. 'neu angefangene row: auch die anderen Werte eintragen
    29. If Not row.IsPosNull Then drawText(CStr(row.Pos), 17)
    30. If Not row.IsMengeNull Then drawText(Format(row.Menge, "N2"), 465)
    31. If Not row.IsEinheitNull Then drawText(Format(row.Einheit), 535)
    32. If Not row.IsEinzelpreisNull Then drawText(Format(row.Einzelpreis, "C2"), 603)
    33. If Not row.IsGesamtpreisNull Then drawText(Format(row.Gesamtpreis, "C2"), 668)
    34. End If
    35. Dim sPrint = sUnprinted.Substring(0, charactersfitted)
    36. g.DrawString(sPrint, myfont, Brushes.Black, layoutRect, _strformat)
    37. _printedLeistungChars += charactersfitted
    38. Dim hoehe = CInt(sz.Height) + 6
    39. DrawGridlines(g, Y, hoehe)
    40. If charactersfitted < sUnprinted.Length Then Return
    41. _printRow += 1
    42. _printedLeistungChars = 0
    43. Y += hoehe
    44. Next
    45. e.HasMorePages = False
    46. Finally
    47. _pageNr += 1
    48. g.DrawString(String.Format(" - Seite {0} -", _pageNr), myfont, Brushes.LightGray, 380, 1130)
    49. End Try
    50. End Sub
    51. Private Sub DrawColumnHeaders(g As Graphics, y As Integer)
    52. Dim X = PrintDocument1.DefaultPageSettings.Margins.Left
    53. g.FillRectangle(Brushes.LightGray, X + 10, y, 40, 30)
    54. g.FillRectangle(Brushes.LightGray, X + 52, y, 400, 30)
    55. g.FillRectangle(Brushes.LightGray, X + 454, y, 65, 30)
    56. g.FillRectangle(Brushes.LightGray, X + 521, y, 55, 30)
    57. g.FillRectangle(Brushes.LightGray, X + 578, y, 80, 30)
    58. g.FillRectangle(Brushes.LightGray, X + 660, y, 80, 30)
    59. Dim drawText As Action(Of String, Integer) = Sub(s, dx) g.DrawString(s, myfont, Brushes.Black, X + dx, y + 5)
    60. drawText("Pos.", 15)
    61. drawText("Leistung", 240)
    62. drawText("Menge", 462)
    63. drawText("Einheit", 524)
    64. drawText("EP [€]", 600)
    65. drawText("GP [€]", 685)
    66. End Sub
    67. Private Sub DrawGridlines(g As Graphics, y As Integer, hoehe As Integer)
    68. Dim X = PrintDocument1.DefaultPageSettings.Margins.Left
    69. Dim dy = hoehe
    70. y -= 5
    71. g.DrawLine(_pnGray, X + 51, y, X + 51, y + dy)
    72. g.DrawLine(_pnGray, X + 453, y, X + 453, y + dy)
    73. g.DrawLine(_pnGray, X + 520, y, X + 520, y + dy)
    74. g.DrawLine(_pnGray, X + 582, y, X + 582, y + dy)
    75. g.DrawLine(_pnGray, X + 664, y, X + 664, y + dy)
    76. g.DrawLine(_pnGray, X + 10, y + dy, X + 739, y + dy)
    77. End Sub
    78. End Class

    Es kommt nun nicht mehr vor, dass er aus der Print-Schleife rausspringt, um in der nächsten Zeile weiterzumachen.
    Sondern wenn er rausspringt, machter immer auf der nächsten Seite mit der rausgesprungenen Zeile weiter.
    Es wird nämlich nun der Rest-Platz auf der Seite gemessen - und wenn der zu niedrig ist für nichtmal eine Zeile - dann auf nächster Seite weiter (erster Return).
    Also er geht mit der aktuellen Row auf die nächste Seite, wenn er entweder garnichts drucken konnte, oder nicht alles unterbringen konnte.

    Für deinen allgemeinen Ansatz brauchst du was komplizierteres als ein einfaches KeyValuePair je Spalte.
    Da brauchste ein Objekt, was die Spaltenbreite und auch die bisher gedruckten Zeichen sich merkt.
    In meim Code fährt das ja im Form herum (_printedLeistungChars) - das geeeeht, weils ist die einzige problematische Spalte, aber nicht schön.
    Beim generisschen Ansatz müssen alle Zellen so (also mit Vermessung und LayoutRect) gedruckt werden, und die höchste Zelle gibt dann die Gesamt-Zeilen-Höhe vor.
    Jo, das mit dem Seitenvorschub wird bisserl komplizierter, weil man muss erst alle Zell-Texte vermessen, und dann gucken, welche Höhe man noch drucken kann. und danach erst tatsächlich drucken.
    (Bzw. wenn die erste Zelle die Begrenzung erreicht, dann kann man abbrechen, weil damit ist die LayoutRect-Höhe aller anderen Zellen auch geklärt.)

    Ich würd auch nicht versuchen per Code die Spaltenbreiten festzulegen, sondern das soll der User am DGV machen, und das wird dann proportional auf die PrintMargins übertragen.



    warum ein eigener PrintPreview-Dialog?
    Ich find den vorhandenen sehr gut - der zeigt, wie die Seiten aussehen werden.
    Für Einstellungen der DruckParameter nimmt man den PrintDialog (dassis ein anderer).
    Dann muss man nur dafür sorgen, dass der PrintPreviewDialog dasselbe PrintDocument bekommt, an dem der PrintDialog seine Einstellungen vorgenommen hat.
    Am einfachsten bringt man das auf dem (Main-)Form unter - Dialoge in Modulen zu instanzieren ist ungeschickt, weil da geht alles verloren, wenn man sie schliesst.

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

    tragl schrieb:

    Ich hab mich ein bisschen an dem Code von diesem Thread bedient
    Fang hier an:
    Drucken mehrseitiger Dokumente
    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!

    tragl schrieb:

    Da tut sich allerdings nix
    Du musst danach DeinPrintPreviewControl.InvalidatePreview aufrufen.
    Ansonsten tl;dr, daher: Man kann doch von einem Control ein Bild machen und dieses als Bild (ggf. gestückelt) an das Document übergeben - AFAIR.
    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.

    ErfinderDesRades schrieb:

    Da brauchste ein Objekt, was die Spaltenbreite und auch die bisher gedruckten Zeichen sich merkt.

    ne, für die Spaltenüberschriften nicht. Er soll ja nicht die Spaltenbeschriftung mittendrin abschneiden und auf der neuen Seite fortsetzen, sondern den kompletten Text als neue Spalte auf die neue Seite - das funzt ja auch.

    ErfinderDesRades schrieb:

    Ich würd auch nicht versuchen per Code die Spaltenbreiten festzulegen, sondern das soll der User am DGV machen, und das wird dann proportional auf die PrintMargins übertragen.

    jain. wenn der User die soweit zusammengeschoben hat, dass Text abgeschnitten wird, dann würde der auch beim Drucken abgeschnitten. Deshalb wollte ich mir mit Dim maxCellContentWidth = g.MeasureString(_dgv.RowsX.Select(Function(rw) rw.Cells(colIndex).FormattedValue.ToString).Max, _myfont) die quasi breiteste Zelle holen.
    Muss ich mal debuggen, warum das nicht überall klappt.

    ErfinderDesRades schrieb:

    warum ein eigener PrintPreview-Dialog?

    Damit ich eigene Buttons etc. hinzufügen kann.

    RodFromGermany schrieb:

    Fang hier an:
    #
    Den Thread hab ich schon im Blick. Hab gestern erstmal mit dem "Groben" angefangen und bin halt auf die genannten Probleme gestoßen. ICh hab noch n altes Projekt von dir gefunden "Tabelle drucken",
    das hab ich gestern mal runtergeladen und acker ich die Tage mal durch

    VaporiZed schrieb:

    Du musst danach DeinPrintPreviewControl.InvalidatePreview aufrufen.

    Danke dafür. Hab ich eingebaut. Es wird was aktualisiert, allerdings erscheint danach nur noch der Überschriftstext, ansonsten ist die Seite blank (die ganzen Tabellen-Sachen fehlen)


    ErfinderDesRades schrieb:

    Beim generisschen Ansatz müssen alle Zellen so (also mit Vermessung und LayoutRect) gedruckt werden, und die höchste Zelle gibt dann die Gesamt-Zeilen-Höhe vor.

    Ich hab grad noch die Idee, dass ich ja die gesamtanzahl an DGV-Rows mit entspr. Höhe addieren kann - außerdem kenne ich ja die Gesamthöhe der PrintPage, damit sollte sich doch was machen lassen.
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Sooo, nach langem Tüfteln hab ich eine bisher gut funktionierende Lösung gebastelt.
    Der Code ist sicher an einigen Stellen verbesserungsbedürftig:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Drawing, System.Drawing.Printing
    2. Public Module Print
    3. Private WithEvents _PrintDoc As PrintDocument
    4. Private _dgv As DataGridView
    5. Private _myfont As Font = New Font("Arial", 11, FontStyle.Regular)
    6. Private _penGray As New Pen(Brushes.LightGray, 0.5)
    7. Private _headerHeight As Integer = 30
    8. Private _rowHeight As Integer = 20
    9. Private _UsedWidth As Integer
    10. Private _UsedHeight As Integer
    11. Private _startColIndex As Integer = 0
    12. Private _activeColIndex As Integer = 0
    13. Private _interruptColIndex As Integer = 0
    14. Private _printedMaxColIndex As Integer = 0
    15. Private _activeRowIndex As Integer = 0
    16. Private _hasRowInterruption As Boolean = False
    17. Private _hasColInterruption As Boolean = False
    18. Private _pageCounter As Integer = 1
    19. 'TODO: Abfangen, wenn Headertext mehrzeilig ist dann soll er auch mehrzeilig gedruckt werden
    20. Public Sub PrintDgv(DocumentName As String, dgv As DataGridView)
    21. _PrintDoc = New PrintDocument With {.DocumentName = DocumentName}
    22. _dgv = dgv
    23. Using dlgPreview As New dlgPrintPreview
    24. dlgPreview.WindowState = FormWindowState.Maximized
    25. dlgPreview.PrintPreviewControl1.Document = _PrintDoc
    26. dlgPreview.nudHorizontal.Value = 1
    27. dlgPreview.nudVertical.Value = 1
    28. dlgPreview.ShowDialog()
    29. End Using
    30. End Sub
    31. Private Sub PrintDoc_PrintPage(sender As Object, e As PrintPageEventArgs) Handles _PrintDoc.PrintPage
    32. Dim g = e.Graphics
    33. Dim margin = _PrintDoc.DefaultPageSettings.Margins
    34. margin.Left = 30
    35. margin.Right = 30
    36. margin.Top = 30
    37. margin.Bottom = 30
    38. Dim X = margin.Left
    39. Dim Y = margin.Top
    40. g.DrawString($"{_PrintDoc.DocumentName} | Seite: {_pageCounter}", _myfont, Brushes.Black, X, Y) 'Überschrift
    41. Y += 30 'etwas Platz schaffen zu den Spaltenköpfen
    42. Dim kvpOfColsAndMaxWidth = GetVisibleColAndMaxWidth() 'Spalten und deren maximale Breiten holen 'TODO: klappt nicht einwandfrei, Text steht teilweise trotzdem über
    43. Dim maxPrintWidth = If(_PrintDoc.DefaultPageSettings.Landscape, _PrintDoc.DefaultPageSettings.PaperSize.Height, _PrintDoc.DefaultPageSettings.PaperSize.Width) - margin.Right - margin.Left 'die maximale Breite je nach Orientierung
    44. If e.HasMorePages Then e.HasMorePages = False 'wenn weiter unten weitere Seiten gefordert wurden, dann hier zurücksetzen - sonst Endlosschleife
    45. 'Columns
    46. If _hasRowInterruption Then
    47. _startColIndex = _printedMaxColIndex + 1
    48. _activeColIndex = _printedMaxColIndex + 1
    49. Else
    50. _startColIndex = If(_hasColInterruption, _interruptColIndex, 0)
    51. _activeColIndex = If(_hasColInterruption, _interruptColIndex, 0)
    52. End If
    53. For iCol = _activeColIndex To _dgv.Columns.Count - 1
    54. If Not _hasRowInterruption Then _hasColInterruption = False 'solange wie es Row-Unterbrechungen gibt muss/soll er die Spaltenköpfe auf jeder Seite neu malen 'TODO: Einstellbar machen?
    55. _activeColIndex = iCol
    56. Dim colIsVisible = kvpOfColsAndMaxWidth.Any(Function(kvp) kvp.Key.Index = _activeColIndex)
    57. If Not colIsVisible Then Continue For 'wenn die Spalte im DGV nicht sichtbar ist, dann überspringen
    58. Dim colAndMaxWidth = kvpOfColsAndMaxWidth.First(Function(kvp) kvp.Key.Index = _activeColIndex) 'passendes KVP raussuchen
    59. If _UsedWidth + colAndMaxWidth.Value > maxPrintWidth Then 'überschreiten die Spaltenköpfe die Seitenbreite, dann Abbruch und erstmal Rows eintragen
    60. _hasColInterruption = True
    61. _interruptColIndex = _activeColIndex 'Abbruch-Spaltenindex merken
    62. Exit For
    63. End If
    64. DrawHeader(g, X, Y, _UsedWidth, colAndMaxWidth) 'Spaltenkopf zeichnen
    65. _UsedWidth += colAndMaxWidth.Value
    66. Next
    67. _UsedHeight = Y + _headerHeight + 5 'etwas Platz zwischen Spaltenköpfe und Rows schaffen
    68. _UsedWidth = 0 'Benutzte Breite zurücksetzen, damit die Rows nun wieder von ganz links an gemalt werden
    69. 'Rows
    70. Dim toColIndex = If(_hasColInterruption, _activeColIndex - 1, _activeColIndex) 'Wenn ein Abbruch wegen Überbreite (Spalten) erfolgt, dann activeColIndex-1, sonst wird bei den Rows 1 Spalteninhalt zuviel gemalt.
    71. DrawRows(g, X, _startColIndex, toColIndex, kvpOfColsAndMaxWidth) 'die zugehörigen Row-Inhalte der bisher gemalten Spaltenköpfe
    72. If _hasRowInterruption Then
    73. e.HasMorePages = True
    74. _pageCounter += 1
    75. Return
    76. Else
    77. _printedMaxColIndex = toColIndex 'Wenn kein Row-Abbruch mehr kommt, dann hier den Max-Index der bisher gemalten Spaltenköpfe merken
    78. End If
    79. If _hasColInterruption Then
    80. e.HasMorePages = True
    81. _pageCounter += 1
    82. Return
    83. End If
    84. If e.HasMorePages Then e.HasMorePages = False
    85. End Sub
    86. Private Sub DrawRows(g As Graphics, X As Integer, startCol As Integer, endCol As Integer, kvpOfColsAndMaxWidth As Dictionary(Of DataGridViewColumn, Integer))
    87. Dim margin = _PrintDoc.DefaultPageSettings.Margins
    88. Dim maxPrintHeight = If(_PrintDoc.DefaultPageSettings.Landscape, _PrintDoc.DefaultPageSettings.PaperSize.Width, _PrintDoc.DefaultPageSettings.PaperSize.Height) - margin.Bottom 'die maximale Höhe
    89. For iRow = 0 To _dgv.Rows.Count - 1
    90. If _UsedHeight + _rowHeight > maxPrintHeight Then
    91. _hasRowInterruption = True
    92. Exit For
    93. End If
    94. If Not _hasRowInterruption Then _activeRowIndex = iRow
    95. If iRow <> _activeRowIndex Then Continue For
    96. _hasRowInterruption = False
    97. Dim rw = _dgv.Rows(_activeRowIndex)
    98. For iCol = startCol To endCol
    99. Dim colIndex = iCol
    100. Dim colIsVisible = kvpOfColsAndMaxWidth.Any(Function(kvp) kvp.Key.Index = colIndex)
    101. If Not colIsVisible Then Continue For
    102. Dim colAndMaxWidth = kvpOfColsAndMaxWidth.First(Function(kvp) kvp.Key.Index = colIndex)
    103. Dim formattedValue = rw.Cells(iCol).FormattedValue.ToString
    104. If formattedValue = "True" Then formattedValue = "Ja"
    105. If formattedValue = "False" Then formattedValue = "Nein"
    106. g.DrawString(formattedValue, _myfont, Brushes.Black, X + _UsedWidth, _UsedHeight + 1)
    107. g.DrawRectangle(_penGray, X + _UsedWidth, _UsedHeight, colAndMaxWidth.Value, _rowHeight)
    108. _UsedWidth += colAndMaxWidth.Value
    109. Next
    110. _UsedWidth = 0
    111. _UsedHeight += _rowHeight
    112. Next
    113. End Sub
    114. Private Sub DrawHeader(g As Graphics, x As Integer, y As Integer, totalWidth As Integer, colAndMaxWidth As KeyValuePair(Of DataGridViewColumn, Integer))
    115. Dim drawHeaderText As Action(Of String, Integer) = Sub(s, dx) g.DrawString(s, _myfont, Brushes.Black, x + dx, y + 5)
    116. g.FillRectangle(Brushes.LightGray, x + totalWidth, y, colAndMaxWidth.Value, _headerHeight)
    117. drawHeaderText(colAndMaxWidth.Key.HeaderText, totalWidth)
    118. End Sub
    119. ''' <summary>Speichert alle sichtbaren DGV-Columns, sowie die maximale Schriftbreite der Spalte (oder HeaderCell-Breite, falls größer) in einem Dictionary</summary>
    120. Private Function GetVisibleColAndMaxWidth() As Dictionary(Of DataGridViewColumn, Integer)
    121. Dim dic As New Dictionary(Of DataGridViewColumn, Integer)
    122. Dim g = _dgv.CreateGraphics
    123. For i = 0 To _dgv.ColumnsX.Where(Function(col) col.Visible).Count - 1
    124. Dim colIndex = i
    125. Dim maxCellContentWidth = g.MeasureString(_dgv.RowsX.Select(Function(rw) rw.Cells(colIndex).FormattedValue.ToString).Max, _myfont) 'TODO: klappt noch nicht zu 100%
    126. Dim colWidth = _dgv.Columns(i).Width
    127. If colWidth > maxCellContentWidth.Width Then
    128. dic.Add(_dgv.Columns(colIndex), colWidth)
    129. Else
    130. dic.Add(_dgv.Columns(colIndex), CInt(maxCellContentWidth.Width))
    131. End If
    132. Next
    133. Return dic
    134. End Function
    135. End Module


    Ich hab' noch das Problem, dass er mir die maximalen Spaltenbreiten (bzw. maximale Textlänge) nicht korrekt ausgibt - teilweise steht Text über die Linien hinaus.
    Ansonsten @ErfinderDesRades wäre das denke ich schonmal ein guter Ansatz für's allgemeine Drucken eines DataGridViews.

    Wenn mir jemand einen Tipp gibt, wie ich das mit der Textlänge hinbekomme, dann kann ich mir das noch erweitern, dass nur bestimmte (ausgewählte) Spalten und Rows gedruckt werden.
    Ich bastel' dann die Tage mal an meinem eigenen PrintPreviewDialog weiter und verschöner den noch etwas. :thumbup:

    Edit: Mit dem längsten Inhalt hab ich rausgefunden. Ich dachte ich könnte den längsten String mit .Max rausfinden, ging aber nicht. Hab mir ne kleine Function gebastelt, die mir den längsten String zurück gibt,
    den vermesse ich dann mit g.MeasureString:

    VB.NET-Quellcode

    1. ''' <summary>Speichert alle sichtbaren DGV-Columns, sowie die maximale Schriftbreite der Spalte (oder HeaderCell-Breite, falls größer) in einem Dictionary</summary>
    2. Private Function GetVisibleColAndMaxWidth() As Dictionary(Of DataGridViewColumn, Integer)
    3. Dim dic As New Dictionary(Of DataGridViewColumn, Integer)
    4. Dim g = _dgv.CreateGraphics
    5. For i = 0 To _dgv.ColumnsX.Where(Function(col) col.Visible).Count - 1
    6. Dim colIndex = i
    7. Dim ht = _dgv.Columns(colIndex).HeaderText
    8. Dim CellContent = _dgv.RowsX.Select(Function(rw) rw.Cells(colIndex).FormattedValue.ToString).ToArray
    9. Dim maxCellContentWidth = g.MeasureString(GetLongestString(CellContent), _myfont).ToSize.Width 'TODO: klappt noch nicht zu 100%
    10. Dim colWidth = g.MeasureString(_dgv.Columns(i).HeaderText, _myfont).ToSize.Width
    11. If colWidth > maxCellContentWidth Then
    12. dic.Add(_dgv.Columns(colIndex), colWidth + 5)
    13. Else
    14. dic.Add(_dgv.Columns(colIndex), maxCellContentWidth + 5)
    15. End If
    16. Next
    17. Return dic
    18. End Function
    19. Private Function GetLongestString(SomeStrings As String()) As String
    20. Dim longest = ""
    21. For Each str In SomeStrings
    22. If str.Length > longest.Length Then longest = str
    23. Next
    24. Return longest
    25. End Function


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

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

    @tragl GetLongestString() funktioniert nicht, weil "iii" hat mehr Buchstaben als "mm", ist aber kürzer.
    Du musst den breitesten gedruckten Text ermitteln, sieh vielleicht mal hier rein: Drucken mehrseitiger Dokumente
    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!

    tragl schrieb:

    - ich hab's hinbekommen, dass wenn die Header erstellt werden, er weitere Seiten erstellt und die restlichen Header dahin verteilt. Außerdem erstelle ich für jeden Header direkt die entsprechenden Zellinhalte mit.
    Was passiert jetzt aber, wenn die Zeilen mehr werden, als es die Seite zu lässt? Hier müsste wieder eine neue Seite erstellt werden und die Header von der 1. wieder mit angedruckt werden.
    ich hab noch nicht das Druck-Konzept verstanden:
    Mir scheint, du willst eine zweite Seite ausdrucken, wenn die Tabelle breiter ist als die Druck-Seite? (Zeilenhöhe konstant, Spaltenbreite variabel)?
    In dem anderen Thread war ja Konzept, dass alle Spalten auf die Seiten-Breite passen, und wenn Zell-Texte zu lang werden, werden sie umgebrochen (also Spaltenbreite konstant, Zeilenhöhe variabel).

    ErfinderDesRades schrieb:

    ich hab noch nicht das Druck-Konzept verstanden:


    ist beides variabel. Wenn die Header breiter sind, als die Seite dann druckt er erst alles auf die 1. Seite. Passen die Rows zu den Headern der 1. Seite nicht drauf, dann macht er ne neue Seite mit den gleichen Headern und macht
    die Rows dort weiter. Erst wenn die Rows dann abgeschlossen sind, macht er mit den Headern auf einer weiteren Seite weiter. Ich guck' mal dass ich nen kleines Testprojekt baue..

    @RodFromGermany: Also muss ich statt der Buchstabenanzahl jeden String mit g.MeasureString vermessen und da den längsten zurück geben?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Also in deim Konzept geben die SpaltenHeader die Breiten vor, und der Druck muss sich dem in beide Dimensionen anpassen?
    Also wenn die Header zu breit sind einen "Seitenumbruch" nach rechts vollführen?
    Und wenn der Zellentext zu lang ist einen Zeilumbruch in der Zeile
    Und natürlich wenn die Seite voll ist auf de(r/n) nächsten Seite(n) weitermachen (klassischer Seitenumbruch)?

    Jo, versuchma dein Konzept detailliert auszuformulieren (oder habichs nu bereits richtig begriffen?), im Zusammenhang mit deim Testprojekt.
    @ErfinderDesRades:

    Hier mal ein Mini-Testprojekt - ich denke da sollte alles aufgeklärt werden :)

    ErfinderDesRades schrieb:

    Und wenn der Zellentext zu lang ist einen Zeilumbruch in der Zeile
    Und natürlich wenn die Seite voll ist auf de(r/n) nächsten Seite(n) weitermachen (klassischer Seitenumbruch)?


    Ja, so in etwa kommt das hin. Mit zu langem Zellentext hab ich noch nix zu tun, das wäre eine Extra-Wurst die man einbauen müsste.
    Bei mir geht das aktuell nur so:

    Wenn die Header zu Breit sind für eine Seite -> nächste Seite (nach rechts) und da weitermalen
    Wenn die Rows zu hoch sind für die Seite -> nächste Seite (nach unten), die gleichen Header wie auf der 1. Seite und da mit den restlichen Rows weitermachen
    Dateien
    • dgvprint01.zip

      (20,94 kB, 39 mal heruntergeladen, zuletzt: )
    "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:

    Also muss ich statt der Buchstabenanzahl jeden String mit g.MeasureString vermessen und da den längsten zurück geben?
    Ja.
    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!