Die vom Anwendungs-Assistenten erstellten SDI- und MDI-Rahmenprogramme bieten per Vorgabe den Aufhänger für das Drucken und die Druckvorschau (auch als Seitenansicht bezeichnet). Man kann das deaktivieren, wenn man im Schritt 4 des MFC-Anwendungs-Assistenten das Kontrollkästchen Drucken und Druckvorschau ausschaltet. Im allgemeinen ist es aber sinnvoll, diese Befehle in jedes Projekt einzubinden. Außerdem erfordern sie nur einen geringen Overhead. Die eigentliche Knochenarbeit beim Drucken erledigt der Gerätekontext und das GDI (Graphics Device Interface - grafische Geräteschnittstelle). Das Programmgerüst stellt für das Drukken der Dokumentseite einen Gerätekontext bereit, den man beinahe wie einen normalen Fenstergerätekontext verwenden kann.
Das Gerüst für eine SDI-Anwendung unterstützt das Drucken von Bildern aus Ansichten, wobei die Informationen aus dem Dokument stammen. Da diese Informationen bereits in den Ansichten der Anwendung angezeigt werden, lassen sie sich gegebenenfalls drucken, indem man die Ansicht modifiziert und Druckfunktionen hinzufügt.
Das Gerüst ruft Ihre Funktion OnDraw
in der Ansicht auf, um ein Bild anzuzeigen. Es
gibt eine korrespondierende Funktion OnPrint
, die das Gerüst aufruft, damit Ihre Ansicht
die zu druckenden Informationen behandeln kann. Häufig benutzt man dazu den
gleichen Zeichencode, wie er in der Funktion OnDraw
vorhanden ist. In diesem Fall
brauchen Sie die Funktion OnPrint
eigentlich gar nicht implementieren. Das Gerüst
erledigt das per Voreinstellung in der Basisklasse CView
und ruft OnDraw
auf. Der Drukker
wird dann genauso behandelt wie der Bildschirm, da die Klasse einen Gerätekontext
für die zu verwendenden Zeichenfunktionen als Ersatz für den üblichen Bildschirmgerätekontext
bietet. Ihre Funktion OnDraw
kann bestimmen, ob es sich bei dem
an die Funktion übergebenen Gerätekontext um einen Bildschirm- oder Druckergerätekontext
handelt. Da aber die Zeichenfunktionen in beiden in der gleichen Weise arbeiten,
ist nicht einmal diese Kenntnis erforderlich.
Die vom Standardgerüst bereitgestellten Druckfunktionen können Sie untersuchen, indem
Sie eine normale SDI-Anwendung mit dem Anwendungs-Assistenten erstellen.
Lassen Sie das Kontrollkästchen Drucken und Druckvorschau in Schritt 4 des Anwendungs-Assistenten
eingeschaltet (d.h., Sie können bereits im Schritt 1 auf Fertigstellen
klicken), und nennen Sie das Projekt PrintIt
.
Die Standardunterstützung für das Drucken und die Druckvorschau ist nur in SDI- und MDI-Anwendungen verfügbar. Dialogfeldbasierende Anwendungen müssen ihre eigene Druckunterstützung implementieren. |
Zuerst brauchen Sie eine Grafik, die auszudrucken ist. In der Funktion OnDraw
der
Klasse CPrintItView
(eine normale CView
-Klasse) können Sie eine Testanzeige erstellen,
wie es Listing C.1 zeigt. Das Testbild selbst ist nicht allzu wichtig, erlaubt es aber,
die Ausgaben auf dem Drucker und auf dem Bildschirm miteinander zu vergleichen.
Listing C.1: Die Datei PrintItView.cpp - ein Druckbeispiel in OnDraw erzeugen
1: void CPrintItView::OnDraw(CDC* pDC)
2: {
3: CPrintItDoc* pDoc = GetDocument();
4: ASSERT_VALID(pDoc);
5:
6: // ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten
hinzufügen
7:
8: // Metrischen Abbildungsmodus einstellen
9: pDC->SetMapMode(MM_LOMETRIC);
10:
11: // Schrift mit Höhe 2,2 cm deklarieren und erzeugen
12: CFont fnBig;
13: fnBig.CreateFont(220,0,0,0,FW_HEAVY,FALSE,FALSE,0,
14: ANSI_CHARSET,OUT_DEFAULT_PRECIS,
15: CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
16: FF_SWISS+VARIABLE_PITCH,"Arial");
17:
18: // Neue Schrift auswählen, originale speichern
19: CFont* pOldFont = pDC->SelectObject(&fnBig);
20:
21: // Rechteck im Client-Bereich deklarieren
22: CRect rcClient;
23: GetClientRect(&rcClient);
24:
25: // In logische Einheiten konvertieren
26: pDC->DPtoLP(&rcClient);
27:
28: // Variablen zum Zeichnen einrichten
29: const int nPoints = 50;
30: int xm = rcClient.Width();
31: int ym = rcClient.Height();
32: double dAspW = xm/(double)nPoints;
33: double dAspH = ym/(double)nPoints;
34:
35: // Schwarzen Stift auswählen
36: CPen* pOldPen =
37: (CPen*)pDC->SelectStockObject(BLACK_PEN);
38:
39: // Linien zeichnen
40: for(int i=0;i<nPoints;i++)
41: {
42: int xo = (int)(i * dAspW);
43: int yo = (int)(i * dAspH);
44:
45: pDC->MoveTo(xo,0);
46: pDC->LineTo(xm,yo);
47: pDC->LineTo(xm-xo,ym);
48: pDC->LineTo(0,ym-yo);
49: pDC->LineTo(xo,0);
50: }
51:
52: // Alten Stift wiederherstellen
53: pDC->SelectObject(pOldPen);
54:
55: // Vordergrundtext zeichnen
56: pDC->SetTextAlign(TA_CENTER+TA_BASELINE);
57: pDC->SetBkMode(TRANSPARENT);
58:
59: // Grauen Text einstellen
60: pDC->SetTextColor(RGB(64,64,64));
61: pDC->TextOut(xm/2,ym/2,"Probedruck");
62:
63: // Alte Schrift wiederherstellen
64: pDC->SelectObject(pOldFont);
65: }
Abbildung C.1:
Testausgabe einer Grafik mit PrintIt in einem Fenster
Obwohl diese OnDraw
-Funktion eine Menge Code enthält, ist keine Zeile überflüssig.
Der Code zeichnet die Linien innerhalb des Client-Bereichs und schreibt einen Probetext
in die Mitte. Beachten Sie, daß Zeile 9 den Abbildungsmodus auf MM_LOMETRIC
setzt. Damit werden die logischen Koordinaten in Zehntel Millimeter übersetzt.
Zeile 13 erzeugt eine 2,2 cm hohe Schrift, die Zeile 61 zum Zeichnen des Probetextes verwendet. Die Zeilen 40 bis 50 zeichnen auf der Basis der Koordinaten des Client- Bereichs den Rahmen für das kunstvolle Muster. Die Details können Sie selbst studieren, hier geht es vor allem um den Druckvorgang.
Wenn Sie das Programm erstellen und ausführen, nachdem Sie die Zeilen in die Funktion
OnDraw
gemäß Listing C.1 eingegeben haben, sollte im Anwendungsfenster eine
Grafik wie in Abbildung C.1 zu sehen sein.
Die große Frage lautet nun: Was ist zu tun, um das Bild zu drucken? Überraschenderweise
nur wenig - das Standardgerüst versucht bereits, das Bild zu drucken, und übergibt
dazu der Funktion OnDraw
den Gerätekontext des Druckers anstelle des Fensters.
Wenn Sie das Menü Datei der Anwendung PrintIt
öffnen und den Befehl Seitenansicht
wählen, erscheint eine verkleinerte Darstellung des Bildes in der oberen linken
Ecke, auch wenn die Schrift für die Linienzeichnung zu groß ist. Das liegt aber nicht
am Programmgerüst - dieses tut sein bestes, um das Fenster wiederzugeben. Dem Gerätekontext
wurden die falschen Koordinaten übergeben. Das Problem liegt in Zeile
23 bei der Funktion GetClientRect
.
Beachten Sie, daß die Funktion GetClientRect
ein Element der Ansicht und nicht des
Gerätekontextes ist. Für das Fenster funktioniert das ausgezeichnet, da der Gerätekontext
die gleiche Größe wie das Fensterrechteck hat. Jetzt geben Sie das Fensterrechteck
an den Druckergerätekontext aus (der im Vergleich zum Fenster klein ist), erzeugen
aber eine Schrift mit einer Höhe von 2,2 cm, die (bedingt durch den
Abbildungsmodus) immer die gleiche Größe hat.
Um das Problem mit den Koordinaten des Client-Bereichs zu lösen, ist das richtige
Rechteck für den Drucker statt für das Fenster zu übergeben. Zum Glück ruft das Programmgerüst
eine virtuelle Funktion auf, die Sie in Ihrer Ansicht überschreiben können,
um alle benötigten Informationen zusammenzutragen. Wie bereits weiter oben
erwähnt, handelt es sich hierbei um die Funktion OnPrint
, die analog zu OnDraw
ist.
Beim Zeichnen in einem Fenster wird OnDraw
aufgerufen; wenn man auf einen Drukker
zeichnet, erfolgt der Aufruf von OnPrint
. Vielleicht fragen Sie sich, wie der Zeichencode
in OnDraw
ausgeführt wird, um die Anzeige der Beispielgrafik zu drucken.
Die Standardimplementierung von OnPrint
in der Klasse CView
ruft einfach OnDraw
auf und übergibt dieser Funktion ihren Druckergerätekontext.
Ihre Funktion OnPrint
muß OnDraw
nicht aufrufen. Sie können OnPrint
überschreiben,
um irgend etwas anderes zu zeichnen, aber viele Anwendungen müssen genau
das ausdrucken können, was der Benutzer sieht.
Um die virtuelle Funktion OnPrint
zu überschreiben, führen Sie die folgenden Schritte
aus:
1. Gehen Sie im Arbeitsbereich auf die Registerkarte Klassen.
2. Klicken Sie auf das oberste Pluszeichen, um die Projektklassen einzublenden.
3. Klicken Sie mit der rechten Maustaste auf die Ansichtsklasse, der Sie die überschriebene
OnPrint
-Funktion hinzufügen möchten (im Beispiel CPrintItView
), um
das Kontextmenü anzuzeigen.
4. Wählen Sie den Befehl Virtuelle Funktion hinzufügen, um das Dialogfeld Überschreiben neuer virtueller Funktionen für Klasse CPrintItView anzuzeigen.
5. In der Liste Neue virtuelle Funktionen sollte die virtuelle Funktion OnPrint
vorhanden
sein.
6. Klicken Sie auf die Schaltfläche Hinzufügen und Bearbeiten, um die virtuelle
Funktion OnPrint
umzugestalten.
Die vorgegebene überschriebene Funktion für OnPrint
sieht folgendermaßen aus:
void CPrintItView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
CView::OnPrint(pDC, pInfo);
}
Als erster Unterschied zur Funktion OnDraw
fällt der zweite Parameter auf, der Zeiger
auf ein CPrintInfo
-Objekt pInfo
. Hier finden Sie die Einzelheiten über den aktuellen
Druck, insbesondere die benötigten Rechteckkoordinaten für den Druckergerätekontext.
Zu CPrintInfo
gehören zahlreiche nützliche Member-Variablen. Einige davon
führt Tabelle C.1 auf.
Auf einige andere Variablen in CPrintInfo
gehen wir weiter hinten in diesem Anhang
ein. Zuerst müssen wir aber die Rechteckkoordinaten des Druckbereiches und nicht
des Fensterbereichs ermitteln. Die Elementvariable m_rectDraw
speichert die Rechteckkoordinaten
der aktuellen Druckseite. Diese Koordinaten kann man beim Druckergerätekontext
in der Funktion OnDraw
verwenden. Dennoch besteht das Problem, daß
diese Struktur nicht an OnDraw
übergeben wird. Allerdings lassen sich die Koordinaten
in eine Elementvariable der Klasse CPrintItView
kopieren.
Fügen Sie nach dem Kommentar // TODO ...
und noch vor dem Aufruf von
CView::OnPrint
die nachstehenden Zeilen hinzu, um das Rechteck zu speichern:
// Rechteckbereich aus pInfo kopieren
if (pInfo) m_rcPrintRect = pInfo->m_rectDraw;
Diese Anweisung speichert den zu druckenden Bereich im Element m_rcPrintRect
der Klasse CPrintItView
. Diese Member-Variable müssen Sie demzufolge noch deklarieren.
Klicken Sie dazu im Arbeitsbereich auf der Registerkarte Klassen mit der
rechten Maustaste auf die Klasse CPrintItView
, und wählen Sie aus dem Kontextmenü
den Befehl Member-Variable hinzufügen. Den Variablentyp geben Sie mit CRect
und die Deklaration mit m_rcPrintRect
an. Der Zugriffsstatus sollte Privat sein, da
keine andere Klasse über dieses interne Rechteck Bescheid wissen muß.
Der an OnPrint
übergebene Gerätekontext unterscheidet sich leicht vom Anzeigekontext,
da zum einen weniger Farben vorhanden sein können und er wahrscheinlich größer
als die Anzeige ist. Davon abgesehen kann man ihn genauso wie den Gerätekontext
für den Bildschirm zum Zeichnen einsetzen. Daraus erklärt sich, daß man dieselbe
OnDraw
-Funktion sowohl zum Drucken als auch zur Anzeige in einem Fenster nutzen
kann. Der Aufruf der Basisklasse CView::OnPrint
implementiert Code, der genau das
bewirkt.
Der Gerätekontext speichert ein Flag, das man über die Funktion IsPrinting
abfragen
kann und das angibt, ob man in einen bildschirmbasierten Gerätekontext oder einen
druckerbasierten zeichnet. Diesen Unterschied nutzt man, um die gedruckte Ausgabe
gegenüber der Bildschirmausgabe zu ändern, oder genauer gesagt, um die
Koordinaten für die gedruckte Ausgabe anzupassen.
Für das Beispielprogramm muß man nur noch die Koordinaten m_rcPrintRect
verwenden,
wenn man in der Funktion OnDraw
druckt. Listing C.2 zeigt den Code, der
mit der Funktion IsPrinting
bestimmt, ob der Client-Bereich des Fensters oder das
Rechteck des Druckers zu verwenden ist. Die von der Seitenansicht produzierte Ausgabe
zeigt Abbildung C.2.
Listing C.2: Drucken in einen Rechteckbereich in die Standardimplementierung der Funktion OnDraw eingefügt
1: // Client-Rechteck deklarieren
2: CRect rcClient;
3:
4: // Gerätekontext auf Druckermodus testen
5: if (pDC->IsPrinting())
6: {
7: // Drucken, also Druckerrechteck verwenden
8: rcClient = m_rcPrintRect;
9: }
10: else
11: {
12: // Kein Drucken, also genügt Client-Rechteck
13: GetClientRect(&rcClient);
14: }
15:
16: // In logische Einheiten konvertieren
17: pDC->DPtoLP(&rcClient);
Beachten Sie, daß Zeile 5 von Listing C.2 die Funktion IsPrinting
des Gerätekontextes
in einer if
-Anweisung aufruft. Die Funktion liefert TRUE
, wenn es sich um einen
Druckergerätekontext (oder die Druckvorschau) handelt, und FALSE
für jeden anderen
Gerätekontext. Beim Drucken kann man das gespeicherte Rechteck der Druckseite an
rcClient
zuweisen, wie es Zeile 8 zeigt. Bei einem normalen Bildschirmfenster verwendet
man einfach die normale GetClientRect
-Funktion, um das Client-Rechteck
des Fensters zu ermitteln (siehe Zeile 13).
Da das Programm mit einem Abbildungsmodus arbeitet, muß man sowohl die zum
Drucken als auch die zur Anzeige verwendeten Rechteckkoordinaten von Geräteeinheiten
in logische Einheiten umrechnen. Das erledigt die Funktion DPtoLP
in Zeile 17.
Wenn Sie in die bereits vorhandene Funktion OnDraw
die Zeilen 4 bis 14 aus Listing
C.2 einfügen und die Anwendung erstellen und ausführen, läßt sich die Seitenansicht
wie zuvor aufrufen - aber mit besseren Ergebnissen (siehe Abbildung C.2).
Abbildung C.2:
Seitenansicht mit den Rechteckkoordinaten einer vollständigen Druckseite
Wie aus Abbildung C.2 hervorgeht, ist die gedruckte Ausgabe gestreckt, weil das Papier wesentlich länger und schmaler ist als das Fenster. Die Beziehung zwischen der Breite und der Höhe bezeichnet man als Höhen-/Seitenverhältnis. Um ein Strecken des Bildes in die eine oder andere Richtung zu vermeiden, muß man das gleiche Höhen-/Seitenverhältnis wie beim Bild im Fenster einhalten. Der Code in Listing C.2 versucht das gar nicht erst, was in den meisten Fällen zu einer unbefriedigenden Ausgabe führt. Fügen Sie deshalb den entsprechenden Code hinzu, um das Höhen-/Seitenverhältnis der gedruckten Ausgabe zu gewährleisten.
Am besten ermittelt man zuerst, ob man entweder die Breite oder die Höhe auf den Maximalwert setzt, um die bestmögliche Ausnutzung des Druckbereichs zu erzielen. Abhängig davon verkürzt man die andere Dimension, um das Höhen-/Seitenverhältnis zu wahren.
Dazu braucht man Angaben über die Abmessungen des Papiers und dessen Höhen-/
Seitenverhältnis. Diese Details (und viele weitere) lassen sich über eine Funktion des
Gerätekontextes, GetDeviceCaps
, abrufen. Wenn man die Flags ASPECTX
oder ASPECTY
an GetDeviceCaps
übergibt, kann man die Beziehung zwischen der Breite eines Pixels
und dessen Höhe ermitteln. Handelt es sich um eine 1:1-Beziehung, ist das Pixel
quadratisch. Andernfalls ist es rechteckig und kann vom eigenen Höhen-/Seitenverhältnis
des Bildschirms abweichen. Wenn das Verhältnis von 1:1 abweicht, kann man
entscheiden, welche Achse das größte Bild erhält, während man das Höhen-/Seitenverhältnis
des Bildschirms beibehält. Auf diese Weise lassen sich gestreckte Bilder vermeiden.
Listing C.3 zeigt den Code, der genau die beschriebenen Abläufe in der Funktion
OnDraw
realisiert.
Listing C.3: Modifikationen an der Funktion OnDraw zur Wahrung des Höhen-/Seitenverhältnisses bei gleichzeitig größtmöglicher Druckausgabe
1: // Client-Rechteck deklarieren und Abmessungen holen
2: CRect rcClient;
3: GetClientRect(&rcClient);
4:
5: // Gerätekontext auf Druckermodus testen
6: if (pDC->IsPrinting())
7: {
8: // Verhältnis Druckerbreite/Fensterbreite ermitteln
9: double dWidthRatio=(double)m_rcPrintRect.Width()/
10: (double)rcClient.Width();
11:
12: // Verhältnis Druckerhöhe/Fensterhöhe ermitteln
13: double dHeightRatio=(double)m_rcPrintRect.Height()/
14: (double)rcClient.Height();
15:
16: // Höhen-/Seitenverhältnis des Geräts berechnen
17: double dAspect=(double)pDC->GetDeviceCaps(ASPECTX)/
18: (double)pDC->GetDeviceCaps(ASPECTY);
19:
20: // Neue relative Höhe ermitteln
21: int nHeight=(int)(rcClient.Height() *
22: dWidthRatio * dAspect );
23:
24: // Neue relative Breite ermitteln
25: int nWidth=(int)(rcClient.Width() *
26: dHeightRatio * (1.0 / dAspect) );
27:
28: // Das gesamte Rechteck festlegen
29: rcClient=m_rcPrintRect;
30:
31: // Beste Anpassung längs oder quer ermitteln
32: if (nHeight > nWidth)
33: {
34: // Längs am besten, also Breite anpassen
35: rcClient.BottomRight().x=
36: m_rcPrintRect.TopLeft().x + nWidth;
37: }
38: else
39: {
40: // Quer am besten, also Höhe anpassen
41: rcClient.BottomRight().y=
42: m_rcPrintRect.TopLeft().y + nHeight;
43: }
44: }
45:
46: // In logische Einheiten konvertieren
47: pDC->DPtoLP(&rcClient);
Beachten Sie, daß sowohl das Bildschirmfenster als auch die bedruckte Fläche auf die
Fensterkoordinaten zurückgreifen, die aus der Funktion GetClientRect
in Zeile 3
stammen. Für die Darstellung auf dem Bildschirm unternimmt der Code nichts weiter
und fährt wie gehabt fort.
Bei der Druckausgabe passiert dagegen eine ganze Menge, wenn der Test IsPrinting
in Zeile 6 das Ergebnis TRUE
liefert. Zuerst ist das Verhältnis zwischen Papierbreite und
Fensterbreite sowie zwischen Papierhöhe und Fensterhöhe zu ermitteln. Das geschieht
in den Zeilen 9 und 13, die die Papierabmessungen durch die Fensterabmessungen
dividieren.
Als nächstes müssen Sie die Eigenheiten des Höhen-/Seitenverhältnisses des Geräts
berechnen. Dazu verwenden Sie die Funktion GetDeviceCaps
in Zeile 17, um das Verhältnis
der Breite zur Höhe im Gerät selbst zu ermitteln und das Ergebnis in dAspect
zu speichern.
Mit diesen Werten können Sie nun die Breiten- und Höhenkoordinaten des Geräts im Vergleich zu den Fensterabmessungen berechnen, wie es aus den Zeilen 21 und 25 hervorgeht. Diese Berechnung, die das Höhen-/Seitenverhältnis des Geräts für jede Dimension einschließt, liefert die angepaßte Höhe für die volle Seitenbreite oder umgekehrt. Nun müssen Sie entscheiden, ob sich die volle Breite oder Höhe einer Seite am besten eignet und die jeweils andere Dimension anpassen. Die Bedingung in Zeile 32 trifft diese Entscheidung auf der Basis der größeren Breite oder Höhe. Das bedeutet, daß man bei einem hohen, schmalen Fenster besser die volle Höhe des Papiers nutzt und die Breite anpaßt. Wenn Sie umgekehrt ein kurzes, breites Fenster haben, verwendet man besser die volle Breite und paßt die Höhe an. Je nachdem, was besser ist, findet die Anpassung in Zeile 35 oder 41 statt, indem die x- oder y-Koordinate des rechts unten liegenden Punktes auf die angepaßte Breite oder Höhe gesetzt wird.
Alle anderen Abmessungen werden mit der Zuweisung in Zeile 29 auf rcClient
des
Papiers gesetzt, so daß die Anpassung die einzige erforderliche Änderung ist. Nach
diesem Codeabschnitt setzt das Programm fort und verwendet das angepaßte Rechteck
für die Zeichnung.
Wenn Sie die Anwendung erstellen und ausführen, nachdem Sie die Zeilen in Listing
C.3 in die Funktion OnDraw
eingefügt haben, sollte das Fenster für das Drucken oder
die Seitenansicht das gleiche Höhen-/Seitenverhältnis aufweisen wie die Darstellung
auf dem Bildschirm. Wenn Sie das Fenster strecken, um es höher als breit zu machen,
verwendet die gedruckte Ausgabe die volle Höhe der Seite statt der vollen Breite, wobei
aber das korrekte Höhen-/Seitenverhältnis erhalten bleibt.
Es kommt zwar häufig vor, daß man den Fensterinhalt ausdrucken muß, in der Regel hat man es aber mit der Druckausgabe großer und komplexer, mehrseitiger Dokumente mit den vom Benutzer als so wichtig erachteten Daten zu tun. Auch hier kommt uns das Programmgerüst zu Hilfe und vereinfacht diesen Vorgang mit dem allgemeinen Dialogfeld Seite einrichten und einem System zur Seitennumerierung, um den angegebenen Seitenbereich zu drucken und in der Vorschau zu betrachten.
Bei mehrseitigen Dokumenten gilt unser Augenmerk zuerst den Anfangs- und Endseiten,
die auch anzeigen, wie viele Seiten zu drucken sind. Wenn das Drucken beginnt,
wird als erstes die virtuelle Funktion OnPreparePrinting
der Ansichtsklasse des Programmgerüsts
aufgerufen. Sie stellt als Parameter das CPrintInfo
-Objekt pInfo
bereit.
An dieser Stelle begegnen Sie CPrintInfo
das erste Mal, und hier können Sie das
Objekt auch zuerst ändern, um die Druckausgabe an die eigenen Erfordernisse anzupassen.
Der Anwendungs-Assistent stellt die Funktion OnPreparePrinting
automatisch
bereit, wenn Sie eine SDI-Anwendung erstellen. In diesem Fall brauchen Sie sich
also nicht selbst darum zu kümmern. Die Standardimplementierung können Sie sich
ansehen, indem Sie auf das Element OnPreparePrinting
der Klasse CPrintItView
auf
der Registerkarte Klassen doppelklicken.
Die Funktion sieht folgendermaßen aus:
BOOL CPrintItView::OnPreparePrinting(CPrintInfo* pInfo)
{
// Standardvorbereitung
return DoPreparePrinting(pInfo);
}
Beim Drucken wird die Funktion DoPreparePrinting
automatisch aufgerufen und ihr
der Zeiger pInfo
auf das CPrintInfo
-Objekt übergeben. Die Funktion DoPreparePrinting
richtet den erforderlichen Gerätekontext ein und ruft das Standarddialogfeld
Drucken auf, wenn Sie drucken (und nicht die Seitenansicht anzeigen). Auf dieses
Dialogfeld geht der nächste Abschnitt näher ein. Zunächst einmal können Sie den zu
druckenden Seitenbereich einrichten, indem Sie das Objekt CPrintInfo
vor dem Aufruf
von DoPreparePrinting
modifizieren.
Fügen Sie zu diesem Zweck die folgenden Zeilen vor dem Kommentar // Standardvorbereitung
ein:
pInfo->SetMinPage(2);
pInfo->SetMaxPage(8);
Diese beiden Elementfunktionen der Klasse CPrintInfo
modifizieren das CPrintInfo
-
Objekt, auf das pInfo
zeigt, um die Startseite auf Seite 2 über SetMinPage
und die
letzte Seite auf Seite 8 via SetMaxPage
zu setzen.
Wenn man das Dokument jetzt druckt, wird die Funktion OnPrint
sechsmal aufgerufen.
Der einzige Unterschied zwischen jedem dieser Aufrufe ist die Member-Variable
pInfo->m_nCurPage
, die die aktuelle Seite aufnimmt, wenn die Schleife zwischen 2
und 8 läuft.
Je nach der Art der zu schreibenden Anwendung kann sich das Verfahren zur Bestimmung
der Seitenanzahl ändern. Wenn Sie Musik-CDs verkaufen und eine Broschüre
über Ihren Produktionsbereich drucken wollen, bringen Sie wahrscheinlich das Cover
der CD und einen kurze Inhaltsübersicht auf je einer gedruckten Seite unter. Verkaufen
Sie zum Beispiel 120 verschiedene CDs, brauchen Sie 120 Seiten. Wenn Sie dagegen
eine umfangreiche öffentliche Ausschreibung mit unterschiedlichen Rechnungsposten
und formatierten Einträgen drucken, müssen Sie wahrscheinlich die Höhe aller
verschiedenartigen Teile messen und die Seitenzahl nach der Ausführung des Seitenumbruchs
berechnen. Wie auch immer, wenn Sie die Seitenzahl ermittelt haben, ist
OnPreparePrinting
die Stelle, wo Sie diese Zahl in das Objekt CPrintInfo
schreiben.
Man muß den Benutzer nicht in jedem Fall mit dem Dialogfeld Drucken
»ärgern«. Das Dialogfeld läßt sich übergehen, indem man die Variable |
Um den Unterschied zwischen einem ganzseitigen Bericht und einem Fensterausdruck
hervorzuheben, können Sie eine ganz andere Zeichnung in der Funktion OnPrint
als
in OnDraw
implementieren, wie es Listing C.4 zeigt. In dieser OnPrint
-Funktion wird
die Funktion CView::OnPrint
der Basisklasse überhaupt nicht aufgerufen. Demnach
wird auch der Standardaufruf von OnDraw
nicht ausgeführt. In dieser Implementierung
ist damit die Druckausgabe und die Bildschirmausgabe vollkommen unterschiedlich.
Listing C.4: Seitenspezifisches Zeichnen in OnPrint implementieren
1: void CPrintItView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
2: {
3: // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
4:
5: // Schrift erzeugen und auswählen
6: CFont fnTimes;
7: fnTimes.CreatePointFont(720,"Times New Roman",pDC);
8: CFont* pOldFont=(CFont*)pDC->SelectObject(&fnTimes);
9:
10: // Pinsel erzeugen und auswählen
11: CBrush brHatch(HS_CROSS,RGB(64,64,64));
12: CBrush* pOldBrush =
13: (CBrush*)pDC->SelectObject(&brHatch);
14:
15: // Seitentext erzeugen
16: CString strDocText;
17: strDocText.Format("Seitennummer %d",
18: pInfo->m_nCurPage);
19:
20: pDC->SetTextAlign(TA_CENTER+TA_BASELINE);
21:
22: // Einige hilfreiche Punktobjekte einrichten
23: CPoint ptCenter=pInfo->m_rectDraw.CenterPoint();
24: CPoint ptTopLeft=pInfo->m_rectDraw.TopLeft();
25: CPoint ptBotRight=pInfo->m_rectDraw.BottomRight();
26:
27: // Punkte für den Rhombus erzeugen
28: CPoint ptPolyArray[4]=
29: {
30: CPoint(ptTopLeft.x,ptCenter.y),
31: CPoint(ptCenter.x,ptTopLeft.y),
32: CPoint(ptBotRight.x,ptCenter.y),
33: CPoint(ptCenter.x,ptBotRight.y)
34: };
35:
36: // Rhombus zeichnen
37: pDC->Polygon(ptPolyArray,4);
38:
39: // Text zeichnen
40: pDC->TextOut(ptCenter.x,ptCenter.y,strDocText);
41:
42: // Alte Zeichenobjekte wiederherstellen
43: pDC->SelectObject(pOldFont);
44: pDC->SelectObject(pOldBrush);
45:}
Die Zeilen 6 bis 12 von Listing C.4 richten die Ressourcen (eine Schrift und einen Pinsel) zum Drucken ein. Eigentlich gibt es dafür eine bessere Stelle. Darauf geht der Abschnitt »GDI-Objekte mit OnBeginPrinting hinzufügen« weiter hinten in diesem Anhang ein.
In der Beispielanwendung gibt Zeile 17 die Seitennummer stellvertretend für den eigentlichen
Inhalt einer Seite aus. Bei einer richtigen Anwendung beziehen Sie sich
über die Seitennummer wahrscheinlich auf das Dokument und suchen nach einem bestimmten
Datenelement. Im weiter oben erwähnten Szenario einer Musik-CD-Produktion
kann sich die Seitennummer auf eine bestimmte CD beziehen, und die Zeichenfunktionen
übernehmen dann diese Daten. Auf derartig ausgeklügelte Verfahren
wollen wir an dieser Stelle verzichten, so daß die aktuelle Seitennummer einfach mit
pInfo->m_nCurPage
diesen Punkt demonstrieren soll.
Die Zeilen 22 bis 37 richten einen Rhombus als Hintergrund ein. Zeile 40 zeichnet den Text mit der aktuellen Seitennummer in die Mitte der Seite. Die ursprünglichen Einstellungen für Schrift und Pinsel stellen die Zeilen 43 und 44 wieder her.
Wenn Sie das Programm nach diesen Änderungen in OnPrint
erstellen, ausführen
und im Menü Datei den Befehl Seitenansicht wählen, sollten Sie eine Vorschau auf
mehrere Seiten über die Schaltflächen Nächste und Vorherige wie in Abbildung C.3
erhalten. Bei angeschlossenem Drucker können Sie auch das mehrseitige Dokument
ausdrucken.
Beim Drucken eines mehrseitigen Dokuments erscheint zuerst ein Dialogfeld, in dem
Sie die Druckereinstellungen anpassen können (siehe Abbildung C.4). Es handelt sich
hierbei um das Standarddialogfeld Drucken, das von der Funktion CView::DoPreparePrinting
aufgerufen wird. Diese Funktion wird wiederum von der überschriebenen
Funktion OnPreparePrinting
aufgerufen. Im Dialogfeld Drucken können Sie
den zu druckenden Seitenbereich, die Anzahl der Kopien, ein Sortier-Flag, den Zieldrucker
und eine ganze Reihe von Eigenschaften für den jeweiligen Drucker einrichten.
Abbildung C.3:
Die Seitenansicht eines mehrseitigen Dokuments
Abbildung C.4:
Das Standarddialogfeld Drucken
Der Benutzer kann über das Dialogfeld die Druckeroptionen ändern. Die Einstellungen
aktualisieren dann das Objekt CPrintInfo
, bevor es an Ihre Anwendung übergeben
wird. Sie können das Dialogfeld bei Bedarf mehr oder weniger anpassen. Das
hängt von Ihren Anforderungen ab und vom Aufwand, den Sie in diesen Teil der Anwendung
stecken wollen.
Wie Sie Tabelle C.1 entnehmen können, gehört zu den Klassenelementen von CPrintInfo
ein Zeiger m_pPD
. Dieser zeigt auf eine CPrintDialog
-Klasse, die eine MFC-
Wrapper-Klasse für das Dialogfeld Drucken darstellt. Diese Klasse enthält auch ein
Element m_pd
, eine PRINTDLG
-Struktur mit den Standardeinstellungen, die im Dialogfeld
Drucken erscheinen. In dieser Struktur sind zahlreiche Elemente enthalten, wie
es Listing C.5 zeigt. Damit lassen sich die Vorgaben im Dialogfeld vollständig anpassen,
was sogar bis zu einer vollkommen anderen Vorlage gegenüber der Standardvorlage
des Dialogfelds reicht (wenn Sie eine Herausforderung wünschen). Es würde den
Umfang dieses Buches sprengen, auf alle Elemente im Detail einzugehen. Eines der
herausragenden Elemente ist die Member-Variable nCopies
. Die vorgegebene Anzahl
der Kopien, die das Dialogfeld anzeigt, können Sie über das Element nCopies
in der
Struktur einstellen, unmittelbar bevor die Funktion CView::DoPreparePrinting
aufgerufen
wird. Fügen Sie dazu die folgende Zeile in die Funktion OnPreparePrinting
ein:
pInfo->m_pPD->m_pd.nCopies = 15;
Wenn Sie das Dialogfeld Drucken öffnen, nachdem Sie diese Zeile hinzugefügt haben,
ist die Anzahl der Kopien mit 15 vorgegeben (falls Ihr Drucker oder Druckertreiber
mehrere Kopien unterstützt). Die anderen Vorgabewerte können Sie in der Struktur
PRINTDLG
sinngemäß einstellen.
Listing C.5: Die Struktur PRINTDLG
1: typedef struct tagPD {
2: DWORD lStructSize;
3: HWND hwndOwner;
4: HANDLE hDevMode;
5: HANDLE hDevNames;
6: HDC hDC;
7: DWORD Flags;
8: WORD nFromPage;
9: WORD nToPage;
10: WORD nMinPage;
11: WORD nMaxPage;
12: WORD nCopies;
13: HINSTANCE hInstance;
14: DWORD lCustData;
15: LPPRINTHOOKPROC lpfnPrintHook;
16: LPSETUPHOOKPROC lpfnSetupHook;
17: LPCTSTR lpPrintTemplateName;
18: LPCTSTR lpSetupTemplateName;
19: HANDLE hPrintTemplate;
20: HANDLE hSetupTemplate;
21: } PRINTDLG;
Nachdem der Benutzer das Dialogfeld Drucken mit OK bestätigt hat, können Sie die
Änderungen mit Hilfe der in Tabelle C.2 aufgeführten Zugriffsfunktionen der Klasse
CPrintDialog
abrufen. Wenn Sie zum Beispiel die festgelegte Anzahl der Kopien wissen
möchten, die der Benutzer vor dem Ausdrucken festgelegt hat, können Sie auf
diesen Wert zugreifen, nachdem er von der Funktion CView::DoPreparePrinting
zurückgegeben
wurde (siehe Listing C.6).
Natürlich können Sie auch alle anderen Werte in der PRINTDLG
-Struktur pInfo-
>m_pPD-m_pd
an dieser Stelle testen.
Liefert die vom Benutzer eingestellte Anzahl der Kopien zurück. | |
Liefert den ausgewählten Druckeranschluß, beispielsweise LPT1:. | |
Liefert |
Listing C.6: Überprüfung des Standarddialogfelds Drucken nach einer bestimmten Anzahl von Kopien.
1: BOOL CPrintItView::OnPreparePrinting(CPrintInfo* pInfo)
2: {
3: pInfo->SetMinPage(1);
4: pInfo->SetMaxPage(10);
5:
6: pInfo->m_pPD->m_pd.nCopies = 3;
7:
8: do
9: {
10: // Testen, ob Benutzer den Druckvorgang abgebrochen hat
11: if (DoPreparePrinting(pInfo) == FALSE)
12: return FALSE;
13:
14: // Benutzer warnen, falls zu viele Kopien angegeben
15: if (pInfo->m_pPD->GetCopies()>5)
16: AfxMessageBox("Bitte nicht mehr als 5 Kopien wählen");
17:
18: // Schleife bis gültige Zahl eingegeben
19: } while(pInfo->m_pPD->GetCopies()>5);
20: return TRUE;
21: }
In Listing C.6 liefert die Funktion CView:DoPreparePrinting
den Wert FALSE
zurück,
wenn der Benutzer auf Abbrechen klickt (Zeilen 11 und 12). Andernfalls prüft die
Funktion die eingestellten Kopien in Zeile 15 und gibt eine Warnung aus, wenn mehr
als fünf Kopien (ein willkürlich gewähltes Kriterium) angegeben sind. Nach Auswertung
der Schleifenbedingung in Zeile 19 wird die Schleife solange fortgesetzt, bis der
Benutzer eine gültige Anzahl von Kopien eingibt oder Abbrechen wählt.
Wenn Sie im Menü Datei der Anwendung den Befehl Seite einrichten wählen, können
Sie die vorgegebene Ausrichtung der Seite für den Drucker ändern. Das Dialogfeld
bietet die Optionen Hochformat und Querformat. Um im Querformat zu drukken,
sind keinerlei Änderungen am Code erforderlich. Wenn Sie diese Option wählen
und dann eine Seitenvorschau starten, sollte der Gerätekontext nun in die gedrehte
Seite zeichnen. Solange Ihre Anwendung das Element rectDraw
des Objekts CPrintInfo
beachtet, läßt sich das Drucken im Querformat automatisch bewerkstelligen.
Wie bereits erwähnt, funktioniert zwar der Code von Listing C.4, es gibt aber eine bessere Lösung, um die benötigten Ressourcen zuzuweisen. Momentan ruft das Programm für jede zu druckende Seite die Funktion OnPrint auf und erstellt alle Ressourcen von Grund auf neu. Bei dem einfachen Ausdruck des Beispiels wirkt sich das nicht aus, in großen und komplexen Berichten empfiehlt es sich aber, eine Reihe von Ressourcen und andere Berechnungen nur einmal beim Start des Berichts auszuführen. Dann druckt man mehrere Seiten und gibt die Ressourcen nach dem Ende des Berichts wieder frei.
Die virtuelle Funktion OnBeginPrinting
stellt einen idealen Platz für diese Initialisierung
dar. In der Schwesterfunktion OnEndPrinting
gibt man dann die Ressourcen wieder
frei.
Der Aufruf von OnBeginPrinting
erfolgt nach OnPreparePrinting
. Hier wird auch
zum ersten Mal ein Druckergerätekontext übergeben. Genau dieser Gerätekontext
kommt während des Druckvorgangs zum Einsatz, so daß man alle GDI-Objekte und
die Koordinaten der Druckseite an diesem Punkt einrichten kann. Der vom Klassen-
Assistenten automatisch erzeugte Standardcode liefert eine leere Funktion:
void CPrintItView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// ZU ERLEDIGEN: Zusätzliche Initialisierung vor dem Drucken hier einfügen
}
Sehen Sie sich die Funktionsdefinition genauer an. Die Parameter sind eigentlich für den Compiler auskommentiert, so daß Sie eine Warnung bezüglich nicht verwendeter Parameter erhalten, wenn Sie die Anwendung kompilieren. Denken Sie daran, die Kommentare bei diesen Parametern zu entfernen, bevor Sie sie verwenden.
Nunmehr können Sie die Aufrufe für das Erzeugen der GDI-Objekte in die Funktion einfügen, um das Ganze nicht bei jeder Seite erneut ausführen zu müssen:
m_fnTimes.CreatePointFont(720,"Times New Roman", pDC);
m_brHatch.CreateHatchBrush(HS_CROSS,RGB(64,64,64));
Beachten Sie, daß vor den Objekten fnTimes
und brHatch
das Präfix m_
steht. Diese
Namenskonvention weist darauf hin, daß die Objekte auf Klassenebene (in die Klasse
eingebettet) und nicht nur lokal (in die Funktion eingebettet) gültig sind. Da man in OnPrint
auf diese GDI-Objekte zugreifen muß, kann man sie in die Klassendeklaration
aufnehmen. Fügen Sie dazu die Schrift- und Pinselobjekte wie folgt in die Klassendeklaration
ein:
protected:
CFont m_fnTimes;
CBrush m_brHatch;
Die Objekte fügen Sie hinzu, indem Sie entweder im Arbeitsbereich auf der Registerkarte
Klassen auf die Klasse CPrintItView
doppelklicken und die Variablen direkt
eintragen oder über das Dialogfeld Member-Variable hinzufügen in die Klasse aufnehmen.
Beachten Sie, daß der schraffierte Pinsel mit der Funktion CreateHatchBrush
und
nicht mit dem Konstruktor erzeugt wird. Das hat folgenden Grund: Der Pinsel existiert
genauso lange wie die Ansicht, aber in der Funktion OnBeginPrinting
müssen Sie DeleteObject
aufrufen, so daß die zugrundeliegende GDI-Ressource zwischen zwei
Druckausgaben freigegeben wird. Den Code zum Löschen der beiden GDI-Objekte
(Schrift und Pinsel) realisieren Sie in der Funktion OnEndPrinting
mit den folgenden
Zeilen:
m_fnTimes.DeleteObject();
m_brHatch.DeleteObject();
Jetzt sind lediglich noch die lokalen GDI-Objekte von der Funktion OnPrint
selbst zu
entfernen und deren Referenzen durch die Versionen der Member-Variablen zu ersetzen.
Dazu ersetzen Sie die lokalen Variablen fnTimes
von CFont
und brHatch
von
CBrush
sowie deren Erstellungsfunktionen und selektieren einfach die vordefinierte
Schrift und den Pinsel:
CFont* pOldFont = (CFont*)pDC->SelectObject(&m_fnTimes);
CBrush* pOldBrush = (CBrush*)pDC->SelectObject(&m_brHatch);
Wenn Sie die Anwendung nach diesen Änderungen erstellen und ausführen, bemerken Sie wahrscheinlich keinen Unterschied. Funktionell ist alles gleich geblieben, aber das Drucken und die Seitenansicht sollten etwas schneller arbeiten. Insbesondere bei einem großen, komplexen 100seitigen Bericht, der auf eine Menge von GDI-Ressourcen zurückgreift, werden Sie bei diesem Verfahren zweifellos eine höhere Druckgeschwindigkeit registrieren.
Vor dem Aufruf der beiden Funktionen OnDraw
und OnPrint
wird die virtuelle Funktion
OnPrepareDC
aufgerufen, die Sie in Ihrer Ansichtsklasse überschreiben können, um
Modifikationen am Gerätekontext vorzunehmen, die sowohl für OnDraw
als auch OnPrint
zutreffen. Beispielsweise kann man die Abbildungsmodi oder bestimmte Zeichenmodi
einstellen, die sowohl für die Darstellung auf dem Bildschirm als auch den
Ausdruck gültig sind. Der Anwendungs-Assistent stellt die überschriebene Funktion
nicht bereit, man kann sie aber einfach über das Dialogfeld Virtuelle Funktion hinzufügen
aufnehmen. Im Beispiel ist den beiden Funktionen OnDraw
und OnPrint
die
Funktion SetTextAlign
des Gerätekontextes gemeinsam. In eine OnPrepareDC
-Funktion
können Sie das folgendermaßen hinzufügen:
void CPrintItView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
pDC->SetTextAlign(TA_CENTER+TA_BASELINE);
}
Insbesondere bei der Vorbereitung von WYSIWYG-Ausdrucken kann es vorteilhaft
sein, die Abbildungsmodi und Fensterdimensionen in einer gemeinsamen Funktion
einzurichten, bevor man die Zeichen- oder Druckfunktion aufruft. Hier bietet sich OnPrepareDC
an, um den speziellen Initialisierungscode für den Gerätekontext unterzubringen.
Ein weiterer Einsatzfall der Funktion OnPrepareDC
ist der Aufruf von Escape-Sequenzen
für den Drucker oder anderer dokumentspezifischer Funktionen. Wenn Sie einen
besonders langen Bericht haben, wollen Sie dem Benutzer sicherlich die Möglichkeit
bieten, den Druckvorgang zu beenden und das Drucken abzubrechen. Die Funktion
AbortDoc
des Gerätekontextes bricht das Drucken des Dokuments für einen Druckergerätekontext
ab. Probieren Sie es aus, indem Sie die folgenden Zeilen in OnPrepareDC
einfügen und den Druck des Dokuments nach drei Seiten abbrechen:
if (pDC->IsPrinting())
if (pInfo->m_nCurPage==3) pDC->AbortDoc();
Dieser Anhang ist bisher nur auf die Unterstützung der Druckfunktionen in SDI- und MDI-Gerüsten eingegangen. Diese Unterstützung fügt sich zwar harmonisch in die Dokument/Ansicht-Architektur ein, es kann aber vorkommen, daß man nur einfach mal so auf den Drucker zugreifen möchte oder kein Programmgerüst zur Hand hat - beispielsweise in einer dialogbasierten Anwendung.
Das Programmgerüst verbirgt die systemnahe Druckunterstützung, die den Grundstein für alle Druckoperationen legt. Dieser Abschnitt erklärt, wie diese Unterstützung arbeitet, und zeigt den Einsatz in einem dialogbasierten Anwendungsbeispiel.
Im Abschnitt »Das Dialogfeld Drucken« weiter vorn in diesem Anhang haben Sie gesehen,
wie die Klasse CPrintDialog
einen Wrapper für das Standarddialogfeld PRINTDLG
bereitstellt und wie man diesen von CView::DoPreparePrinting
aufruft.
Das gleiche Dialogfeld und die gleiche Klasse kann man direkt verwenden, um den
Zieldrucker und dessen Standardeinstellungen einzurichten, genauso wie man ein normales
modales Dialogfeld einsetzt. Es lassen sich auch die gleichen Zugriffsfunktionen
nutzen, um die Standardwerte für Seitennummern und Anzahl der Kopien festzulegen,
wie Sie es bereits beim Aufruf der Funktion DoPreparePrinting
des Programmgerüsts
kennengelernt haben.
Listing C.7 zeigt dieses Dialogfeld, über das man bei dialogbasierten Anwendungen direkt den Drucker konfigurieren und dann ein kleines Dokument mit den Standardeinstellungen des Dialogfelds drucken kann.
Der direkte Druckvorgang arbeitet mit den in diesem Listing vorgeführten Funktionen
StartDoc
und EndDoc
. Der nächste Abschnitt geht näher darauf ein.
Erstellen Sie als Beispiel mit dem Anwendungs-Assistenten eine dialogfeldbasierende
Anwendung namens DlgPrint
, und erzeugen Sie mit dem Klassen-Assistenten eine
OnOK
-Behandlungsroutine, um den Druckercode zu implementieren, wie es Listing C.7
zeigt.
Listing C.7: DlgPrintDlg.cpp - Das direkte Ausdrucken eines Dokuments in OnOK einer dialogfeldbasierenden Anwendung implementieren.
1: void CDlgPrintDlg::OnOK()
2: {
3: // TODO: Zusätzliche Prüfung hier einfügen
4:
5: // Ein CPrintDialog-Objekt konstruieren
6: CPrintDialog dlgPrint(FALSE,PD_ALLPAGES,this);
7:
8: if (dlgPrint.DoModal()==IDOK)
9: {
10: // Den Drucker-DC vom Dialogfeld mit einem
11: // CDC-Objekt verbinden
12: CDC dcPrint;
13: dcPrint.Attach(dlgPrint.GetPrinterDC());
14:
15: // Eine DOCINFO-Struktur erzeugen und füllen
16: DOCINFO myPrintJob;
17: myPrintJob.cbSize = sizeof(myPrintJob);
18: myPrintJob.lpszDocName = "MeinDruckAuftrag";
19: myPrintJob.lpszOutput = NULL;
20: myPrintJob.lpszDatatype = NULL;
21: myPrintJob.fwType = NULL;
22:
23: // Ausdruck des Dokuments starten
24: if (dcPrint.StartDoc(&myPrintJob)>=0)
25: {
26: // Eine Seite beginnen
27: dcPrint.StartPage();
28:
29: // Zeichnen beginnen
30: dcPrint.TextOut(0,0,"Mein kleiner Druckauftrag");
31:
32: // Seite auswerfen
33: dcPrint.EndPage();
34:
35: // Dokument schließen
36: dcPrint.EndDoc();
37: }
38:
39: // Druckergerätekontext löschen
40: dcPrint.DeleteDC();
41: }
42:
43: // Mit Standardverarbeitung von OnOK fortfahren
44: CDialog::OnOK();
45: }
Listing C.7 deklariert in Zeile 6 ein CPrintDialog
-Objekt, das im Konstruktor drei Parameter
übernimmt. Der erste Parameter ist ein Flag, das man auf TRUE
setzt, um das
Dialogfeld Seite einrichten anzuzeigen. Bei FALSE
erscheint das Dialogfeld Drucken.
Der zweite Parameter stellt eine Gruppe kombinierbarer Flags dar, mit denen sich die
Einstellungen des Dialogfelds (zu umfangreich, um sie hier zu behandeln) festlegen lassen.
Der dritte Parameter ist ein Zeiger auf das übergeordnete Fenster. In diesem Fall
kennzeichnet der C++-Zeiger this
, daß das Dialogfeld das übergeordnete Fenster ist.
Zeile 8 ruft dlgPrint.DoModal
auf, um dieses Dialogfeld anzuzeigen. Wenn der Benutzer
auf OK klickt, beginnt das Drucken. Andernfalls wird der Block übersprungen.
Wenn der Benutzer im Dialogfeld Drucken auf OK geklickt hat, wird ein Gerätekontext
für den Drucker erzeugt und in Zeile 13 mit einem CDC
-Objekt verbunden, um bequemer
darauf zugreifen zu können. Denken Sie daran, den Gerätekontext selbst zu
löschen, wie es Zeile 40 zeigt.
Fügen Sie den Code des Listings und die Behandlungsroutine in Ihre Beispielanwendung ein, erstellen und starten Sie sie, und klicken Sie im Dialogfeld der Anwendung auf OK, um den neuen Code auszuprobieren.
Der CDC
-Gerätekontext umfaßt viele druckerspezifische Funktionen. Bei einem neuen
Ausdruck muß Windows ein Spool-Dokument erzeugen, um den Druckauftrag zu speichern
und ihn bei Vollständigkeit an den Drucker abzuschicken. Die Funktion StartDoc
weist Windows an, das Spooling zu starten, während die Funktion EndDoc
anzeigt,
daß das Dokument vollständig ist und an den Drucker geschickt werden kann. Weiter
vorn in diesem Anhang haben Sie bereits die Funktion AbortDoc
kennengelernt, die
das Drucken abbricht und den Druckauftrag storniert, statt ihn zum Drucker zu senden.
Listing C.7 ruft in Zeile 24 die Elementfunktion StartDoc
des Druckergerätekontextobjekts
dcPrint
auf und übergibt dabei einen Zeiger an eine DOCINFO
-Struktur. Diese
Struktur speichert die Details des Druckauftrags. Als einzige Angabe müssen Sie einen
Namen für das Spool-Dokument festlegen, was in Zeile 18 geschieht. Beachten Sie
das unübliche Element cbSize
, das die Größe der Struktur speichert. Zeile 17 weist
diesem Element den Wert aus sizeof(myPrintJob)
zu. Eine derartige Konstruktion
finden Sie auf der Ebene des Win32-API häufig, weil DOCINFO
eine alte Struktur im Stil
von C ist. Hier wird cbSize
verwendet, da es unterschiedliche Formen von DOCINFO
gibt und man diese nur über die Größe auseinanderhalten kann.
Beim Aufruf von StartDoc
versucht die Funktion, den Druckvorgang zu starten und
liefert im Erfolgsfall einen positiven Wert zurück. Die Gründe für ein Scheitern können
unterschiedlichster Art sein, beispielsweise zu geringer Platz auf dem Datenträger oder
im Hauptspeicher, oder ein beschädigter Druckertreiber, so daß man den Druckvorgang
nur fortsetzten sollte, nachdem man den Rückgabecode ausgewertet hat.
Nach dem Ausdruck des Dokuments rufen Sie EndDoc
auf, wie es Zeile 36 zeigt, um
den eigentlichen Ausdruck des Dokuments in die Wege zu leiten.
Ein weiteres Funktionspaar für den Druckergerätekontext sind StartPage
und EndPage
. Mit der Funktion StartPage
initialisiert man den Gerätekontext, um eine neue Seite
zu drucken. Dabei werden einige Einstellungen des Gerätekontextes wie zum Beispiel
die aktuelle Position des Grafikcursors zurückgesetzt und die Spooling-
Informationen für den Beginn einer neuen Seite gesetzt.
Normalerweise ruft man StartPage
auf, zeichnet etwas in den Gerätekontext, um die
Details der zu druckenden Seite auszugeben, und ruft EndPage
auf, um die Seite in die
Spool-Datei zu schreiben und sie zum Druckdokument hinzuzufügen.
In Listing C.7 steht der Aufruf von StartPage
in Zeile 27, gefolgt von einer einzelnen
Funktion TextOut
, die etwas auf der zu druckenden Seite ausgibt, und einem Aufruf
der Funktion EndPage
in Zeile 33.
Beim Aufruf von EndPage
werden die speziellen Druckercodes für einen Formularvorschub
(Form Feed) an den Spooler gesendet, und das Spool-Dokument registriert eine
weitere Druckseite. Die Folge StartPage
und EndPage
können Sie für alle Dokumentseiten
wiederholen, bevor Sie mit EndDoc
den Druckvorgang abschließen. Den Drukkergerätekontext
können Sie für das Zeichnen zwischen den Aufrufen von StartPage
und EndPage
in genau der gleichen Weise verwenden, wie Sie OnPrint
in der SDI-Anwendung
eingesetzt haben. Das SDI-Gerüst ruft die gleichen Funktionen auf, verbirgt
sie allerdings, und es erscheinen nur die Aufrufe der Funktion OnPrint
zwischen den
Aufrufen für die erste und letzte Seite.