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:

Ausgangszeichnung für Map-Modes

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:

Ausgabe mit verschobenem Nullpunkt

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:

LOMETRIC Map-Mode

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:

Nullpunkt korrigiert

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.

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.

Die endgültige Darstellung der Kurve

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.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470