Koordinatensysteme
Kommen wir jetzt zu einem Thema das in der Regel am Anfang etwas Schwierigkeiten
bereitet, den Mapping-Modes. Über die Mapping-Modes kann das zum Zeichnen verwendete
Koordinatensystem eingestellt werden. In den vorherigen Lektionen haben Sie öfters
die Bezeichnung 'logische Einheiten' gehört. Hier erfahren Sie nun was es damit
genau auf sich hat.
Damit Sie sich auch gleich die Wirkungsweise der nachfolgenden Methoden ansehen
können, beginnen wir diese Lektion mit einem Beispiel das im Verlauf der Lektion
schrittweise modifiziert wird.
 |
Falls Sie noch das Beispiel aus der vorherigen Lektion geöffnet
haben, schließen Sie dieses zunächst. Wir werden es erst später erweitern.
Erstellen Sie dann wie gewohnt ein neues SDI-Projekt und geben dem Projekt den
Namen MapMode. Nach dem Sie den Rahmen der Anwendung erstellt haben,
passen Sie die OnDraw(...) Methode des Ansichtobjekts wie folgt an:
void CMapModeView::OnDraw(CDC*
pDC)
{
CMapModeDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten
hinzufügen
// Groesse der Grafikobjekte
const int nXMIN = -100;
const int nXMAX = 100;
const int nYMIN = -100;
const int nYMAX = 100;
// Stifte und Pinsel erstellen
CPen CBluePen(PS_SOLID,0,RGB(0,0,255));
CPen CRedPen(PS_DOT,1,RGB(255,0,0));
CBrush CBFill(RGB(255,255,200));
// Ellipse zeichnen
pDC->SelectStockObject(BLACK_PEN);
pDC->SelectObject(&CBFill);
pDC->Ellipse(nXMIN,nYMIN,nXMAX,nYMAX);
// X-Achse zeichnen
// negative Achse rot, positive Achse blau
pDC->SelectObject(&CRedPen);
pDC->MoveTo(nXMIN,0);
pDC->LineTo(0,0);
pDC->SelectObject(&CBluePen);
pDC->LineTo(nXMAX,0);
// Y-Achse zeichnen
// negative Achse rot, positive Achse blau
pDC->SelectObject(&CRedPen);
pDC->MoveTo(0,nYMIN);
pDC->LineTo(0,0);
pDC->SelectObject(&CBluePen);
pDC->LineTo(0,nYMAX);
} |
Übersetzen und starten Sie nun das Programm.
|
In der OnDraw(...) Methode wird zunächst die Größe der zu zeichnenden
Objekte festgelegt, die hier den Bereich von -100...+100 einnehmen sollen. Anschließend
werden die zum Zeichnen verwendeten Stifte und Pinsel erstellt. Danach wird eine
Ellipse in hellgelb gezeichnet die den gesamten Zeichenbereich (nicht Fensterbereich!)
belegt. Sodann werden die X- und Y-Achsen gezeichnet, wobei die negativen Achsanteile
in rot und die positiven Achsanteile in blau gezeichnet werden. Beachten Sie auch
die verschiedenen Stiftstile und -dicken! Wenn Sie alles richtig in Ihr Programm
übernommen haben, so werden Sie folgende Ausgabe erhalten:

Am linken und oberen Rand werden Sie die positiven X- und Y-Achsen in blauer
Farbe gerade noch erkennen können, während die negativen Achsen nicht sichtbar sind.
Dies liegt daran, dass standardmäßig der Mapping-Mode MM_TEXT eingestellt ist. Bei
diesem Mapping-Mode befindet sich defaultmäßig der Nullpunkt in der linken oberen
Ecke des Fensters und die Koordinatenwerte der X-Achse nehmen von links nach rechts
zu und die der Y-Achse von oben nach unten. Das GDI bietet zum Verschieben diese
Nullpunktes eine Methode an, die durch die CDC-Methode SetViewportOrg(...)
gekapselt wird. Die Parameter der Methode geben hierbei die Verschiebung des Nullpunkts
an. Positive Werte verschieben den Nullpunkt des Fensters nach rechts bzw. nach
unten. Die Angaben erfolgen hier immer in physikalischen Einheiten, d.h. sie arbeiten
unabhängig vom eingestellten Mapping-Mode (siehe weiter unten).
 |
