Das Ausdrucken von Bildfeldern und Formularen
Sobald es darum geht, Bitmaps zu drucken, stößt man bei Visual Basic normalerweise (und leider völlig unnötig) schnell an die Grenzen des Machbaren. Weder ein Formular, noch ein Bildfeld sind in der Lage, ihren Inhalt auf dem Drucker auszugeben (die PrintForm-Methode ist nur in den allerseltensten Fällen brauchbar). Wer eine solche Möglichkeit sucht, muß entweder auf Zusatzsteuerelement oder auf die Windows-API ausweichen.
Warum ein Formular über keine PrintForm-Methode verfügt, die ihrem Namen gerecht wird und wahlweise die Innenfläche eine Formulars oder das komplette Fenster in einer hochauflösenden Qualität ausgibt, kann nur Anlaß für Spekulationen sein. Doch was wäre das Leben ohne Herausforderungen? Außerdem winkt am Ende ein allgemeines Modul (das war noch nicht perfekt ist), das in eine Klasse verpackt und dann (theoretisch jedenfalls) beliebig oft umgesetzt werden kann.
Etwas Theorie
Warum kann man den Inhalt eines Formulars nicht über einen einfachen BitBlt-Aufruf in den Gerätekontext des Druckers kopieren? Ganz einfach, weil die Bitmap, die über die hDC-Eigenschaft eines Bildfeldes adressiert wird, in einem geräteabhängigen Format vorliegt, auf dem Drucker aber eine Bitmap im geräteunabhängigen Format ausgegeben werden muß. Kurz zur Erinnerung, eine Bitmap in einem geräteunabhängigen Format besteht aus einer allgemeinen Struktur vom Typ BITMAPINFO, der ein Kopf vom Typ BITMAPINFOHEADER vorausgeht, und in der vorallem auch die Paletteninformationen enthalten sind. Das Ausdrucken einer Bitmap besteht daher, stark vereinfacht natürlich, aus folgenden Schritten:
Übung
Das Beispielprogramm der folgenden Übung gibt den Inhalt eines Formulars (wahlweise mit oder ohne Umrandung) auf dem Drucker aus. Da das Beispielprogramm sehr einfach aufgebaut ist, wird auf eine Beschreibung der Oberfläche und der Umsetzung verzichtet und statt dessen die einzelnen Schritte, die zum Ausdruck des Formulars führen, beschrieben.
So funktioniert es im Detail
Schritt 1
Der Kern des Beispielprogramms besteht aus der Funktion FormEinfrieren, die den Inhalt eines Formulars in ein Picture-Objekt überträgt und einen Bezug auf dieses Objekt zurückgibt:
Public Function FormEinfrieren(ByVal hWndQuelle As Long, ByVal QuelleLinks As Long, _
ByVal QuelleOben As Long, ByVal QuelleBreite As Long, ByVal QuelleHöhe As Long) As Picture
Neben der Bezugsnummer werden auch die Abmessungen des Formulars übergeben.
Schritt 2
Die Bezugsnummer des Formulars wird über die GetDC-API-Funktion in eine Bezugsnummer auf den Gerätekontext der Innenfläche des Formulars umgewandelt:
hDCQuelle = GetDC(hWndQuelle)
Soll statt dessen das gesamte Fenster ausgegeben werden, muß statt dessen die API-Funktion GetWindowDC zum Einsatz kommen.
Schritt 3
Über die Bezugsnummer auf den Gerätekontext wird im Arbeitsspeicher ein kompatibler Speicherkontext angelegt:
hDCSpeicher = CreateCompatibleDC(hDCQuelle)
Dieser Speicherkontext besitzt die gleiche »Struktur« wie der Gerätekontext de Formulars bzw. ihrer Innenfläche.
Schritt 4
Der Speicherkontext dient als Grundlage für eine gerätekompatible Bitmap, die über die CreateCompatibleBitmap-API-Funktion angelegt wird, und deren Größe, der Innenfläche des Formulars entspricht:
hBmp = CreateCompatibleBitmap(hDCQuelle, QuelleBreite, QuelleHöhe)
Nach dem eine Bezugsnummer auf den Speicherkontext da ist, wird die Bitmap im Speicher über die SelectObject-API-Funktion in den Speicherkontext gesetzt:
hBmpPrev = SelectObject(hDCSpeicher, hBmp)
Der Rückgabewert ist eine Bezugsnummer auf die sich nun im Speicherkontext hDCSpeicher befindliche Bitmap, die aber noch keinen Inhalt besitzt.
Schritt 5
Damit beim Kopieren der Formularinnenfläche in den Speicherkontext alle Farben erhalten bleiben, muß geprüft werden, ob die Bitmap eine Palette besitzt. Diese Aufgabe übernimmt die Universalfunktion GetDeviceCaps, die, wenn sie mit dem Argument RASTERCAPS aufgerufen wird, die Anzahl der Farben in der Bitmap zurückgibt:
RasterCapsScrn = GetDeviceCaps(hDCQuelle, RASTERCAPS)
Eine Und-Verküpfung des Rückgabewertes mit der Konstanten RC_PALETTE ergibt die Anzahl der Farben innerhalb einer Paletten. Ist der Wert 0, gibt es keine Palette:
PaletteVorhanden = RasterCapsScrn And RC_PALETTE
Über den Aufruf mit dem Argument SIZEPALETTE wird die Größe der Palette ermittelt:
GrößePaletteScrn = GetDeviceCaps(hDCQuelle, SIZEPALETTE)
Schritt 6
Mit den erhaltenen Informationen wird geprüft, ob eine Palette vorhanden ist:
If PaletteVorhanden And (GrößePaletteScrn = 256) Then
Ist dies der Fall, werden folgende Schritte zum Anlegen einer logischen Palette durchgeführt. Zur Erinnerung, eine logische Palette ist nichts anderes als eine Variable mit einem benutzerdefinierten Datentyp, der dem Aufbau einer Palette im Arbeitsspeicher entspricht. Im wesentlich geht es darum, in eine Strukturvariable vom Typ LOGPALETTE eine Reihe von Werten einzutragen:
Dim LogischePalette As LOGPALETTE
LogischePalette.palVersion = &H300
LogischePalette.palNumEntries = 256
Nach der obligatorischen Initialisierung wird die logische Palette mit den Farbeinträgen der Quellpalette gefüllt.
Zurück = GetSystemPaletteEntries(hDCQuelle, 0, 256, LogischePalette.palPalEntry(0))
Danach wird eine Bezugsnummer zu dieser logischen Palette angelegt.
hPal = CreatePalette(LogischePalette)
Anschließend wird sie in den Speicher über die SelectPalette-API-Funktion abgelegt, wobei die Bezugsnummer der alten Palette über den Rückgabewert (dieser Wert wird später für das »Aufräumen« benötigt) zur Verfügung steht:
hPalPrev = SelectPalette(hDCSpeicher, hPal, 0)
Damit die neue Palette wirksam werden kann, muß sie aktiviert werden:
Zurück = RealizePalette(hDCSpeicher)
Das war alles, jetzt kann die in Schritt 6 eingeleitete If-Abfrage über eine End If-Anweisung beendet werden.
Schritt 7
Nachdem alle Vorbereitungen abgeschlossen wurden, wird der Inhalt des Formulars (Quelle) über die BitBlt-Funktion im Verhältnis 1:1 in den Speicherkontext kopiert:
RetVal = BitBlt(hDCSpeicher, 0, 0, QuelleBreite, QuelleHöhe, hDCQuelle, QuelleLinks, QuelleOben, vbSrcCopy)
Schritt 8
Damit ist die meiste Arbeit erledigt. Was jetzt noch folgt, sind eine Reihe von »Aufräumarbeiten«:
hBmp = SelectObject(hDCSpeicher, hBmpPrev)
Als erstes wird der guten Ordnung halber der alte Inhalt des Speicherkontextes wiederhergestellt, auch wenn dieser zuvor gar nicht existierte. Falls eine logische Palette angelegt werden mußte, wird auch diese aus dem Gerätekontext entfernt:
If PaletteVorhanden And (GrößePaletteScrn = 256) Then
hPal = SelectPalette(hDCSpeicher, hPalPrev, 0)
End If
Schritt 9
Übe die API-Funktionen DeleteDC und ReleaseDC wird der Gerätekontext im Speicher entfernt und der Gerätekontext der Quelle wieder gelöscht.
RetVal = DeleteDC(hDCSpeicher)
RetVal = ReleaseDC(hWndQuelle, hDCQuelle)
Schritt 10
Zu diesem Zeitpunkt verfügen wir über eine Bezugsnummer auf eine gerätekompatible Kopie des Formularinhalts, inklusive Palette, im Arbeitsspeicher. Nach dem alten Verfahren ohne Picture-Objekt müßte man als nächstes eine geräteunabhängige Bitmap im Arbeitsspeicher anlegen und die Bitmap über die API-Funktionen GetDIBits und StretchDIBits dort hineinkopieren. Diese Schritte fallen zum Glück weg, denn alles, was noch zu tun ist ist es, über den Aufruf der API-Funktion OleCreatePictureIndirect ein Picture-Objekt im Arbeitsspeicher anzulegen.
Schritt 11
Da das Anlegen eines Picture-Objekts auch von anderen Programmkomponenten gebraucht werden könnte, wird das Ganze wieder in eine Funktion verpackt, der eine Bezugsnummer auf die Bitmap und auf ihre Palette übergeben werden:
Set FormEinfrieren = ErzeugePictureObjekt(hBmp, hPal)
Diese Anweisung sorgt dafür, daß die in Schritt 1 angelegte Funktion als Rückgabewert die Bezugsnummer eines Picture-Objekts übergeben kann.
Schritt 12
In der Funktion ErzeugePictureObject werden als Vorbereitung zwei Strukturen angelegt, die die OleCreatePictureIndirect-API-Funktion benötigt. Da diese OLE-spezifisch sind, werden sie in diesem Zusammenhang als »magische Ingredenzien« betrachtet und nicht weiter diskutiert:
With IID_IDispatch
.Data1 = &H20400
.data4(0) = &HC0
.data4(7) = &H46
End With
With Pic
.Size = Len(Pic)
.Type = vbPicTypeBitmap
.hBmp = hBmp
.hPal = hPal
End With
Schritt 13
Liegen die Strukturen vor, sorgt der Aufruf der OLE-Funktion dafür, daß die Bezugsnummer auf ein neues Picture-Objekt zurückgegeben wird:
RetVal = OleCreatePictureIndirect(Pic, IID_IDispatch, 1, IPic)
Die Bezugsnummer Ipic wird als Rückgabewert an die aufrufende Funktion ErzeugePictureObjekt zurückgegeben:
Set ErzeugePictureObjekt = IPic
Schritt 14
Liegt das Picture-Objekt erst einmal vor, ist der Rest wirklich sehr einfach. Über die PaintPicture-Methode kann die Bitmap frei skalierbar wahlweise auf dem Drucker oder in einem Bildfeld ausgegeben werden. Zuvor wird man eine Reihe von Einstellungen für den Drucker vornehmen, die unter anderem dafür sorgen, daß die Bitmap im richtigen Seitenverhältnis ausgegeben wird.
Schritt 15
Ist die Skalierung vollendet, kann das Picture-Objekt an den Drucker mit der PrintPicture-Methode richtig skaliert ausgegeben werden.
Printer.PaintPicture PictureObjekt, 0, 0, BreitePictureObjektAnDrucker, HöhePictureObjektAnDrucker
Am Schluß wird noch mit der EndDoc-Methode das Ende des Druckvorgangs signalisiert und der Druckerauftrag an den Drucker geschickt.
Drucker.EndDoc
Mit etwas mehr Aufwand, kann ein Formularinhalt in der Ausgabefläche des Printer-Objekts an einer anderen Stelle ausgegeben werden. Damit lassen sich z.B. mehrere Formulare nebeneinander oder untereinander ausgeben.
* * * E N D E * * *