16.2 Einen Druckauftrag erteilen
16.2.1 Methoden und Eigenschaften von PrintDocument
Ein Druckauftrag wird durch ein Objekt vom Typ PrintDocument beschrieben. Wenn Sie mit dem Windows Forms-Designer arbeiten, können Sie das gleichnamige Steuerelement in die Form ziehen (und finden es danach im Komponentenfach wieder). Sie können die Klasse nur mit einem parameterlosen Konstruktor instanziieren:
Dim printDoc As New PrintDocument()
Den Druckauftrag schicken Sie an den Drucker, indem Sie die parameterlose Print-Methode des PrintDocument-Objekts aufrufen:
printDoc.Print()
Ist ein Drucker am Rechner angeschlossen, wird daraufhin der Druckjob gestartet und kurz ein Meldungsfenster angezeigt, das den eingeleiteten Druckvorgang und die Anzahl der zu druckenden Seiten ausgibt.
Ohne weitere Angaben wird der Standarddrucker angesteuert. Stehen mehrere Drucker zur Auswahl, kann über die Eigenschaft PrinterSettings ein anderer Drucker ausgewählt werden. Eine Alternative dazu bietet das PrintDialog-Steuerelement.
Bevor der Druckauftrag mit der Print-Methode abgeschickt wird, sollte dem Druckjob mittels der Eigenschaft DocumentName des PrintDocument-Objekts eine passende Bezeichnung gegeben werden. Standardmäßig lautet diese document. Der Name wird unter anderem in der Druckerwarteschlange verwendet.
Eigenschaften und Methoden von PrintDocument finden Sie in der Tabelle 16.1.
Methode/Eigenschaft | Beschreibung |
|
Startet den Druckauftrag (Methode). |
DefaultPageSettings |
Standardseiteneinstellungen für alle zu druckenden Seiten |
DocumentName |
Zeichenfolge zur Identifizierung des Druckauftrags |
OriginAtMargins |
Gibt an, ob das ein einer Seite zugeordnete Grafikobjekt innerhalb der vom Benutzer angegebenen Seitenränder oder in der linken oberen Ecke des Druckbereichs der Seite positioniert ist. |
PrintController |
Druckercontroller, der den Druckvorgang steuert |
PrinterSettings |
Einstellungen des Druckers, auf dem das Dokument gedruckt wird |
16.2.2 Die Ereignisse in PrintDocument
Die beiden Codezeilen
Dim printDoc As New PrintDocument() printDoc.Print()
drucken zwar ein Blatt Papier aus, aber es ist leer. Woher soll der Drucker auch wissen, was er zu Papier bringen soll? Die Steuerung des Dokumentenausdrucks erfolgt über vier Ereignisse des PrintDocument-Objekts:
- BeginPrint tritt direkt nach dem Aufruf der Print-Methode auf.
- QueryPageSettings ist der richtige Platz zur Änderung des Layouts folgender Seiten.
- PrintPage wird für jede zu druckende Seite nach QueryPageSettings ausgelöst. Hier werden die Dateninformationen übergeben, die der Drucker ausgeben soll.
- EndPrint tritt nach dem Beenden des Druckens auf oder wenn während des Druckvorgangs eine Ausnahme ausgelöst wird.
16.2.3 Die Ereignisse BeginPrint und EndPrint
BeginPrint eignet sich für seitenübergreifende Initialisierungen zum Beispiel von Schriftarten, und EndPrint sollte nötige Aufräumarbeiten durchführen, zum Beispiel Schriftarten wieder freigeben. Die Ereignisse beider Ereignishandler haben als zweiten Parameter ein Objekt vom Typ PrintEventArgs, das in seinen beiden Eigenschaften Cancel und PrintAction den Abbruch der Druckoperation ermöglicht (Cancel=True) bzw. das Druckziel angibt (siehe Tabelle 16.2).
Eigenschaft | Beschreibung |
Cancel |
Ermöglicht den Abbruch der Operation. |
PrintAction |
Beschreibt das Ziel des Druckvorgangs. Sie ist vom Typ PrintAction. |
PrintAction ist vom Typ der in Tabelle 16.3 gezeigten gleichnamigen Enumeration.
Konstante | Wohin wird gedruckt? |
PrintToFile |
Datei |
PrintToPreview |
Druckvorschau |
PrintToPrinter |
Drucker |
16.2.4 Das Ereignis QueryPageSettings
Das zweite Argument des Ereignishandlers ist vom Typ QueryPageSettingsEventArgs. Es hat drei Eigenschaften: Cancel zum Abbruch des Druckvorgangs, PrintAction zur Beschreibung des Ausgabeziels sowie PageSettings. Letztere stellt die Referenz auf ein gleichnamiges Objekt bereit, mit dem die Einstellungen der zu druckenden Seite festgelegt werden. Das in Tabelle 16.4 gezeigte QueryPageSettingsEventArgs spielt nur dann eine Rolle, wenn zu druckende Seiten mit unterschiedlichen Seiteneinstellungen ausgedruckt werden sollen.
Eigenschaft | Beschreibung |
Cancel |
Ermöglicht den Abbruch der Operation. |
PageSettings |
Beschreibt die Seiteneinstellung der aktuellen Seite. |
PrintAction |
Beschreibt das Ziel des Druckvorgangs. Sie ist vom Typ PrintAction. |
16.2.5 Das Ereignis PrintPage
PagePrint ist das wichtigste Ereignis eines Druckjobs, denn in diesem Ereignis wird festgelegt, was der Drucker zu Papier bringen soll. Der Ereignishandler erhält vom Ereignis ein Objekt vom Typ PrintPageEventArgs, in dem die sechs in Tabelle 16.5 gezeigten Eigenschaften definiert sind, die für den Ausdruck von wesentlicher Bedeutung sind:
Eigenschaft | Beschreibung | |
Cancel |
Gibt an, ob der Druckauftrag abzubrechen ist. |
|
Graphics |
Kontext zum Zeichnen der Seite |
R |
HasMorePages |
Gibt an, ob noch mehr Seiten gedruckt werden sollen. |
|
MarginBounds |
Rechteckiger Bereich der Seite nach Abzug der Seitenränder |
R |
PageBounds |
Rechteckiger Bereich, der die Gesamtfläche der Seite darstellt |
R |
PageSettings |
Seiteneinstellung der aktuellen Seite |
R |
Für jede zu druckende Seite werden die Ereignisse QueryPageSettings und PrintPage ausgelöst. Die Eigenschaft PageSettings kann in QueryPageSettings geändert werden und ist in PrintPage schreibgeschützt.
Drucken auf ein Graphics-Objekt
Der Aufruf der Print-Methode setzt die Ereigniskette des PrintDocument-Objekts in Gang. Sie müssen das PrintPage-Ereignis behandeln, damit nicht nur leere Seiten ausgedruckt werden. Hierbei spielt das vom Parameter PrintPageEventArgs übergebene Graphics-Objekt die Schlüsselrolle. Es hat die gleiche Rolle wie das Graphics-Objekt, das vom Paint-Ereignis oder über CreateGraphics bereitgestellt wird, lediglich das Ziel der Ausgabe ist nicht der Bildschirm, sondern die Druckvorschau, der Drucker oder eine Datei. Die Standardmaßeinheit GraphicsUnit.Display entspricht 1/100 Zoll. Außer Myanmar haben alle Länder inzwischen offiziell metrische Einheiten (in der Praxis sind diese in den USA jedoch »unbekannt«). Sie sollten daher die Einheiten umstellen (g ist ein Graphics-Objekt).
g.PageUnit = GraphicsUnit.Millimeter
Für jede zu druckende Seite wird das Ereignis PrintPage erneut ausgelöst. Das hat weitreichende Konsequenzen, denn damit wird auch jedes Mal ein neues Graphics-Objekt erstellt. Wenn Sie eine Graphics-Einstellung für eine Druckseite vornehmen, wird diese nicht bei von der nächsten zu druckenden Seite übernommen. Deshalb müssen Sie die Grundeinstellungen von Graphics für jede weitere Seite erneut festlegen.
Seitenränder
Die Eigenschaft PageBounds liefert die Abmessungen des zu bedruckenden Papiers.
Public ReadOnly Property PageBounds As Rectangle |
Wenn Sie Width und Height des zurückgegebenen Rectangle-Objekts abfragen, erhalten Sie die Abmessungen des entsprechenden Papierformats, zum Beispiel die Breite in Millimeter (die Standardeinheit von 1/100 Zoll wurde nicht geändert):
e.PageBounds.Width * 25.4/100
PageBounds hat seinen Ursprung (0, 0) in der linken oberen Ecke des vom Drucker bedruckbaren Bereichs. Bei den meisten Druckern ist dieser Punkt gegenüber der linken oberen Ecke des Papiers verschoben. 4 mm Rand ist bereits relativ wenig.
Alles, was außerhalb des bedruckbaren Bereichs liegt, wird einfach abgeschnitten. Daher definiert die Eigenschaft MarginBounds des PrintPageEventArgs-Objekts ein kleineres Rechteck, das standardmäßig 100 Einheiten (also 25,4 mm) kleiner als die Seite ist. Dieses Rechteck ist um denselben Betrag verschoben wie PageBounds, sodass es asymmetrisch bezüglich der Seite ist.
Mit der Einstellung OriginAtMargins=True des Druckdokuments vom Typ PrintDocument sind alle Koordinaten relativ zu dem durch MarginBounds festgelegten Bereich und nicht relativ zum bedruckbaren Bereich.
Bedruckbarer Bereich
Die Aufteilung des bedruckbaren und des nicht bedruckbaren Bereichs können Sie mit einigen Eigenschaften in 1/100 Zoll ermitteln. Alle Eigenschaften sind schreibgeschützt. Die Werte hängen vom Druckermodell ab.
- PrintableArea im PrinterSettins-Objekt vom Typ RectangleF beschreibt die bedruckbare Fläche.
- HardMarginX und HardMarginY im PrinterSettins-Objekt vom Typ Single geben den unbedruckbaren Rand an.
- VisibleClipBounds im Graphics-Objekt vom Typ RectangleF beschreibt die bedruckbare Fläche.
Wenn Sie die Werte für den linken und oberen Rand von den Koordinaten zu zeichnender Elemente abziehen, ist der Bezugspunkt nicht mehr der bedruckbare Bereich, sondern die linke obere Ecke der physikalischen Seite. Damit können Sie Elemente auf der Seite absolut positionieren und das Papier maximal ausnutzen.
Beispielausdruck der Seitenränder
Im folgenden Programmbeispiel werden die verschiedenen Ränder durch den Ausdruck von Rechtecken kenntlich gemacht: je eins für die Seite (PageBounds), den Clientbereich (MarginBounds) und des bedruckbaren Bereichs (VisibleClipBounds). Schließlich wird der druckbare Bereich in PrintableArea genutzt, um ein Rechteck absolut zu positionieren. Dazu wird erst ein Druckdokument doc erzeugt und im Ereignishandler der Schaltfläche mit Print ausgedruckt. Dadurch wird der Ereignishandler Druck aufgerufen.
'...\Drucken\Eigenschaften\Seitenrand.vb |
Imports System.Drawing.Printing Public Class Seitenrand Private WithEvents doc As New PrintDocument() Private Sub Druck(sender As Object, e As PrintPageEventArgs) _ Handles doc.PrintPage Dim g As Graphics = e.Graphics Dim schwarz As New Pen(Brushes.Black, 1), rot As New Pen(Brushes.Red, 1) ' vordefinierte Bereiche Dim page As Rectangle = e.PageBounds g.DrawRectangle(schwarz, page.Left, page.Top, page.Width, page.Height) Dim marg As Rectangle = e.MarginBounds g.DrawRectangle(schwarz, marg.X, marg.Y, marg.Width, marg.Height) Dim clip As RectangleF = g.VisibleClipBounds g.DrawRectangle(rot, clip.X, clip.Y, clip.Width, clip.Height) ' bedruckbaren Bereich erfassen g.DrawLine(schwarz, –10, 150, page.Width + 20, 150) g.DrawLine(schwarz, 150, –10, 150, page.Height + 20) ' exaktes Rechteck Dim pa As RectangleF = e.PageSettings.PrintableArea Dim dx As Single = 200 – pa.Left, dy As Single = 200 – pa.Top g.DrawRectangle(schwarz, dx, dy, pa.Width – dx, pa.Height – dy) End Sub Private Sub Drucken_Click(sender As Object, e As EventArgs) _ Handles Drucken.Click doc.Print() End Sub End Class
Die ausgedruckten Rechtecke im Einzelnen:
- PageBounds: Linke obere Ecke entsprechend dem nicht bedruckbaren Bereich um ein paar Millimeter von der Seitenecke nach rechts unten verschoben. Da Breite und Höhe sich auf die gesamte Seite beziehen, sind die rechte und die untere Kante abgeschnitten.
- MarginBounds: Gegenüber PageBounds um 100 Einheiten von 1/100 Zoll in allen Richtungen kleineres Rechteck, um je 100 Einheiten nach rechts unten verschoben.
- VisibleClipBounds: Rechteck, das den Druckbereich maximal ausnutzt. Die linke obere Ecke ist die gleiche wie in PageBounds, wird aber nirgends abgeschnitten.
- PrintableArea: Durch Abzug des unbedruckbaren Bereichs ist die linke obere Ecke 50,8 mm von der der Seite entfernt und nutzt die Seite rechts und unten maximal aus.
Einheiten
Die Standardeinheit 1/100 Zoll ist etwas anachronistisch. Besser ist die Verwendung von Millimeter als Einheit. Das folgende Beispiel druckt ein Rechteck der Breite 100 mm und der Höhe 120 mm, das 50 mm vom linken und 80 mm vom oberen Rand entfernt ist. Durch die Zuweisung an PageUnit sind die Abmessungen ohne weitere Umrechnung anzugeben. Dies gilt nicht für die Verschiebung um den nicht bedruckbaren Rand, da er immer in 1/100 Zoll angegeben ist. Die in Pen angegebene Strichstärke wird auch in Millimeter gemessen. Bei solch dicken Linien ist es wichtig zu beachten, dass die Position und Abmessungen der Zeichenfunktionen sich immer auf die Linienmitte beziehen.
'...\Drucken\Eigenschaften\Seitenrand.vb |
Imports System.Drawing.Printing Public Class Seitenrand Private WithEvents doc As New PrintDocument() Private Sub Druck(sender As Object, e As PrintPageEventArgs) _ Handles doc.PrintPage Dim g As Graphics = e.Graphics Dim schwarz As New Pen(Brushes.Black, 1) g.PageUnit = GraphicsUnit.Millimeter Dim pa As RectangleF = e.PageSettings.PrintableArea g.DrawRectangle(schwarz, 50-pa.X*25.4\100, 80-pa.X*25.4\100, 100, 120) End Sub Private Sub Drucken_Click(sender As Object, e As EventArgs) _ Handles Drucken.Click doc.Print() End Sub End Class
Voreinstellungen
Alle Eigenschaften, die die Seitenränder beschreiben, sind schreibgeschützt. Daher müssen sie gesetzt werden, bevor der Druckvorgang beginnt. Da sie vom Druckjob unabhängig sind, werden sie im Druckdokument gesetzt und werden immer dann genutzt, wenn nicht an anderer Stelle spezifischere Einstellungen gemacht werden.
Public Property DefaultPageSettings As PageSettings |
PageSettings hat die Eigenschaft Margin, die selbst die Eigenschaften Left, Right, Top und Bottom aufweist (siehe dazu auch Abschnitt 16.3, »Seiteneinstellungen mit PageSettings«). Die Einheiten sind dieselben wie im Graphics-Objekt (Voreinstellung 1/100 Zoll). Mit folgender Anweisung legen Sie beispielsweise den linken Rand auf eine Breite von 20 mm fest:
printDocument1.DefaultPageSettings.Margin.Left = 2 * 100 \ 25.4
Das folgende Beispiel setzt Seitenränder von 50, 60, 80 und 97 für den linken, rechten, oberen und unteren Rand. Durch die Umschaltung der Einheit in der Methode Druck sind dies Millimeter. Mit TranslateTransform wird der Bezugspunkt folgender Ausgaben auf die linke obere Ecke des Druckbereichs gesetzt.
'...\Drucken\Eigenschaften\Seitenrand.vb |
Imports System.Drawing.Printing Public Class Voreinstellung Private WithEvents doc As New PrintDocument() Private Sub Druck(ByVal sender As Object, ByVal e As PrintPageEventArgs) _ Handles doc.PrintPage Dim g As Graphics = e.Graphics g.PageUnit = GraphicsUnit.Millimeter Dim pa As RectangleF = e.PageSettings.PrintableArea g.TranslateTransform(e.MarginBounds.Left – pa.Left * 25.4 \ 100, _ e.MarginBounds.Top – pa.Top * 25.4 \ 100) g.DrawRectangle(New Pen(Brushes.Black, 1), 0, 0, 100, 120) End Sub Private Sub Drucken_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles Drucken.Click doc.DefaultPageSettings.Margins = New Margins(50, 60, 80, 97) doc.Print() End Sub End Class
Mit OriginAtMargins=True statt TranslateTransform ergibt sich ein Versatz, dessen Ursache ich nicht befriedigend erklären kann.
16.2.6 Beenden des Druckauftrags
Mit den beiden Eigenschaften HasMorePages und Cancel des PrintPageEventArgs-Objekts kann ein Druckauftrag beendet werden, jedoch unter verschiedenen Voraussetzungen.
HasMorePages ist standardmäßig auf False gesetzt. Sollen mehrere Seiten ausgedruckt werden, muss der Wert True sein, damit der Ereignishandler noch einmal aufgerufen wird. Nach dem Druck der letzten Seite muss der Wert False sein. Dazu muss im Programmcode ermittelt werden, ob noch eine weitere Seite zum Drucken ansteht. In Abschnitt 16.7, »Mehrseitiger Text«, werden wir so einen Fall programmieren.
Cancel steht standardmäßig auf False. Auf True gesetzt, wird der Druckauftrag abgebrochen – unabhängig davon, wie viele Seiten noch zu drucken sind.
16.2.7 WYSIWYG
Graphics-Objekte der Ereignisse PrintPage und Paint unterscheiden sich »nur« durch das Ziel der Ausgabe. Es lohnt sich daher, die Teile der Anzeige eines Programms, die sowohl auf dem Bildschirm angezeigt als auch ausgedruckt werden, durch eine einzige Methode erstellen zu lassen. Das Ziel ist eine möglichst weitgehende Korrespondenz von Anzeige und Ausdruck (WYSIWYG – what you see is what you get).
Ganz ohne Kontrollstrukturen zur Anpassung der Ausgabe werden Sie aber nicht auskommen, denn die Ausgabegeräte unterscheiden sich deutlich. Ausdrucke haben eine feste Seitengröße, während Sie auf dem Bildschirm beliebig scrollen können. Bei einer Animation müssen Sie sich entscheiden, welches der Bilder ausgedruckt werden soll. Ein großer Vorteil von Ausdrucken ist die höhere Auflösung, die aber eine »sauberere« Definition der Ausgabe erfordert, damit zum Beispiel Texte in Blocksatz keine ausgefransten Ränder haben. Noch wichtiger ist die korrekte Ausgabe bei beidseitigem Druck, damit bei dünnem Papier nicht Textzeilen der Rückseite zwischen den Zeilen der Vorderseite landen und durchscheinen.
Das folgende Beispielprogramm zeigt, wie Sie eine gemeinsame Ausgabefunktion für Bildschirm und Drucker prinzipiell realisieren können. Dazu wird ein einfaches in Abbildung 16.1 gezeigtes Linienmuster nach dem Starten des Programms in einer PictureBox angezeigt. Nach dem Anklicken des Buttons wird die Grafik auf dem (Standard-)Drucker ausgedruckt.
Abbildung 16.1 Ausgabe des Beispiels »Ausdruck«
'...\Drucken\Druck\Ausdruck.vb |
Imports System.Drawing.Printing Public Class Ausdruck Private Sub DrawGraphic(g As Graphics, _ x1 As Int32, y1 As Int32, x2 As Int32, y2 As Int32) Dim p As New Pen(Color.Black) For i As Integer = x1 To x2 Step 20 : g.DrawLine(p, x1, y1, i, y2) : Next For i As Integer = x2 To x1 Step –20 : g.DrawLine(p, x2, y1, i, y2) : Next End Sub Private Sub Zeichnung_Paint(sender As Object, ByVal e As PaintEventArgs) _ Handles Zeichnung.Paint Me.DrawGraphic(e.Graphics, _ 0, 0, Zeichnung.ClientSize.Width, Zeichnung.ClientSize.Height) End Sub Private WithEvents doc As New PrintDocument() Private Sub Druck(sender As Object, e As PrintPageEventArgs) _ Handles doc.PrintPage Me.DrawGraphic(e.Graphics, e.MarginBounds.X, e.MarginBounds.Y, _ e.MarginBounds.Width + e.MarginBounds.X, _ e.MarginBounds.Height + e.MarginBounds.Y) End Sub Private Sub Aus(sender As Object, e As EventArgs) Handles Drucken.Click doc.Print() End Sub End Class
Die Routine DrawGraphic übernimmt die Ausgabe der Grafik. Diese Methode wird aus dem Paint-Ereignis der PictureBox und dem PrintPage-Ereignis des PrintDocument-Objekts aufgerufen. Damit die grafischen Methoden auch beim richtigen Empfänger, also dem Bildschirm oder dem Drucker, landen, muss die benutzerdefinierte Methode eine Referenz auf das Graphics-Objekt entgegennehmen, auf das die Grafikroutinen aufgerufen werden.
Für die Ausgabe der Linien in DrawGraphic müssen der obere linke und der untere rechte Eckpunkt festgelegt werden. Auf dem Bildschirm werden die beiden Punkte durch (0, 0) und (pictureBox.ClientSize.Width, pictureBox.ClientSize.Height) beschrieben. Damit keine Linie des Ausdrucks abgeschnitten wird, habe ich die Eckpunkte des Ausdrucks mit den Eigenschaften X und Y von MarginBounds festgelegt.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.