Es gibt eine weitere CDC-Methode SetWindowOrg(...)
die ebenfalls den Nullpunkt verschiebt. Die Angabe der Verschiebungswerte besitzen
hier aber entgegengesetzte Vorzeichen, d.h. eine positive Verschiebung mittels
SetViewportOrg(...) arbeitet gleich wie eine negative Verschiebung
mittels SetWindowOrg(...) |
 |
Verschieben wir jetzt den Nullpunkt des Zeichenbereichs so,
dass das Achsenkreuz vollständig sichtbar ist. Die negativen Enden der Achsen
sollen hierbei genau am Fensterrand zum Liegen kommen, d.h. das Achsenkreuz
muss um 100 Einheiten nach rechts und nach unten verschoben werden.. Erweitern
Sie die OnDraw(...) dazu wie folgt:
void CMapModeView::OnDraw(CDC*
pDC)
{
....
// Stifte und Pinsel erstellen
....
// Neuer Nullpunkt
CPoint
CZero(-nXMIN,-nYMIN);
pDC->SetViewportOrg(CZero);
// Ellipse zeichnen
....
} |
Beachten Sie, dass die Konstanten nXMIN und nYMIN negativ sind. Damit der
Nullpunkt nach rechts unten verschoben wird, müssen Sie ihn um -nXMIN bzw -nYMIN
verschieben!
Übersetzen und starten Sie das Programm
|
Sie sollten danach folgende Ausgabe erhalten:

Damit hätten wir die 'Vorarbeit' für die Darstellung der verschiedenen Mapping-Modes
fertig. Der Mapping-Mode definiert nun, wie logische Einheiten auf physikalische
Einheiten (Pixel bei der Bildschirmausgabe und DPI bei der Druckerausgabe) umgerechnet
werden. Um einen Mapping-Mode einzustellen wird die CDC-Methode SetMapMode(...)
verwendet. Die Methode erhält als Parameter eine Konstante, die den neuen Mapping-Mode
spezifiziert. Folgende Konstanten können hierbei angegeben werden:
|
Konstante
|
Bedeutung
|
| MM_TEXT |
Dies ist der Standard Mapping-Mode.
Eine logische Einheit entspricht einer physikalischen Einheit. Wird bei der
Bildschirmausgabe z.B. eine Linie mit der Länge 10 Einheiten gezeichnet, so
wird die Linie 10 Pixel lang. Die Koordinatenwerte nehmen von links nach rechts
und von oben nach unten zu. |
| MM_LOMETRIC |
Hier entspricht eine logische
Einheit etwa 0.1 mm. Wird wieder eine Linie mit 10 logischen Einheiten gezeichnet,
so wird auf dem Bildschirm die Linie etwa 1 mm lang. Die Koordinatenwerte nehmen
von links nach rechts und von unten nach oben zu. |
| MM_HIMETRIC |
Wie MM_LOMETRIC, eine logische
Einheit entspricht nun aber 0.01 mm. |
| MM_LOENGLISH |
Wie MM_LOMETRIC, eine logische
Einheit entspricht nun aber 0.1 inch (ca. 0.254 cm). |
| MM_HIENGLISH |
Wie MM_LOMETRIC, eine logische
Einheit entspricht nun aber 0.01 inch. |
| MM_TWIPS |
Wie MM_LOMETRIC, eine logische
Einheit entspricht 1/10 point (ca. 1/1440 inch). Die Einheit point kommt aus
dem DTP Bereich und definiert normalerweise die Größe einer Schrift. |
| MM_ISOTROPIC |
Frei wählbare Umrechnung
zwischen logischer und physikalischer Einheit (mehr dazu weiter unten). Die
Umrechnung für die X- und Y-Ausdehnung erfolgt gleichermaßen. |
| MM_ANISOTROPIC |
Wie MM_ISOTROPIC, nur dass
die Umrechnung der X- und Y-Ausdehnung völlig frei einstellbar ist. |
Noch eine Anmerkung zu den in der Tabelle enthaltenen Längenangaben. Die dort
aufgeführten Längen beziehen sich auf einen ideal eingestellten 17'' Monitor. Es
entspricht der Natur der Sache, dass auf 19'' Bildschirmen die Längen entsprechend
größer ausfallen.
Bauen wir jetzt einen der Mapping-Modes in das Beispiel ein.
 |
