Drucken mehrseitiger Dokumente

    • VB.NET

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

      Drucken mehrseitiger Dokumente

      Heute wollen wir uns mit dem Drucken mehrseitiger Dokumente befassen, das Drucken einseitiger Dokumente ist in diesem Zusammenhang trivial, es wird nicht explizit behandelt.
      Als Empfehlung: Wählt einen PDF-Drucker als Standarddrucker aus, da wird bei der Programmenwicklung kein Papier verschwendet.
      Zuerst werden wir 3 Kreise verschiedener Durchmesser zu Papier bringen, jeden Kreis auf ein neues Blatt.
      Was wir brauchen, ist eine Form mit einem Button ein PrintDocument wowie einen PrintPreviewDialog, auf letzteren kann man auch verzichten und in einer Using-Schleife einen erstellen:

      VB.NET-Quellcode

      1. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
      2. Using dlg As New PrintPreviewDialog
      3. dlg.ShowDialog()
      4. End Using
      5. End Sub
      Starten wir diesen Code, werden wir erinnert, dass das Dokument keine Seiten enthält:

      Wir müssen dem Dialog mitteilen, welches Dokument zum Drucken verwendet werden soll, außerdem geben wir ihm einen Arbeitstitel, der beim PDF-Druck als Dateiname verwendet wird:

      VB.NET-Quellcode

      1. Using dlg As New PrintPreviewDialog
      2. Me.PrintDocument1.DocumentName = "Test" ' Arbeitstitel
      3. dlg.Document = Me.PrintDocument1
      4. dlg.ShowDialog()
      5. End Using
      Nun wird uns ein leeres Blatt angeboten:

      Um ein Blatt mit Inhalt zu befüllen, benötigen wir den PrintPage-Handler des zu druckenden Dokuments, wir erstellen ihn, indem wir im Designer auf PrintDocument1 doppelklicken:

      VB.NET-Quellcode

      1. Private Sub PrintDocument1_PrintPage(sender As System.Object, e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
      2. End Sub
      Auch dieser Code liefert nur ein leeres Dokument.
      Zum Test fügen wir eine einfache DrawString-Anweisung in die PrintDocument1_PrintPage-Prozedur ein:

      VB.NET-Quellcode

      1. e.Graphics.DrawString("Bla", New Font("arial", 30), Brushes.Black, New Point(30, 30))
      Und schon passiert etwas:

      Nun bereiten wir die zu zeichnenden Kreise vor, wir geben der Klasse folgende Member:

      VB.NET-Quellcode

      1. Private Diameter() As Integer = { 10, 15, 20 } ' Array der darzustellenden Durchmesser in Zentimeter
      2. Private index As Integer = 0 ' Startindex im Array
      Dieser wird jedesmal genullt, wenn wir den Dialog aufrufen:

      VB.NET-Quellcode

      1. index = 0 ' Startindex zurücksetzen
      2. Using dlg As New PrintPreviewDialog
      Zuerst geben wir die Seitenzahl aus, entsprechend des Indexes. Seitenzahlen beginnen mit 1, also inkrementieren wir den Index:

      VB.NET-Quellcode

      1. Private Sub PrintDocument1_PrintPage(sender As System.Object, e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
      2. Dim g = e.Graphics
      3. g.DrawString(String.Format("Überschrift Seite {0}", index + 1), New Font("arial", 20), Brushes.Black, New Point(30, 30))
      4. End Sub
      Mit Zoom=150% sieht das so aus, jedoch wird momentan nur eine Seite gedruckt:

      Wir wollen genau 3 Seiten drucken, dies steuern wir mit der Property HasMorePages von e (PrintPageEventArgs-Parameter der PrintPage-Prozedur)

      VB.NET-Quellcode

      1. e.HasMorePages = index < 2 ' True bei index >= 2
      2. index += 1 ' nächste Seite
      Und schon werden 3 Seiten gedruckt, hier die 3. Seite mit 150 %

      Nun wollen wir mal einen ersten Druckversuch wagen.
      Ups, hier stimmt was nicht:

      Im Preview-Dialog werden 3 Seiten angezeigt, gedruckt wird aber Seite 4. :S
      Ursache ist, dass zum eigentlichen Druck wieder die PrintDocument1_PrintPage-Prozedur verwendet wird, der Index zählt weiter, e.HasMorePages ist False, da der Index größer-gleich 2 ist.
      Also müssen wir den Index zurücksetzen, wenn ein neuer Druckauftrag gestartet wird:

      VB.NET-Quellcode

      1. Private Sub PrintDocument1_BeginPrint(sender As System.Object, e As System.Drawing.Printing.PrintEventArgs) Handles PrintDocument1.BeginPrint
      2. index = 0 ' Startindex initialisieren
      3. End Sub
      Hier wird im Parameter e.PrintAction auch das Ziel mitgeteilt: PrintToPreview oder PrintToPrinter.
      Nun werden 3 Seiten zur Vorschau angezeigt und es werden 3 Seiten gedruckt, wie es sein soll:

      Nun wollen wir die Kreise darstellen, das ist nun eigentlich trivial:

      VB.NET-Quellcode

      1. Private Sub PrintDocument1_PrintPage(sender As System.Object, e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
      2. Dim g = e.Graphics
      3. g.PageUnit = GraphicsUnit.Millimeter ' Alle Positionsangaben erfolgen nun in Millimetern
      4. g.DrawString(String.Format("Überschrift Seite {0}", index + 1), New Font("arial", 20), Brushes.Black, New Point(30, 30))
      5. Dim dia = Me.Diameter(index) * 10 ' mm => cm
      6. Dim rc = New Rectangle(0, 0, dia, dia)
      7. g.DrawEllipse(Pens.Black, rc)
      8. e.HasMorePages = index < 2 ' True bei index >= 2
      9. index += 1 ' nächste Seite
      10. If index > 2 Then
      11. index = 0
      12. End If
      13. Debug.WriteLine(index.ToString)
      14. End Sub

      Noch ein Ups: Da wir GraphicsUnit.Millimeter eingestellt haben, verschiebt sich auch die Position der Überschrift, wir korrigieren dies, indem wir die Position (10, 10) vorgeben.
      Die Kreise zentrisch in der GraphicsUnit.Millimeter darzustellen ist etwas schwieriger, da sich die GraphicsUnit, im Gegensatz zur MSDN, nicht auf das VisibleClipBound auswirkt.

      Graphics.VisibleClipBounds-Eigenschaft schrieb:

      Die Einheit für das sich ergebende Rechteck wird von der PageUnit-Eigenschaft festgelegt. Die Standardeinheit ist Pixel. Ein Graphics ist normalerweise mit einem Steuerelement verbunden, und der Ursprung des Rechtecks ist relativ zum Clientbereich dieses Steuerelements.
      -----
      So, dies soll zur Aufwärmung reichen,
      im nächsten Beitrag wollen wir ein langes Textdokument drucken. ;)
      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).
      VB-Fragen über PN / Konversation werden ignoriert!

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

      2. Teil:
      Wir drucken ein langes Textdokument.
      Um uns zunächst einen Eindruck zu verschaffen, drucken wir mal einfach drauflos:
      PrintPreviewialog wie oben. Um keine Verwirrung zu stiften, nehme ich Button2 und PrintDocument2, so finden beide Aufgaben in einem Projekt Platz.
      Zunächst zur Zählung 2 Variablen, eine für die aktuelle Seite, eine für die Position im Text. Dieser wird bei mir in einer RichTextBox dargestellt, ich nehme einfach den Text von Post #1 aus dem Post-Editor.

      VB.NET-Quellcode

      1. Private pageNb As Integer = 0 ' aktuelle Seitennummer
      2. Private indexText As Integer = 0 ' Startposition der aktuellen Seite im Text
      Diese werden in der BeginPrint-Prozedur initialisiert

      VB.NET-Quellcode

      1. Private Sub PrintDocument2_BeginPrint(sender As System.Object, e As System.Drawing.Printing.PrintEventArgs) Handles PrintDocument2.BeginPrint
      2. Me.pageNb = 0 ' aktuelle Seitennummer
      3. Me.indexText = 0 ' Startposition der aktuellen Seite im Text
      4. End Sub
      und die PrintPage-Routine sieht im 1. Wurf so aus:

      VB.NET-Quellcode

      1. Private Sub PrintDocument2_PrintPage(sender As System.Object, e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument2.PrintPage
      2. Dim g = e.Graphics
      3. g.DrawString(String.Format("Seite {0}", Me.pageNb + 1), New Font("arial", 20), Brushes.Black, New Point(10, 10))
      4. Dim rc = g.VisibleClipBounds ' der Druckbereich
      5. Dim txt = Me.RichTextBox1.Text ' brauchen wir noch öfter
      6. Dim ft = New Font("Arial", 10) ' der Druck-Font
      7. g.DrawString(txt.Substring(Me.indexText), ft, Brushes.Black, rc)
      8. End Sub

      Wie zu erwarten, sieht das Resultat nicht sonderlich berauschend aus, aber es wird schon mal was gedruckt.
      Als erstes wollen wir der Seite ihre Ränder geben. Diese stehen in der Property Me.PrintDocument2.DefaultPageSettings.Margins.
      Der Druckbereich wird nun folgendermaßen berechnet, oben links einfach draufaddieren, die Breite muss um den linken und den rechten; die Höhe um den oberen und unteren Rand korrigiert werden:

      VB.NET-Quellcode

      1. ' Ruft die Seitenränder für diese Seite ab oder legt diese fest.
      2. Dim margin = Me.PrintDocument2.DefaultPageSettings.Margins
      3. rc.Offset(margin.Left, margin.Top) ' obere linke Ecke
      4. rc.Width -= (margin.Left + margin.Right) ' neue Breite
      5. rc.Height -= (margin.Top + margin.Bottom) ' neue Höhe

      Das Layout ist akzeptabel, wenn Ihr andere Druckbereiche haben wollt, macht Euch einen Dialog, bei dem Ihr die Ränder vorgeben könnt. Vorgabe sollte DefaultPageSettings.Margins des Druckdokuments sein.
      Wenn wir uns nun den Output ansehen, insbesondere den unteren Rand, sehen wir 1., dass nicht der gesamte Text gedruckt wurde und 2. dass unten sogar der untere Teil der letzten Zeile fehlt:

      Wir müssen also feststellen, wieviel des Textes überhaupt auf die Druckseite passt.
      Dazu hat uns Bill die Funktion MeasureString im Zusammenwirken mit der Aufgabe Bestimmung der Zeilenanzahl gegeben:

      VB.NET-Quellcode

      1. ' System.Drawing.StringFormat, das Formatierungsinformationen für die Zeichenfolge darstellt.
      2. Dim stringFormat As New StringFormat(StringFormatFlags.LineLimit) ' Bestimmung der Zeilenanzahl
      3. ' Anzahl der Zeichen in der Zeichenfolge, die auf einmal gedruckt wird
      4. ' Zielgröße, abhängig von Font und Rechteck
      5. Dim charactersFitted As Integer
      6. ' Anzahl der Textzeilen in der Zeichenfolge, wird hier nicht weiter verwendet
      7. Dim linesFilled As Integer
      8. ' Bestimmung der Anzahl der druckbaren Zeichen
      9. e.Graphics.MeasureString(txt.Substring(Me.indexText), ft, rc.Size, stringFormat, charactersFitted, linesFilled)
      Der Rest ergibt sich dann von selbst, mit der entsprechenden Überladung des DrawString-Befehls wird eine ganzzahlige Zeilenanzahl ausgegeben.

      VB.NET-Quellcode

      1. ' Druck des Textes, wegen StringFormatFlags.LineLimit werden keine (höhen-)halben Zeilen gedruckt
      2. e.Graphics.DrawString(txt.Substring(Me.indexText), ft, Brushes.Black, rc, stringFormat)
      3. ' Festlegen der Text-Startposition der nächsten Seite
      4. Me.indexText += charactersFitted
      5. ' bestimmen, ob es überhaupt weitere Seiten gibt
      6. e.HasMorePages = Me.indexText < txt.Length
      7. ' die nächste Seitennummer
      8. Me.pageNb += 1
      Und nun sind wir schon (fast) fertig:

      Es werden 2 Seiten gedruckt, der Text wird vollständig ausgegeben.
      Nun wollen wir noch sehen, was passiert, wenn wir einen anderen Font nehmen.
      Hier könnten wir einen FontDialog nehmen, wir begnügen uns gier mit einem NumericUpDown-Control, mit dem wir die Fontgröße vorgeben können:

      VB.NET-Quellcode

      1. Dim ft = New Font("Arial", NumericUpDown1.Value) ' der Druck-Font
      Mit einer Fontgröße von 8 und 12 ergeben sich folgende Previews:
      bzw.
      die gedruckten Dokumente sehen selbstverständlich genau so aus.
      Hier noch mal die komplette
      PrintPage-Routine

      VB.NET-Quellcode

      1. Private Sub PrintDocument2_PrintPage(sender As System.Object, e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument2.PrintPage
      2. Dim g = e.Graphics
      3. g.DrawString(String.Format("Seite {0}", Me.pageNb + 1), New Font("arial", 20), Brushes.Black, New Point(10, 10))
      4. Dim rc = g.VisibleClipBounds ' der Druckbereich
      5. Dim txt = Me.RichTextBox1.Text ' brauchen wir noch öfter
      6. Dim ft = New Font("Arial", NumericUpDown1.Value) ' der Druck-Font
      7. ' Ruft die Seitenränder für diese Seite ab oder legt diese fest.
      8. Dim margin = Me.PrintDocument2.DefaultPageSettings.Margins
      9. rc.Offset(margin.Left, margin.Top) ' obere linke Ecke
      10. rc.Width -= (margin.Left + margin.Right) ' neue Breite
      11. rc.Height -= (margin.Top + margin.Bottom) ' neue Höhe
      12. g.DrawString(txt.Substring(Me.indexText), ft, Brushes.Black, rc)
      13. ' System.Drawing.StringFormat, das Formatierungsinformationen für die Zeichenfolge darstellt.
      14. Dim stringFormat As New StringFormat(StringFormatFlags.LineLimit) ' Bestimmung der Zeilenanzahl
      15. ' Anzahl der Zeichen in der Zeichenfolge, die auf einmal gedruckt wird
      16. ' Zielgröße, abhängig von Font und Rechteck
      17. Dim charactersFitted As Integer
      18. ' Anzahl der Textzeilen in der Zeichenfolge, wird hier nicht weiter verwendet
      19. Dim linesFilled As Integer
      20. ' Bestimmung der Anzahl der druckbaren Zeichen
      21. e.Graphics.MeasureString(txt.Substring(Me.indexText), ft, rc.Size, stringFormat, charactersFitted, linesFilled)
      22. ' Druck des Textes, wegen StringFormatFlags.LineLimit werden keine (höhen-)halben Zeilen gedruckt
      23. e.Graphics.DrawString(txt.Substring(Me.indexText), ft, Brushes.Black, rc, stringFormat)
      24. ' Festlegen der Text-Startposition der nächsten Seite
      25. Me.indexText += charactersFitted
      26. ' bestimmen, ob es überhaupt weitere Seiten gibt
      27. e.HasMorePages = Me.indexText < txt.Length
      28. ' die nächste Seitennummer
      29. Me.pageNb += 1
      30. 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).
      VB-Fragen über PN / Konversation werden ignoriert!
      Mehrfach wurde im Forum explizit der Druckbereich angesprochen, deswegen poste ich hier noch mal ein Beispiel
      • mit einem PrintDialog zur Druckerauswahl und Vorgabe der Anzahl der Kopien,
      • einem PageSetupDialog zur Festlegung der Seitenausrichtung und der Seitenränder
      • sowie einem PrintPreviewDialog zur Ausgabe der Druckvorschau.
      In dieser wird der modifizierte Druckbereich mit einem Rechteck umrandet und es wird ein Text in dieses Rechteck geschrieben, der Druck wird nach der 1. Seite abgebrochen.
      Eine Form mit 2 Button sowie einem PrintDocument:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Public Class Form1
      2. ' vorbereiteter Text
      3. Private PrintText As String
      4. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
      5. ' den Drucktext aufbauen
      6. Dim txt = "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern. "
      7. For i = 1 To 100
      8. Me.PrintText &= txt
      9. Next
      10. End Sub
      11. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      12. ' Drucker und Anzahl der Kopien festlegen
      13. Using dlg As New PrintDialog
      14. dlg.Document = Me.PrintDocument1
      15. dlg.ShowDialog()
      16. End Using
      17. End Sub
      18. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
      19. ' Seitenränder festlegen
      20. Using dlg As New PageSetupDialog
      21. dlg.Document = Me.PrintDocument1
      22. If dlg.ShowDialog() <> Windows.Forms.DialogResult.OK Then
      23. Return
      24. End If
      25. End Using
      26. ' Druckvorschau
      27. Using dlg As New PrintPreviewDialog
      28. dlg.Document = Me.PrintDocument1
      29. dlg.ShowDialog()
      30. End Using
      31. End Sub
      32. Private Sub PrintDocument1_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
      33. Dim g = e.Graphics
      34. Dim rc = g.VisibleClipBounds ' der Druckbereich
      35. ' Seitenränder für diese Seite entsprechend dem PagesetupDialog festlegen
      36. Dim margin = Me.PrintDocument1.DefaultPageSettings.Margins
      37. rc.Offset(margin.Left, margin.Top) ' obere linke Ecke
      38. rc.Width -= (margin.Left + margin.Right) ' neue Breite
      39. rc.Height -= (margin.Top + margin.Bottom) ' neue Höhe
      40. ' das Um-Rechteck zeichnen
      41. g.DrawRectangle(Pens.Black, rc.Left, rc.Top, rc.Width, rc.Height)
      42. ' den Texz ausgeben
      43. Dim ft = New Font("Arial", 12) ' der Druck-Font
      44. g.DrawString(Me.PrintText, ft, Brushes.Black, rc)
      45. End Sub
      46. End Class

      Standard-Bildränder (10 mm):


      Oberer Bildrand auf 100 mm vergrößert:

      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).
      VB-Fragen über PN / Konversation werden ignoriert!

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

      Hier möchte ich noch mal auf die Auswahl und die Vorgabe von Hoch- oder Querformat eingehen.
      Per Default ist beim Drucken das Papier auf Hochkant eingestellt.
      Wenn der User entscheiden soll, ob das Dokument hoch oder quer gedruckt wird, muss vor dem PrintPreviewDialog ein PrintDialog vorgeschaltet werden.

      VB.NET-Quellcode

      1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      2. Using dlg = New PrintDialog ' Druckerauswahl, Druckereigenschaften, Papierausrichtung
      3. dlg.Document = Me.PrintDocument1
      4. If dlg.ShowDialog <> Windows.Forms.DialogResult.OK Then
      5. Return
      6. End If
      7. End Using
      8. Using dlg = New PrintPreviewDialog ' Vorschau und Druck
      9. dlg.Document = Me.PrintDocument1
      10. If dlg.ShowDialog <> Windows.Forms.DialogResult.OK Then
      11. Return
      12. End If
      13. End Using
      14. End Sub

      Abhängig vom ausgewählten Drucker wird bei Eigenschaften ein spezifischer Dialog angezeigt, in dem die Ausrichtung des Papiers vorgegeben werden kann, hier am Beispiel des Foxit PhantomPDF Printer:

      Steht fest, dass das Dokument im Querformat gedruckt werden soll, kann dies dem Druckdokument auch direkt vorgegeben werden
      oder es wird die vom User im PrintDialog vorgenommene Auswahl überschrieben
      oder es wird der Wert einer externen Checkbox eingetragen:

      VB.NET-Quellcode

      1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      2. Me.PrintDocument1.DefaultPageSettings.Landscape = True ' True: Quer, False: Hochkant
      3. Using dlg = New PrintPreviewDialog ' Vorschau und Druck
      4. dlg.Document = Me.PrintDocument1
      5. If dlg.ShowDialog <> Windows.Forms.DialogResult.OK Then
      6. Return
      7. End If
      8. End Using
      9. 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).
      VB-Fragen über PN / Konversation werden ignoriert!

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

      In diesem Thread wurde nach den verfügbaren Druckern und der Farbtauglichkeit des gewählten Druckers gefragt.
      Um das nicht immer wieder einzeln auszuführen, hier die Abfragen:
      Abfrage der installierten Drucker:

      VB.NET-Quellcode

      1. For Each printer In PrinterSettings.InstalledPrinters ' Shared Property
      2. Me.ListBox1.Items.Add(printer.ToString())
      3. Next
      Abfrage der Farbtauglichkeit:

      VB.NET-Quellcode

      1. Using dlg = New PrintDialog
      2. If dlg.PrinterSettings.SupportsColor Then
      3. MessageBox.Show("Farbe")
      4. Else
      5. MessageBox.Show("Schwarz-Weiß")
      6. End If
      7. End Using
      Zu weiteren Properties seht Euch bitte die PrinterSettings an: docs.microsoft.com/de-de/dotne…s?view=netframework-4.7.2
      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).
      VB-Fragen über PN / Konversation werden ignoriert!