Das Druckframework
von Herfried K. Wagner
1. Das Druckframework
Neu
Seit dem 05.12.02 gibt es die neue Kategorie Die Grundlagen von Visual Basic .NET. In dieser werden in unregelmäßigen Abständen Kapitel aus dem Tutorial Die Grundlagen von Visual Basic .NET von Herfried K. Wagner und Markus Palme erscheinen.
1.1 Das Druckmodell von .NET
Klassen für das Drucken
Die Klassen zum Drucken unter .NET sind als Teil von GDI+ in der System.Drawing.Printing-Namespace abgelegt. Die wichtigsten davon sind die Klassen PrintDocument, die das Dokument enthält, das ausgedruckt werden soll und PrinterSettings, über die Informationen über Drucker und Einstellungen ermittelt und die Druckeinstellungen geändert werden können. Das Zusammenspielen der Klassen bei einem Druckvorgang und dem Einstellen der Druckoptionen ist so komplex, dass es am Besten an einem Beispiel erklärt werden kann. In den folgenden Codeausschnitten ist die Printing-Namespace von System.Drawing nicht importiert, damit besser sichtbar ist, aus welchen Namespaces die Befehle stammen.
Steuerelemente für das Drucken
Bei der Beschreibung der Steuerelemente wurden die Steuerelemente PrintDialog, PrintPreviewDialog, PrintPreviewControl, PrintDocument und PageSetupDialog vorgestellt. Zur Erinnerung wird hier die Funktion der jeweiligen Steuerelemente bzw. Klassen nocheinmal zusammengefasst:
- PrintDialog
- Dieses Steuerelement dient zur Anzeige des Dialogs, der zur Auswahl des Druckers und zu dessen Konfiguration verwendet wird.
- PrintPreviewDialog
- Mit diesem Steuerelement kann eine grafische Vorschau des Dokuments erfolgen, das ausgedruckt werden kann. Diese wird in einem eigenen Dialog angezeigt, über den auch der Ausdruck vom Benutzer gestartet werden kann.
- PrintPreviewControl
- Bei diesem Steuerelement handelt es sich um den Bruder des PrintPreviewDialog. Es kann direkt auf einem Formular verwendet werden und dient zur Einbettung der Druckvorschau in die Formulare, die der Programmierer erstellt.
- PrintDocument
- Dies ist eigentlich kein Steuerelement sondern eine herkömmliche Klasse, die ein Dokument, das in der Vorschau angezeigt oder ausgedruckt werden kann, darstellt.
- PageSetupDialog
- Über diesen Dialog können Einstellungen getroffen werden, wie der Ausdruck erfolgen soll. Man kann den Benutzer das Papierformat und verschiedene andere Optionen in diesem Dialog festlegen lassen.
In den folgenden Beispielen werden wir nicht alle dieser Steuerelemente verwenden, so belassen wir es mit einem Beispiel zum PrintPreviewDialog- Steuerelement stellvertretend für das PrintPreviewControl.
1.2 Erstellen eines Druckdokuments
Der Ablauf des Druckvorgangs
Ein Druckdokument ist unter .NET eine Instanz der Klasse Printing.PrintDocument. Es enthält Verweise auf Objekte, die Informationen über den Drucker und das verwendete Seitenlayout enthalten und den Titel des Dokuments. Ausserdem ist diese Klasse für den Ausdruckvorgang zuständig. Im Gegensatz zu VB6 läuft der Druckvorgang nun nicht mehr durch den einfachen Aufruf von Methoden ab, sondern erfordert, dass ein Ereignishandler für das Ereignis PrintPage der Klasse PrintDocument implementiert wird. Der Vorgang des Ausdruckens erfolgt in den beiden folgenden Schritten:
- Aufruf der Methode Print des PrintDocument-Objekts zum Starten des Druckvorgangs.
- VB .NET löst daraufhin das Ereignis PrintPage des PrintDocument-Objekts aus, in dem die Seiten gedruckt werden. Innerhalb dieses Ereignisses werden die Druckbefehle zum Gestalten der Seiten abgelegt. Der Parameter e des Ereignisses besitzt die boolesche Eigenschaft HasMorePages, die angibt, ob noch weitere Seiten gedruckt werden sollen. Wird der Wert der Eigenschaft auf True gesetzt, erfolgt ein erneuter Aufruf des Handlers. Auf diese Weise können mehrseitige Dokumente gedruckt werden.
Im Folgenden sei m_pd des PrintDocument-Objekt, das unser Dokument repräsentiert. Im folgenden Code wird zuerst ein Handler für das PrintPage-Ereignis registriert, dann kann der Ausdruck erfolgen.
Private m_pd As New Printing.PrintDocument() Private m_intCurrentPage As Integer Private Sub FMain_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs _ ) Handles MyBase.Load AddPrinterNamesToComboBox(cboPrinters) With cboPrinters If .Items.Count > 0 Then .SelectedIndex = 0 Else MessageBox.Show( _ "No printers installed, quitting!", _ "Error", _ MessageBoxButtons.OK, _ MessageBoxIcon.Exclamation _ ) Me.Close() End If End With m_pd.DocumentName = "Unser erstes Dokument" AddHandler m_pd.PrintPage, AddressOf m_pd_PrintPage m_intCurrentPage = 0 End Sub
Zusammenstellen des Dokumentinhalts
In unserem Beispiel wollen wir ein zweiseitiges Dokument drucken, das ein grafisches Element sowie die Seitennummer als Text enthält. Da das Ereignis PrintPage für jede Seite einmal ausgelöst wird, muss gespeichert werden, welche Seite gerade gedruckt wird, was im Beispiel über ein privates Mitglied m_intCurrentPage getan wird, das die Nummer der zu druckenden Seite enthält. Innerhalb des Ereignishandlers wird diese Variable um eins erhöht, wird die letzte Seite erreicht, setzen wir die Variable zurück und teilen VB .NET durch Setzen der HasMorePages-Eigenschaft auf False mit, das keine weiteren Seiten mehr folgen.
Private Sub m_pd_PrintPage( _ ByVal sender As System.Object, _ ByVal e As System.Drawing.Printing.PrintPageEventArgs _ ) m_intCurrentPage += 1 Select Case m_intCurrentPage ' Drucken der ersten Seite. Case 1 ' Zeichnen eines elliptischen Bereichs über die ' gesamte Seite (ohne Randabstände). e.Graphics.FillEllipse( _ New Drawing2D.HatchBrush( _ Drawing.Drawing2D.HatchStyle.Percent10, _ Color.Red, _ Color.White _ ), _ e.MarginBounds _ ) ' Text genau in der Mitte der Seite ausgeben. Je nach ' Randabständen muss das nicht unbedingt genau in der ' Mitte der Ellipse sein! Dim strText As String = "Das ist die Seite 1" Dim fntFont As New Font("Arial", 18) e.Graphics.DrawString( _ strText, _ fntFont, _ New SolidBrush(Color.Blue), _ CSng( _ ( _ e.PageBounds.Width - _ e.Graphics.MeasureString( _ strText, _ fntFont _ ).Width _ ) * 0.5 _ ), _ CSng(200) _ ) ' Es folgen noch weitere Seiten. e.HasMorePages = True ' Drucken der zweiten Seite. Case 2 ' Seitennummer im linken oberen Eck ausgeben. e.Graphics.DrawString( _ "Seite 2", _ New Font("Times New Roman", 12), _ New SolidBrush(Color.Black), _ e.MarginBounds.Left, _ e.MarginBounds.Top _ ) ' Das war die letzte Seite. e.HasMorePages = False ' Seitenzähler wieder zurücksetzen. m_intCurrentPage = 0 End Select End Sub
Der Druck wird schliesslich über m_pd.Print() gestartet. Der Ereignishandler für das PrintPage-Ereignis enthält einen Parameter e vom Datentyp PrintPageEventArgs. Dieser Datentyp enthält noch einige andere interessante Mitglieder:
- Cancel
- Hier kann angegeben werden, ob der Druckauftrag abgebrochen werden soll.
- MarginBounds
- Gibt ein Rechteck an oder zurück, das die Randeinstellungen für die aktuelle Seite enthält. Das Setzen dieses Rechtecks kann auch über die Eigenschaft Margins des PageSetting-Objekts durchgeführt werden.
- PageBounds
- Gibt ein Rechteck an oder zurück, das die Grösse der aktuellen Seite enthält.
- PageSettings
- Dieses Mitglied enthält die Einstellungen zur Seite, auf der der Ausdruck erfolgen soll: Landscape gibt an oder zurück, wie die Seite, auf der der Ausdruck erfolgen soll, ausgerichtet werden soll, Color gibt an oder zurück, ob der Ausdruck in Farbe erfolgen soll und PaperSize enthält die Masse des Papiers, auf das gedruckt werden soll.
- PrinterSettings
- Hier sind die Einstellungen des Druckers, also welcher Drucker verwendet werden soll und wie er konfiguriert ist, enthalten.
- Graphics
- Dieses Mitglied enthält einen Verweis auf ein Graphics-Objekt, über das grafische Objekte auf verschiedenen Geräten ausgegeben werden können. Über das Mitglied PageUnit dieser Klasse kann die Masseinheit eingestellt werden, die für die Koordinaten, die bei Methodenaufrufen angegeben werden, benutzt werden; man kann u.a. zwischen Pixel, Millimeter und Zoll wählen. Im folgenden Beispiel soll der Text "Hello World!" vertikal zentriert am oberen Rand des Dokuments ausgegeben werden. Der Code zum Zentrieren ist etwas eleganter als der Code aus dem vorigen Listing:
Dim sf As New StringFormat() ' Der Text soll zentriert ausgegeben werden. sf.Alignment = StringAlignment.Center ' String ausgeben. e.Graphics.DrawString( _ "Hello World!", _ New Font("Times New Roman", 18), _ New SolidBrush(Color.Green), _ e.MarginBounds.Left + e.MarginBounds.Width * 0.5, _ e.MarginBounds.Top, _ sf _ )
1.3 Einstellen der Druckoptionen
Die Auswahl des Druckers
Benutzerdefinierte Auswahl
Um an eine Liste der Namen aller am System installierten Drucker zu gelangen, nützt man die Aufzählung InstalledPrinters der Klasse PrinterSettings. Folgender Code fügt alle Namen einer ComboBox hinzu, damit der Benutzer einen der Drucker wählen kann:
Private Sub AddPrinterNamesToBomboBox( _ ByRef cboComboBox As ComboBox _ ) Dim s As String For Each s In Printing.PrinterSettings.InstalledPrinters cboComboBox.Items.Add(s) Next s End Sub Private Sub SelectPrinter( _ ByRef cboComboBox As ComboBox, _ ByRef strPrinterName As String _ ) Dim i As Integer For i = 0 To cboComboBox.Items.Count - 1 If cboComboBox.Items(i) = strPrinterName Then cboComboBox.SelectedIndex = i Exit For End If Next i End Sub Private Sub cboPrinters_SelectedIndexChanged( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs _ ) Handles cboPrinters.SelectedIndexChanged ' Bei einer Änderung des ausgewählten Druckers wird der ' Drucker, der zum Drucken des Dokuments verwendet werden ' soll, auf die Auswahl des Benutzers angepasst. If cboPrinters.SelectedIndex <> -1 Then m_pd.PrinterSettings.PrinterName = cboPrinters.Text End If End Sub
Man kann die Auswahl des Druckers auch direkt mit dem PrintDialog-Steuerelement durchführen lassen, jedoch empfiehlt es sich, dem Benutzer diese Möglichkeit auch direkt am Formular zu geben. Bevor gedruckt wird, darf man nicht vergessen, beim zu druckenden PrintDocument-Objekt, in unserem Beispiel m_pd, einen Verweis auf den ausgewählten Drucker zu erstellen.
Auswahl über das PrintDialog-Steuerelement
Der folgende Code instanziert ein PrintDialog-Steuerelement in die Instanzvariable pdlg und weist diesem das Dokument aus m_pd zu. Danach wird der bisher ausgewählte Drucker im PrintDialog ausgewählt, damit der Benutzer nicht verunsichert wird, wenn dort ein anderer Drucker markiert ist, als der, den er am Formular eingestellt hat. Dann wird der Dialog angezeigt, sofern der Benutzer die Einstellungen, die er im Dialog getroffen hat, nicht verwirft, werden diese auf unsere Objekte übertragen, also es wird die Auswahl in der ComboBox angepasst. Die Zuweisung des ausgewählten Druckers können wir uns ersparen, da diese bereits im SelectedIndexChanged-Ereignis der ComboBox cboPrinters geschieht (dieses Ereignis wird automatisch ausgelöst, wenn durch den Programmcode ein anderes Element ausgewählt wird).
Private Sub btnChoosePrinter_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs _ ) Handles btnChoosePrinter.Click Dim pdlg As New PrintDialog() ' Dokument an Printerdialog weiterreichen. With pdlg .Document = m_pd .PrinterSettings = m_pd.PrinterSettings .AllowPrintToFile = False If .ShowDialog(Me) = DialogResult.OK Then ' Die Einstellungen in unserem Formular werden nun ' angepasst... SelectPrinter( _ cboPrinters, _ .PrinterSettings.PrinterName _ ) End If End With pdlg = Nothing End Sub
Anpassen der Seiteneinstellungen
Die Einstellungen für den Randabstand und das Papierformat werden in der PageSettings-Eigenschaft des PrintDocument-Objekts gespeichert. Über den PageSetupDialog kann dem Benutzer die Möglichkeit gegeben werden, diese Einstellungen zu verändern. Damit die Änderungen auch wirklich übernommen werden, müssen wieder die PrinterSettings und nun auch die PageSettings des PrintDocument-Objekts mit dem PrintSetupDialog-Steuerelement verbunden werden. Danach erfolgt wiederum die Anzeige des Dialogs und bei Bestätigung der Eingaben durch den Benutzer die Aktualisierung der Anzeige in der ComboBox. Zusätzlich müssen aber die eingestellten Masse für die Randabstände, die in PageSettings.Margins abgelegt sind, neu berechnet werden. Warum das erforderlich ist, kann dem kommentierten Code des nächsten Listings entnommen werden:
Private Sub btnPageSetup_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs _ ) Handles btnPageSetup.Click Dim psdlg As New PageSetupDialog() With psdlg .PrinterSettings = m_pd.PrinterSettings .PageSettings = m_pd.DefaultPageSettings If .ShowDialog(Me) = DialogResult.OK Then ' Hier wird ein Fehler (?!) ausgebügelt: VB .NET ' konvertiert anscheinend alle Werte von Zoll in ' Millimeter, da (vermutlich) im englischen Dialog die ' Werte in Inch eingegeben werden. Allerdings ist der ' Umrechnungsfaktor nicht genau Zoll:Millimeter, ' sondern etwas mehr, sodass beim Wert 10 bei erneutem ' Aufruf 9,9 in der TextBox steht (Umrechnungsfaktor ' 0,254). .PageSettings.Margins = _ Printing.PrinterUnitConvert.Convert( _ .PageSettings.Margins, _ Printing.PrinterUnit.ThousandthsOfAnInch, _ Printing.PrinterUnit.HundredthsOfAMillimeter _ ) ' Die Einstellungen in unserem Formular werden nun ' angepasst... SelectPrinter( _ cboPrinters, _ .PrinterSettings.PrinterName _ ) End If End With psdlg = Nothing End Sub
Erstellen einer Druckvorschau
Eine Druckvorschau kann über das PrintPreviewDialog- oder das PrintPreviewControl- Steuerelement angezeigt werden. Durch das Einbinden einer Möglichkeit, Dokumente vor dem Ausdruck zu betrachten, kann der Benutzer in seiner Entscheidung erleichtert werden, einen Druckauftrag zu geben oder nicht. Daher sollte in jeder Anwendung, in der formatierte Dokumente ausgegeben werden können, eine Druckvorschau integriert werden. Im folgenden Beispiel wird ein PrintPreviewDialog-Streuerelement instanziert und mit dem PrintDocument-Objekt verknüpft. Die Anzeige erfolgt wiederum über die Methode ShowDialog. Dabei ist zu beachten, dass zum Erstellen der Druckvorschau das Ereignis PrintPage des PrintDocument-Objekts gefeuert wird, wie dies auch beim normalen Ausdruckvorgang der Fall ist. Aus diesem Grund muss man aufpassen, dass der Seitenzähler immer korrekt zurückgesetzt wird. Ein Aufruf von ShowDialog entspricht also ungefähr dem Aufruf der Methode Print von PrintDocument und dem gleichzeitigen Anzeigen des Vorschaudialogs.
Private Sub btnPreview_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs _ ) Handles btnPreview.Click ' Seitenzähler initialisieren. m_intCurrentPage = 0 ' Vorschaudialog erstellen und anzeigen. Dim ppdld As New PrintPreviewDialog() With ppdld ' Der Druckvorschau das Dokument zuweisen. .Document = m_pd ' Die Druckvorschau soll maximiert gezeigt werden. .WindowState = FormWindowState.Maximized ' Druckvorschau anzeigen. .ShowDialog(Me) End With ppdld = Nothing End Sub
Wie bereits erwähnt, kann die Druckvorschau auch über das PrintPreviewControl- Steuerelement erfolgen, dessen Verwendung sich nicht stark vom PrintPreviewDialog-Steuerelement unterscheidet. Die Druckvorschau besitzt einen Fehler, der die Verwendung in kommerziellen Produkten nahezu unmöglich macht. Bei der Vorschau wird nämlich der Ursprung des koordinatensystems in die linke obere Ecke des Blattes gesetzt, im Gegensatz dazu setzen die Drucker diese aber erst im druckbaren Bereich an. Es werden also die physikalischen Ränder des Druckers nicht berücksichtigt. Ebenso ist im .NET-Framework keine Methode enthalten, diese Ränder zu ermitteln.
Ihre Meinung
Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.