Im Beispiel wird der Mapping-Mode MM_LOMETRIC verwendet um
das Achsenkreuz mit einer Ausdehnung von 2 x 2 cm (entspricht -100..100 jeweils)
zu zeichnen. Erweitern Sie die OnDraw(...) Methode zunächst wie folgt:
void CMapModeView::OnDraw(CDC*
pDC)
{
// Stifte und Pinsel erstellen
,,,,
// Map-Mode auf LOMETRIC
setzen
pDC->SetMapMode(MM_LOMETRIC);
// Neuer Nullpunkt
....
} |
Übersetzen und starten Sie das Programm.
|
Sie werden dann folgende Ausgabe erhalten:

Was jetzt noch stört ist, dass das Achsenkreuz nun nicht mehr mit dem Fensterrand
abschließt. Dies rührt daher, dass zuerst der Mapping-Mode umgestellt und dann der
Nullpunkt des Fenster auf die Koordinaten 100/100 verschoben wird. Wie Sie aber
bereits vorher erfahren haben, verwendet die Methode SetViewportOrg(...)
immer physikalische Einheiten, d.h. unabhängig vom eingestellten Mapping-Mode wird
der Nullpunkt auf das Pixel 100/100 im Fenster gesetzt. Die Zeichenfunktionen
MoveTo(...) und LineTo(...) hingegen verwenden jedoch immer logische
Koordinaten, so dass das Achsenkreuz hier nicht mehr 100 Pixel in jeder Richtung
lang ist. Was wir bräuchten wäre eine Methode, die uns die logischen Einheiten in
physikalische Einheiten umwandelt damit der Nullpunkt entsprechend dem eingestellten
Mappung-Mode verschoben werden kann. Und selbstverständlich gibt es eine solche
Methode, die CDC-Methode LPtoDP(...). Auch der umgekehrte Weg
ist möglich, die Umrechnung von physikalischen Einheiten in logische Einheiten.
Dazu gibt es die entsprechende Methode DPtoLP(...). Mit Hilfe dieser Methoden
können wir nun das Achsenkreuz wieder bündig am Fensterrand abschließen lassen.
 |
Erweitern Sie die OnDraw(...) Methode wie unten angegeben.
Beachten Sie hierbei bitte, dass sich auch die Angabe des Nullpunktes geändert
hat. War der Nullpunkt vorher auf der Koordinaten 100/100 so liegt er jetzt
auf der Koordinaten 100/-100. Die Ursache dafür liegt in der geänderten Achsrichtung
des Y-Achse (Koordinatenwerte nehmen nun von unten nach oben zu).
void CMapModeView::OnDraw(CDC*
pDC)
{
....
// Neuer Nullpunkt
CPoint
CZero(-nXMIN,nYMIN);
// Verschiebung des Nullpunktes
aufgrund Mapping-Mode ausrechnen
pDC->LPtoDP(&CZero);
pDC->SetViewportOrg(CZero);
....
} |
Übersetzen und starten Sie das Programm wieder. Sie können nun auch etwas
mit den Mapping-Modes experimentieren, sollten jedoch die Modes MM_ISOTROPIC
und MM_ANISOTROPIC noch nicht verwenden da uns hier noch einige Einstellungen
fehlen.
|
Das Programm wird Ihnen folgende Ausgabe liefern:

