Zeichnen mit MFC Erweiterung 2


Einleitung

Um diese Anleitung nicht zu lang werden zu lassen, schreibe ich immer nur die Dinge auf, die verändert werden müssen. Bei Codeabschnitten werden die Teile, die von mir verändert wurden fett geschrieben.

Beispiel

Aufgabe

Das Programm Zeichner soll so erweitert werden, dass jeder Strich in einer CObjectList gespeichert wird. Dazu wird eine Klasse Strich gebraucht, in der jeweils ein Strich mit allen Information (startpunkt, endpunkt, strichdicke, strichfarbe) gespeichert wird.

Lösung in Prosa

  1. Zuerst wird eine Neue Klasse für einen Strich gebraucht.
    -> Einfügen
    -> Neue Klasse...
    -> Allgemeine Klasse
    -> Klassenname : Linie
    -> Abgeleitet von CObject
    -> Sollte nach bestätigen mit OK ein Fehlerdialog erscheinen einfach auf OK drücken(unwichtig :-)
  2. Hinzufügen von Member-Variablen zur Klasse Linie zur vollständigen Beschreibung einer Linie:
    1. mit der rechten Maustaste auf die Klasse Linie klicken, Member-Variable hinzufügen...
      (Mehrmals wiederholen)
    2. Variablenname=startpunkt; Variablentyp=CPoint
    3. Variablenname=endpunkt; Variablentyp=CPoint
    4. Variablenname=farbe; Variablentyp=COLORREF
    5. Variablenname=dicke; Variablentyp=int
  3. Includieren der Headerdatei der Klasse Linie in die Datei CZeichnerView.cpp und CZeichnerDoc.cpp:
    -> CZeichnerView.cpp:
    // ZeichnerView.cpp : Implementierung der Klasse CZeichnerView
    //
    
    #include "stdafx.h"
    #include "Zeichner.h"
    
    #include "ZeichnerDoc.h"
    #include "ZeichnerView.h"
    #include "StrichDialog.h"
    #include "Linie.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    
    -> CZeichnerDoc.cpp:
    // ZeichnerDoc.cpp : Implementierung der Klasse CZeichnerDoc
    //
    
    #include "stdafx.h"
    #include "Zeichner.h"
    
    #include "ZeichnerDoc.h"
    #include "Linie.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    
  4. Hinzufügen einer Variable des Typs CObjectList zu der Klasse CZeichnerDoc:
    Wichtig: Daten sollten immer in der Klasse CZeichnerDoc abgelegt werden, weil das von Windows so vorgesehen ist. (z.B.: Speicher-/Ladefunktion befindet sich hier)
    1. Mit rechter Maustaste auf Klasse CZeichnerDoc klicken, Member-Variable hinzufügen...
    2. Variablentyp=CObjectList Variablenname=ObjektListe
  5. Nun muss jedes Mal, wenn ein neuer Strich gezeichnet wird, dieser zur ObjektListe hinzugefügt werden:
    Dazu muss der Handler für das Loslassen der Maustaste abgeändert werden. (in der Klasse CZeichnerView; Methode OnLButtonUp)
    Der Handler sollte dann so ungefähr aussehen:
    void CZeichnerView::OnLButtonUp(UINT nFlags, CPoint point) 
    {
    	// TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen und/oder Standard aufrufen
    
    	// Holen der Dokumentklasse
    	// wird gebraucht, weil sich dort die ObjektListe befindet
    	CZeichnerDoc* pDoc = GetDocument();
    	// Prüfe, ob pDoc != NULL, sonst wirf Exception
    	ASSERT_VALID(pDoc);
    
    	// Neue Linie anlegen
    	Linie *l = new Linie();
    
    	// Merken des Endpunktes, eigentlich sinnlos,
    	// weil sowieso gleich wieder woanders gebraucht
    	// Trotzdem zur besseren Übersicht
    	endpunkt = point;
    
    	// Zuweisen der aktuellen Eigenschaften der Linie
    	l->dicke = strichdicke;
    	l->startpunkt = startpunkt;
    	l->endpunkt = endpunkt;
    	l->farbe = strichfarbe;
    
    	// Anfügen der Linie am Ende der ObjektListe
    	pDoc->ObjektListe.AddTail(l);
    
    	// Neuzeichnen aller Elemente, vorher löschen des Bildschirms
    	Invalidate(true);
    	
    	CView::OnLButtonUp(nFlags, point);
    }
    
  6. Dass alle Elemente der Liste ObjektListe wirklich gezeichnet werden, muss noch die Methode OnDraw der Klasse CZeichnerView angepasst werden:
    void CZeichnerView::OnDraw(CDC* pDC)
    {
    	CZeichnerDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    
    	CPen *oldpen,*newpen;
    	POSITION pos;
    	Linie *l;
    	
    	pos = pDoc->ObjektListe.GetHeadPosition();
    
    	while (pos != NULL)
    	{
    		// Linie holen und in Zeiger auf Linie konvertieren
    		l = (Linie*) pDoc->ObjektListe.GetNext(pos);
    		// Neuen Zeichenstift anlegen mit Informationen aus Linie
    		newpen = new CPen(PS_SOLID,l->dicke,l->farbe);
    		// Neuen Stift einstellen, alten merken
    		oldpen = pDC->SelectObject(newpen);
    
    		// Verschiebe Grafikcursor zu Position startpunkt (ohne zu zeichnen)
    		pDC->MoveTo(l->startpunkt);
    		// Zeichne Linie zum endpunkt
    		pDC->LineTo(l->endpunkt);
    
    		// wieder alten Stift einstellen
    		pDC->SelectObject(oldpen);
    		// neuen Stift wieder löschen
    		delete newpen;
    	}
    	// ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten hinzufügen
    }
    
  7. Für guten Programmierstil sollten alle Linien beim Beenden des Programmes wieder entfernt werden. Dies wird am Besten im Destruktor von CZeichnerDoc erledigt.
    Hinweis: Ein Destruktor einer Klasse hat keinen Rückgabewert(nicht mal void) und hat den Namen '~'+Name der Klasse. Er wird beim Zerstören der Klasse automatisch aufgerufen.
    CZeichnerDoc::~CZeichnerDoc()
    {
    	POSITION pos = ObjektListe.GetHeadPosition();
    
    	// Löschen jeder einzelnen Linie,
    	// weil sie nicht automatisch gelöscht werden
    	while (pos != NULL)
    		delete (Linie*)ObjektListe.GetNext(pos);
    	
    	// Löschen der Verweise auf die Linien in der Liste
    	ObjektListe.RemoveAll();
    }
    
  8. Fertig