vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Tag C

Drucken und Druckvorschau

Die Funktionalität des Rahmenprogramms

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.

Standardfunktionen zum Drucken

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.

Druckunterstützung bei einem Standardgerüst

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.

OnPrint überschreiben

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.

Tabelle C.1: Member-Variablen von CPrintInfo, die sich auf Druckinformationen beziehen

Variablenname

Beschreibung des Inhalts

m_nCurPage

Die aktuell gedruckte Seite bei mehrseitigen Druckausgaben.

m_nNumPreviewPages

Entweder 1 oder 2, je nach Anzahl der angezeigten Vorschauseiten.

m_rectDraw

Die Rechteckkoordinaten der Druckseite.

m_pPD

Zeiger auf eine CPrintDialog-Klasse, wenn das Dialogfeld Drucken verwendet wird.

m_bDirect

TRUE, wenn das Dialogfeld Drucken umgangen wird.

m_bPreview

TRUE, wenn momentan die Druckvorschau aktiv ist.

m_strPageDesc

Ein Formatstring für die Generierung der Seitennummer.

m_lpUserData

Ein Zeiger, der für die Speicherung von Benutzerdaten verwendet werden kann.

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 Druckergerätekontext

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

Das Höhen-/Seitenverhältnis

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.

Höhen-/Seitenverhältnis von Geräten

Bei den meisten Druckern eignet sich das Höhen-/Seitenverhältnis von 1:1 scheinbar am besten. Wenn man sich aber die Druckergebnisse von Thermodruckern wie etwa in Fax-Geräten genauer ansieht, erkennt man einen deutlichen Unterschied im Höhen-/Seitenverhältnis gegenüber dem Original.

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.

Seitenumbruch und Seitenausrichtung

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.

Anfangs- und Endseite festlegen

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.

Das Dialogfeld Drucken überspringen

Man muß den Benutzer nicht in jedem Fall mit dem Dialogfeld Drucken »ärgern«. Das Dialogfeld läßt sich übergehen, indem man die Variable pInfo->m_bDirect in OnPreparePrinting auf TRUE setzt.

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.

Das Dialogfeld Drucken

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

Das Kontrollkästchen Sortieren

Wenn der Benutzer das Kontrollkästchen Sortieren im Dialogfeld Drukken ausschaltet, werden die Seiten jeweils von der ersten bis zur letzten ausgegeben, wobei der Druckertreiber diesen Vorgang bei mehreren Kopien automatisch wiederholt. Ist das Kontrollkästchen eingeschaltet, wird jede Seite entsprechend der Anzahl Kopien mehrfach gedruckt, bis es zur nächsten Seite weitergeht. In Ihrem Code brauchen Sie dafür keine besonderen Vorkehrungen treffen - der Druckertreiber muß aber diese Funktion unterstützen. Andernfalls ist das Kontrollkästchen im Dialogfeld Drucken deaktiviert und läßt sich nicht nutzen.

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.

Die Struktur DevMode

In der Struktur DevMode sind verschiedene Attribute zusammengefaßt, die die technischen Fähigkeiten und die Konfiguration des Geräts beschreiben. Die Funktion GetDevMode der Klasse CPrintDialog liefert einen Zeiger auf diese Struktur zurück.

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.

Tabelle C.2: Zugriffsfunktionen von CPrintDialog

Funktionsname

Beschreibung

GetCopies

Liefert die vom Benutzer eingestellte Anzahl der Kopien zurück.

GetFromPage

Liefert die angegebene erste Seite zurück.

GetToPage

Liefert die angegebene letzte Seite zurück.

GetPortName

Liefert den ausgewählten Druckeranschluß, beispielsweise LPT1:.

GetDriverName

Gibt den ausgewählten Druckertreiber (Zieldrucker) zurück.

GetPrinterDC

Gibt einen Gerätekontext für den Drucker zurück.

PrintAll

Liefert TRUE, wenn alle Seiten ausgewählt sind.

PrintCollate

Liefert TRUE, wenn Sortierung gefordert ist.

PrintRange

Liefert TRUE, wenn ein Bereich angegeben ist.

PrintSelection

Liefert TRUE, wenn eine bestimmte Auswahl von Seiten gewählt wird.

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.

Hochformat und Querformat

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.

GDI-Objekte mit OnBeginPrinting hinzufügen

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.

Die Koordinaten aus OnBeginPrinting

Vielleicht haben Sie versucht, auch die Koordinaten von OnBeginPrinting zu speichern. Das funktioniert nicht, weil das Element m_rectDraw von CPrintInfo in dieser Phase nicht initialisiert wurde und zufällige Koordinaten zurückkommen.

Die Vorbereitung des Gerätekontextes anpassen

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.

Den Druckauftrag abbrechen

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();

Direktes Drucken ohne Programmgerüst

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.

Das Dialogfeld Drucken direkt aufrufen

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.

Die Funktionen StartDoc und EndDoc

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.

Den Windows-Spooler überwachen

Den Aufbau des Druckdokuments können Sie verfolgen, indem Sie einen Haltepunkt in die Funktion OnPrint oder nach der Funktion StartDoc setzen und das Symbol für den Druckerstatus aus der Druckergruppe öffnen, die man über das Windows Start-Menü unter Einstellungen findet.

Die Funktionen StartPage und EndPage

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.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1