Messen Sie auch einmal das Achsenkreuz nach. Die einzelnen Achsen
sollten jetzt etwa 2 cm lang sein. Eine genaue Länge der Achsen ist nicht vorhersagbar,
da dies von der Größe des verwendeten Monitors und dessen Einstellung abhängt. Auf
einem 17'' Monitor sollte jedoch die gewünschte Länge relativ gut erreicht werden.
Kommen wir nun zu den speziellen Mapping-Modes MM_ISOTROPIC und
MM_ANISOTROPIC. Mit Hilfe dieser Mapping-Modes können Sie das logische Koordinatensystem
frei einstellen. Beim Mapping-Mode MM_ISOTROPIC wird die Ausdehnung in X- und Y-Richtung
im gleichen Verhältnis verändert während der Mapping-Mode MM_ANISOTROPIC unterschiedliche
Ausdehnung in X- und Y-Richtung zulässt. Die Auswahl der Mapping-Modes erfolgt zunächst
mit der oben angegebenen CDC-Methode SetMapMode(...). Zur Einstellung
der Ausdehnung des logischen Koordinatensystems werden zwei zusätzliche CDC-Methoden
benötigt. Die erste Methode SetWindowExt(...) setzt die Ausdehnung des
Fenster und die zweite Methode SetViewportExt(...) die Ausdehnung des dazugehörigen
Viewports. Beide Methoden erhalten als Parameter die jeweilige Ausdehnung in X-
und Y-Richtung. Die Umrechnung von logische in physikalische Einheiten erfolgt dabei
nach folgender Formel:
XPoint = (XCoord-XWindowOrg) * (XViewportExt / XWindowExt)
+ XViewportOrg
YPoint = (YCoord-YWindowOrt) * (YViewportExt / YWindowExt) + YViewportOrg |
Wie aus der Formel zu ersehen ist, spielt für die Umrechnung im
Endeffekt nur das Verhältnis ViewportExt / WindowExt eine Rolle. Die restlichen
Parameter dienen nur zur Verschiebung des Nullpunkts des Koordinatensystems. Sehen
wir uns dazu einmal einen Auszug aus einem Listing an das ein Rechteck zeichnet:
// Mapping-Mode
setzen
pDC->SetMappingMode (MM_ANISOTROPIC);
// Window-Ausdehnung setzen
pDC->SetWindowExt(10,10);
// Viewport-Ausdehnung setzen
pDC->SetViewportExt(2,5);
// ein Rechteck zeichnen
pDC->Rectangle(0,0,100,100); |
Nach der Umstellung des Mapping-Modes auf MM_ANISOTROPIC werden
Sie ein Rechteck erhalten, dass 20 Pixel breit (100*2/10) und 50 Pixel (100*5/10)
hoch ist. Vertauschen Sie die Parameter der beiden Methoden SetWindowExt(...)
und SetViewportExt(...), so wird das Rechteck entsprechend vergrößert dargestellt.
 |
Wenn Sie die Mapping-Modes MM_ISOTROPIC oder MM_ANISOTROPIC setzen, so müssen
Sie immer zuerst die Methode SetWindowExt(...) aufrufen bevor Sie
SetViewportExt(...) aufrufen! |
 |
Erweitern wir das Beispiel nun so, dass die Ellipse und die
Achsen immer fensterfüllend dargestellt werden. Beachten Sie bitte, dass die
Ellipse und die Achsen immer mit den gleichen Koordinatenangaben gezeichnet
werden. Die Dehnung bzw. Stauchung der Zeichnung erfolgt durch WINDOWS über
den Mapping-Mode.
void CMapModeView::OnDraw(CDC*
pDC)
{
// Stifte und Pinsel erstellen
....
// Mapping-Mode auf
ungleichmaessige Ausdehnungen stellen
pDC->SetMapMode(MM_ANISOTROPIC);
// log. Koordinatensystem
so umstellen, dass Fensterbreite den Bereich
// XMax-XMin belegt und Fensterhoehe den Bereich YMax-YMin
CRect CClientRect;
GetClientRect(&CClientRect);
pDC->SetWindowExt(nXMAX-nXMIN,nYMAX-nYMIN);
pDC->SetViewportExt(CClientRect.right,-CClientRect.bottom);
// Nullpunkt des neuen Koordinatensystem
berechnen
// Der Nullpunkt liegt um XMin Einheiten vom linken Rand
// und um YMax Einheiten vom oberen Rand weg
// Nullpunktangabe erfolgt immer in physikalischen Einheiten!
CPoint CZero(-nXMIN,-nYMAX);
pDC->LPtoDP(&CZero);
pDC->SetViewportOrg(CZero);
// Ellipse zeichnen
....
} |
Übersetzen und starten Sie das Programm.
|
Die Ausgabe wir jetzt stets fensterfüllend dargestellt werden. Doch einen kleinen
Fehler hat das Beispiel noch. Die negativen Achsteile sollten eigentlich als rote
gestrichelte Linie ausgegeben werden. Wenn Sie jedoch das Fenster über eine bestimmte
Größe hinaus vergrößern, so wird aus der gestrichelten Linie eine dicke rote Linie.
Wie bereits in einer der vorherigen Lektionen aufgeführt, können gemusterte Linien
(wenigsten unter WINDOWS 9x) nur mit der Strichstärke 1 Pixel gezeichnet werden.
Nun ist es aber so, dass die Strichstärke beim Erstellen des Stifts in logischen
Einheiten angegeben wird. Wird nun aufgrund des Mapping-Modes die Strichstärke breiter
als 1 Pixel, so wird aus der gestrichelten Linie eine durchgezogene Linie. Um eine
Linie unabhängig vom Mapping-Mode immer 1 Pixel breit zu zeichnen, geben Sie bei
der Erstellung des Stifts als Strichstärke den Wert 0 vor. Sie können dies nun zum
Abschluss in das Beispiel noch einbauen.
Das fertige Beispiel finden Sie übrigens im Programmverzeichnis unter
05GDI\MapMode.
Wenden wir uns nun wieder unserem Ausgangsbeispiel zu.
 |
Schließen Sie jetzt das bisherige Beispiel und öffnen Sie das
inzwischen modifizierte Ausgangsbeispiel 05GDI\GDIFunc.
Versuchen jetzt einmal wieder selbst die Kurve der Messwerte fensterfüllend
darzustellen. Wenn Sie sich an die oben angegebene Vorgehensweise halten, sollte
diese Übung keine großen Schwierigkeiten bereiten.
 |
Bevor Sie aber Verzweifeln kann ich Ihnen auch meine Lösung anzeigen. |
Lösung zur fensterfüllende Darstellung
void CGDIFuncView::OnDraw(CDC*
pDC)
{
....
// ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten
hinzufügen
// Stift fuer das Koordinatensystem
// ACHTUNG! Stiftstaerke
auf 0 setzen damit die Linie
// immer 1 Pixel breit gezeichnet wird!
CPen
CAxisPen(PS_DASH,0,RGB(0,0,255));
// Stift fuer die
Kurve
CPen CLinePen;
CLinePen.CreatePen(PS_SOLID,0,RGB(255,0,0));
// Gemusterter Pinsel
zum Ausfuellen des Polygons
CBrush CPFillBrush(HS_BDIAGONAL,RGB(0,255,0));
// Mapping-Mode auf
ungleichmaessige Ausdehnungen stellen
pDC->SetMapMode(MM_ANISOTROPIC);
// log. Koordinatensystem
so umstellen, dass Fensterbreite den Bereich
// XMax-XMin belegt und Fensterhoehe den Bereich YMax-YMin
CRect CClientRect;
GetClientRect(&CClientRect);
pDC->SetWindowExt(m_nXMax-m_nXMin,m_nYMax-m_nYMin);
pDC->SetViewportExt(CClientRect.right,-CClientRect.bottom);
// Nullpunkt des neuen
Koordinatensystem berechnen
// Der Nullpunkt liegt um XMin Einheiten vom linken Rand
// und um YMax Einheiten vom oberen Rand weg
// Nullpunktangabe erfolgt immer in physikalischen Einheiten!
CPoint CZero(-m_nXMin,-m_nYMax);
pDC->LPtoDP(&CZero);
pDC->SetViewportOrg(CZero);
// Hintergrund fuer gestrichelte Linie
....
} |
Na, haben Sie es auch wirklich erst selbst versucht? Dann haben Sie vermutlich
beim ersten Versuch vergessen die Stiftstärken für die Achsen und das Polygon
auf 0 zu setzen damit die Linien immer 1 Pixel breit gezeichnet werden,
unabhängig vom Mapping-Mode. Ansonsten werden die Achsen nicht mehr gestrichelt
gezeichnet.
Der Rest der Übung folgt im Prinzip genau dem im Kurs angegebenen Kochrezept
zur Darstellung von fensterfüllenden Grafiken.
Ende der Lösung
|
Übersetzen und starten Sie das Programm. Wenn Sie alles richtig eingegeben
haben, so erhalten Sie immer die Kurve fensterfüllend dargestellt.

Damit beenden wir dieses Beispiel, das Sie auch im Kursverzeichnis
unter 05GDI\GDIFunc finden.
|
Verlassen wir nun die Darstellung von Grafiken vorläufig. In der nächsten Lektion
geht es weiter mit der Ausgabe von Texten.
|