vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 10

SDI-Anwendungen

Heute erstellen Sie mit Visual C++ eine andere Art von Anwendungen als in den vergangenen Lektionen - SDI-Anwendungen oder Einzeldokumentanwendungen (SDI - Single Document Interface). Eine SDI-Anwendung ist eine dokumentbezogene Anwendung, die nur mit einem Dokument zu einem bestimmten Zeitpunkt und nur mit einem Typ von Dokument arbeiten kann.

Typische Beispiele für SDI-Anwendungen sind der Windows-Editor, WordPad und Paint. Diese Anwendungen können nur eine Art von Aufgabe realisieren und nur an einer Aufgabe zu einem bestimmten Zeitpunkt arbeiten. WordPad verhält sich wie eine SDI-Version der Textverarbeitung Word. Man kann damit viele Aufgaben wie in Word erledigen. Während es in Word aber möglich ist, an mehreren Dokumenten zur gleichen Zeit zu arbeiten, schränkt Sie WordPad auf nur ein einziges Dokument zu einem bestimmten Zeitpunkt ein.

Insbesondere lernen Sie heute, wie ...

Die Dokument/Ansicht-Architektur

Bei einer SDI-Anwendung erstellt der Anwendungs-Assistent mehr Klassen als bei einer dialogfeldbasierenden Anwendung. Die Klassen realisieren jeweils bestimmte Funktionsweisen einer SDI-Anwendung. Wenn man einmal von der Dialogfeldklasse About absieht, besteht eine SDI-Anwendung aus vier speziellen Klassen:

Die Klasse CWinApp erzeugt alle anderen Komponenten in der Anwendung. Diese Klasse empfängt alle Ereignisnachrichten und übergibt dann die Nachrichten an die Klassen CFrameView und CView.

Die Klasse CFrameView bildet den Fensterrahmen. Die Klasse nimmt die Menüs, Symbolleisten, Bildlaufleisten und alle anderen sichtbaren Objekte auf, die mit dem Rahmen verbunden sind. Die Klasse bestimmt, wieviel vom Dokument zu einem bestimmten Zeitpunkt zu sehen ist. Bei SDI-Anwendungen brauchen Sie an den beiden ersten Klassen nur wenige (oder überhaupt keine) Programmänderungen vorzunehmen.

Die Klasse CDocument nimmt Ihr Dokument auf. In dieser Klasse erstellen Sie die erforderlichen Datenstrukturen, um die Daten des Dokuments zu speichern und zu verwalten. Die Klasse empfängt die Eingaben von der Klasse CView und übergibt Anzeigeinformationen an die Klasse CView. Außerdem ist die Klasse für das Speichern und Abrufen der Dokumentdaten in bzw. aus Dateien verantwortlich.

Die Klasse CView ist die Klasse, die die visuelle Repräsentation des Dokuments für den Benutzer anzeigt. Die Klasse reicht Eingabeinformationen an die Klasse CDocument weiter und empfängt Anzeigeinformationen aus der Klasse CDocument. Das meiste, was Sie in dieser Klasse realisieren, bezieht sich auf die Ausgabe des Dokuments für den Benutzer und die Behandlung der Benutzereingaben. Die Klasse CView hat mehrere abgeleitete Klassen, die man als Basisklassen für die Ansichtsklassen verwenden kann. Diese abgeleiteten Klassen sind in Tabelle 10.1 aufgeführt.

Tabelle 10.1: Von CView abgeleitete Klassen

Klasse

Beschreibung

CEditView

Liefert die Funktionalität eines Eingabefelds. Mit dieser Klasse lassen sich einfache Texteditoren implementieren.

CFormView

Die Basisklasse für Ansichten, die Steuerelemente enthalten. Mit dieser Klasse lassen sich formularbasierte Dokumente in Anwendungen bereitstellen.

CHtmlView

Liefert die Funktionalität eines Webbrowsers. Diese Ansicht behandelt direkt die URL-Navigation, Hyperlinks usw. Verwaltet eine Verlaufsliste für das Durchsuchen in Rückwärts- und Vorwärtsrichtung.

CListView

Stellt die Funktionalität von Listen in der Dokument/Ansicht-Architektur bereit.

CRichEditView

Realisiert Zeichen- und Absatzformatierungen. Mit dieser Klasse lassen sich Textverarbeitungen implementieren.

CScrollView

Stellt Bildlauffähigkeiten für eine CView-Klasse bereit.

CTreeView

Stellt die Funktionalität zur Steuerung von Bäumen in der Dokument/Ansicht-Architektur bereit.

Alle vier Klassen arbeiten zusammen, um die vollständige Funktionalität einer SDI-Anwendung bereitzustellen, wie es Abbildung 10.1 zeigt. Auf der Grundlage dieser Architektur lassen sich relativ einfach leistungsfähige dokumentbezogene Anwendungen aufbauen.

Abbildung 10.1:
Die Dokument/Ansicht-Architektur

Lassen Sie sich vom Begriff Dokument nicht täuschen. Es bedeutet nicht, daß Sie nur Anwendungen wie Textverarbeitungen und Tabellenkalkulationen erstellen können. Im weiteren Sinne bezeichnet man mit Dokument die Daten, die Ihre Anwendung verarbeitet, während sich die Ansicht auf die visuelle Repräsentation der Daten bezieht. Beispielsweise könnte man die Anwendung Solitär als Dokument/Ansicht-Anwendung erstellen, wobei das Dokument die Karten und deren Lage im Spielfeld darstellt. In diesem Fall handelt es sich bei der Ansicht um die Anzeige der Karten, wobei jede Karte dort erscheint, wo es das Dokument festgelegt hat.

Eine SDI-Anwendung erstellen

Um eine Vorstellung von der Arbeitsweise der Dokument/Ansicht-Architektur in einer realen Anwendung zu bekommen, erstellen Sie heute eine neue Version der Zeichenanwendung aus Lektion 3. In der neuen Version bleibt die Zeichnung des Benutzers dauerhaft erhalten. Das bedeutet, daß die Zeichnung nicht gelöscht wird, wenn ein anderes Fenster über der Anwendung liegt. Außerdem kann man jetzt die Zeichnungen speichern und wiederherstellen.

Das Anwendungsgerüst erstellen

Das Gerüst für die heutige Anwendung erstellen Sie in folgenden Schritten:

1. Legen Sie mit dem Anwendungs-Assistenten ein neues Projekt namens Tag10 an.

2. Im ersten Dialogfeld des Anwendungs-Assistenten wählen Sie die Option Einzelnes Dokument (SDI).

3. Im zweiten Schritt des Assistenten übernehmen Sie die Standardwerte.

4. Im dritten Dialogfeld des Assistenten schalten Sie das Kontrollkästchen für die Unterstützung von ActiveX-Steuerelementen aus.

5. Behalten Sie die Standardwerte im vierten Dialogfeld des Anwendungs-Assistenten bei, und klicken Sie auf die Schaltfläche Weitere Optionen.

6. Im Dialogfeld Weitere Optionen geben Sie eine aus drei Buchstaben bestehende Erweiterung für die Dateien ein, die Ihre Anwendung generiert (beispielsweise dhc oder dvp). Klicken Sie auf die Schaltfläche Schliessen, um das Dialogfeld zu schließen, und klicken Sie dann auf Weiter, um zum nächsten Schritt des Assistenten zu gelangen.

7. Übernehmen Sie wieder die Standardwerte im fünften Dialogfeld des Anwendungs-Assistenten.

8. Im sechsten und letzten Schritt des Assistenten können Sie die Basisklasse wählen, von der Ihre Ansichtsklasse abgeleitet wird. Übernehmen Sie den Vorgabewert CView, und klicken Sie auf Fertigstellen. Der Anwendungs-Assistent generiert nun das Gerüst der Anwendung.

Eine Linienklasse erzeugen

Als erstes beschäftigen wir uns mit der Frage, wie man die Daten in der Dokumentklasse darstellt. Bei jeder Zeichenanwendung hat man es mit einer Reihe von Linien zu tun. Jede Linie besteht aus einem Anfangspunkt und einem Endpunkt. Man könnte nun annehmen, daß man für die Datendarstellung eine Folge von Punkten verwendet. In diesem Fall müssen Sie auch spezielle Anpassungen vornehmen, wo die Folge von Linien zwischen den Punkten endet und die nächste beginnt. Es ist sinnvoller, die Zeichnung als Folge von Linien zu repräsentieren. Damit kann man jede einzelne Linie, die im Fenster gezeichnet wird, speichern, ohne sich darum kümmern zu müssen, wo eine Gruppe von zusammenhängenden Punkten endet und die nächste beginnt.

Leider stellt die MFC-Bibliothek keine Klasse für Linienobjekte bereit, obwohl es eine Klasse für Punktobjekte (CPoint) gibt. Es bleibt Ihnen nichts weiter übrig, als ihre eigene Linienklasse in folgenden Schritten zu erzeugen:

1. Gehen Sie im Arbeitsbereich auf die Registerkarte Klassen, und markieren Sie das oberste Objekt im Baum (Tag10 Klassen). Klicken Sie mit der rechten Maustaste, und wählen Sie den Befehl Neue Klasse aus dem Kontextmenü.

2. Im Dialogfeld Neue Klasse wählen Sie als Klassentyp Allgemeine Klasse. Geben Sie als Klassennamen CLine ein, und klicken Sie auf die erste Zeile im Listenfeld Basisklasse. Geben Sie CObject als Basisklasse ein, und belassen Sie den Zugriff als public, wie es Abbildung 10.2 zeigt.

Abbildung 10.2:
Der Assistent Neue Klasse

3. Wenn Sie auf OK klicken, um die Klasse CLine hinzuzufügen, erhalten Sie den Hinweis, daß der Klassen-Assistent die passende Header-Datei zur Ableitung der Klasse CLine von der Klasse CObject nicht finden kann (siehe Abbildung 10.3). Klicken Sie auf OK, um dieses Meldungsfeld zu schließen.

Die passende Header-Klasse ist bereits in die Dateien der Klasse CLine eingebunden. Solange sich der Compiler beschwert, daß er die Definition für das CObject-Objekt nicht finden kann, kümmern Sie sich nicht um diese Meldung. Wenn Sie allerdings eine Basisklasse verwenden, die etwas tiefer in der MFC-Klassenhierarchie angesiedelt ist, sollten Sie diese Meldung ernst nehmen und die passende Header-Datei zu den #include-Anweisungen in der Quelldatei der Klasse hinzufügen.

Abbildung 10.3:
Warnung, daß Header-Dateien der Basisklassen nicht gefunden werden konnten

Die Klasse CLine konstruieren

Momentan muß Ihre CLine-Klasse nur zwei Datenelemente aufnehmen, und zwar die beiden Endpunkte der Linie, die sie repräsentiert. Fügen Sie diese beiden Datenelemente hinzu, und nehmen Sie auch einen Klassenkonstruktor auf, der beide Werte setzt, wenn die Instanz der Klasse erzeugt wird. Führen Sie dazu folgende Schritte aus:

1. Markieren Sie im Arbeitsbereich auf der Registerkarte Klassen die Klasse CLine.

2. Klicken Sie mit der rechten Maustaste auf die Klasse CLine, und wählen Sie den Befehl Member-Variable hinzufügen aus dem Kontextmenü.

3. Geben Sie CPoint als Variablentyp und m_ptFrom als Variablenname ein. Als Zugriff wählen Sie die Option Privat. Klicken Sie auf OK, um die Variable hinzuzufügen.

4. Wiederholen Sie die Schritte 2 und 3, nennen Sie die Variable diesmal m_ptTo.

5. Klicken Sie mit der rechten Maustaste auf die Klasse CLine, und wählen Sie Member-Funktion hinzufügen aus dem Kontextmenü.

6. Lassen Sie den Funktionstyp leer, und geben Sie als Funktionsdeklaration CLine(CPoint ptFrom, CPoint ptTo) ein. Klicken Sie auf OK, um die Funktion hinzuzufügen.

7. In die neue Funktion übernehmen Sie den Code aus Listing 10.1.

Listing 10.1: Der Konstruktor von CLine

1: CLine::CLine(CPoint ptFrom, CPoint ptTo)
2: {
3: // Anfangs- und Endpunkte initialisieren
4: m_ptFrom = ptFrom;
5: m_ptTo = ptTo;
6: }

Dieser Objektkonstruktor initialisiert die Anfangs- und Endpunkte mit den Punkten, die man an den Konstruktor übergibt.

Die Klasse CLine zeichnen

Nach den Regeln des objektorientierten Entwurfs sollte Ihre Klasse CLine in der Lage sein, sich selbst zu zeichnen. Wenn die Ansichtsklasse die Linie anzeigen soll, kann sie dann mit einer Nachricht das Linienobjekt anweisen, sich selbst neu zu zeichnen. Diese Funktionalität realisieren Sie in folgenden Schritten:

1. Wählen Sie Member-Funktion hinzufügen aus dem Kontextmenü, um eine neue Funktion in die Klasse CLine einzufügen.

2. Legen Sie den Funktionstyp als void und die Funktionsdeklaration als Draw(CDC *pDC) fest.

3. Schreiben Sie den Code aus Listing 10.2 in die gerade erstellte Funktion Draw.

Listing 10.2: Die Funktion Draw der Klasse CLine

1: void CLine::Draw(CDC * pDC)
2: {
3: // Linie zeichnen
4: pDC->MoveTo(m_ptFrom);
5: pDC->LineTo(m_ptTo);
6: }

Diese Funktion ist fast identisch mit der, die Sie letzte Woche erstellt haben. Es handelt sich um eine einfache Funktion, die eine Bewegung zum ersten Punkt im Gerätekontext ausführt und dann eine Linie zum zweiten Punkt im Gerätekontext zeichnet.

Die Funktionalität des Dokuments implementieren

Nachdem Sie nun über ein Objekt verfügen, mit dem sich die vom Benutzer ausgeführten Zeichnungen darstellen lassen, können Sie diese CLine-Objekte im Dokumentobjekt in einem einfachen dynamischen Array speichern. Für dieses Array fügen Sie eine CObArray-Elementvariable in die Dokumentklasse ein.

Die Klasse CObArray ist eine Objektarrayklasse, die sich selbst dynamisch in der Größe an die Anzahl der eingefügten Elemente anpaßt. Die Klasse kann beliebige Objekte aufnehmen, die von der Klasse CObject abgeleitet sind. Die Anzahl der Objekte ist lediglich durch die Größe des Hauptspeichers begrenzt. Die MFC-Bibliothek enthält weitere dynamische Arrayklassen wie CStringArray, CByteArray, CWordArray, CDWordArray und CPtrArray. Diese Klassen unterscheiden sich durch den Objekttyp, den sie aufnehmen können.

Fügen Sie das CObArray in CTag10Doc mit dem Assistenten Member-Variable hinzufügen ein, und benennen Sie das Objekt mit m_oaLines.

Linien hinzufügen

Als erstes müssen Sie der Dokumentklasse beibringen, wie man neue Linien hinzufügt. Dazu sind lediglich die Anfangs- und Endpunkte zu holen, ein neues Linienobjekt anzulegen und dann das Objekt in das Objektarray einzufügen. Fügen Sie eine neue Member-Funktion in die Klasse CTag10Doc ein. Legen Sie den Funktionstyp als CLine* , die Deklaration mit AddLine(CPoint ptFrom, CPoint ptTo) und den Zugriffsstatus als Public fest. Schreiben Sie den Code aus Listing 10.3 in die Funktion.

Listing 10.3: Die Funktion AddLine der Klasse CTag10Doc

1: CLine * CTag10Doc::AddLine(CPoint ptFrom, CPoint ptTo)
2: {
3: // Ein neues CLine-Objekt erzeugen
4: CLine *pLine = new CLine(ptFrom, ptTo);
5: try
6: {
7: // Die neue Linie in das Objektarray einfügen
8: m_oaLines.Add(pLine);
9: // Dokument als bearbeitet markieren
10: SetModifiedFlag();
11: }
12: // Ist Speicherausnahme aufgetreten?
13: catch (CMemoryException* perr)
14: {
15: // Meldung für Benutzer, schlechte Neuigkeiten
16: // mitteilen.
17: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
18: // Wurde Linienobjekt erzeugt?
19: if (pLine)
20: {
21: // Objekt löschen
22: delete pLine;
23: pLine = NULL;
24: }
25: // Ausnahmeobjekt löschen
26: perr->Delete();
27: }
28: return pLine;
29: }

Auf den ersten Blick handelt es sich um eine unkomplizierte Funktion. Als erstes legt sie eine neue Instanz von CLine an und übergibt an den Konstruktor die Anfangs- und Endpunkte als Argumente. Unmittelbar darauf erscheint die folgende interessante Konstruktion, die den Programmablauf steuert:

try
{
.
.
.
}
catch (...)
{
.
.
.
}

Was soll das bedeuten? Diese Konstruktion ist ein Beispiel der strukturierten Ausnahmebehandlung. Bestimmte Codeabschnitte könnten aufgrund eines unvorhergesehenen Umstandes scheitern, beispielsweise wegen Speichermangels oder unzureichendem Platz auf dem Datenträger. Den problematischen Codeabschnitt können Sie in einen try-Block einschließen. Diesem sollten sich immer ein oder mehrere catch- Blöcke anschließen. Wenn ein Problem während der Codeausführung im try-Block auftritt, springt das Programm unmittelbar zu den catch-Blöcken. Jeder catch-Block legt fest, welchen Ausnahmetyp er behandelt (im speziellen Fall der Funktion AddLine behandelt er lediglich Ausnahmen infolge Speichermangels). Gibt es für das aufgetretene Problem einen entsprechenden catch-Block, wird dieser Block ausgeführt und bietet der Anwendung die Möglichkeit, Gegenmaßnahmen einzuleiten. Wenn kein catch-Block für den aufgetretenen Problemtyp vorhanden ist, springt das Programm zu einer Standardbehandlungsroutine für Ausnahmen - die wahrscheinlich Ihre Anwendung herunterfährt. Weitere Informationen zur strukturierten Ausnahmebehandlung finden Sie im Anhang A.

Innerhalb des try-Blocks fügen Sie die neue CLine-Instanz in das Array der Linienobjekte ein. Als nächstes rufen Sie die Funktion SetModifiedFlag auf, um das Dokument als bearbeitet (noch nicht gespeichert) zu markieren. Wenn der Benutzer die Anwendung schließt oder ein anderes Dokument öffnen, ohne zuerst die aktuelle Zeichnung zu speichern, fordert ihn die Anwendung auf, die aktuelle Zeichnung zu speichern (mit dem bekannten Meldungsfeld Ja, Nein, Abbrechen).

Im catch-Block informieren Sie den Benutzer, daß das System keinen Speicher mehr zur Verfügung hat, und löschen dann das CLine-Objekt und das Ausnahmeobjekt.

Schließlich gibt die Funktion das CLine-Objekt an die aufrufende Routine zurück. Damit kann das Ansichtsobjekt das Linienobjekt veranlassen, sich selbst zu zeichnen.

Die Linienanzahl ermitteln

Als nächstes Element nehmen Sie in die Dokumentklasse eine Funktion auf, die die Anzahl der Linien im Dokument zurückgibt. Diese Funktionalität ist erforderlich, da das Ansichtsobjekt in einer Schleife das Linienarray durchgehen muß, um jedes Linienobjekt anzuweisen, sich selbst zu zeichnen. Das Ansichtsobjekt muß die Gesamtzahl der Linien im Dokument bestimmen und jede gewünschte Linie aus dem Dokument zurückgeben können.

Um die Anzahl der Linien im Dokument zurückzugeben, ermittelt man die Anzahl der Linien im Objektarray, so daß man einfach den Rückgabewert der Methode GetSize der Klasse CObArray zurückgeben kann. Fügen Sie dazu eine neue Elementfunktion in die Klasse CTag10Doc ein. Legen Sie den Typ als int und die Deklaration als GetLineCount mit Zugriffsstatus Public fest. In die Funktion übernehmen Sie den Code aus Listing 10.4.

Listing 10.4: Die Funktion GetLineCount der Klasse CTag10Doc

1: int CTag10Doc::GetLineCount()
2: {
3: // Größe des Arrays zurückgeben
4: return m_oaLines.GetSize();
5: }

Eine bestimmte Linie abrufen

Schließlich brauchen Sie noch eine Funktion, die eine bestimmte Linie aus dem Dokument liefert. Dazu gibt man einfach das Objekt an der angegebenen Position im Objektarray zurück. Fügen Sie eine neue Elementfunktion in die Klasse CTag10Doc ein. Legen Sie den Typ als CLine*, die Deklaration als GetLine(int nIndex) und den Zugriff mit Public fest. In die Funktion schreiben Sie den Code aus Listing 10.5.

Listing 10.5: Die Funktion GetLine der Klasse CTag10Doc

1: CLine* CTag10Doc::GetLine(int nIndex)
2: {
3: // Zeiger auf Linienobjekt für die angegebene
4: // Position im Objektarray zurückgeben
5: return (CLine*)m_oaLines[nIndex];
6: }

Beachten Sie, daß das zurückzugebende Objekt in den Typ eines CLine-Objekts umzuwandeln ist. Da die Klasse CObArray ein Array von CObjects ist, stellt jedes vom Array gelieferte Element eine Instanz von CObject und nicht eine Instanz eines CLine-Objekts dar.

Die Zeichnung anzeigen

Die Dokumentklasse verfügt nun über Fähigkeit, die Zeichnung zu speichern. Als nächstes sind im Ansichtsobjekt die Benutzereingaben zu lesen und dann anzuzeigen. Die Mausereignisse zur Übernahme der Benutzereingaben sind fast identisch zu denen, die Sie letzte Woche erstellt haben. Der zweite Teil der Funktionalität betrifft die Ausgabe der Zeichnung. Dazu erweitern Sie eine bereits vorhandene Funktion in der Ansichtsobjektklasse.

Bevor Sie diese Funktionen hinzufügen, brauchen Sie noch eine Elementvariable in der Klasse CTag10View, um die vorherige Position des Mauszeigers festzuhalten (genau wie in der Anwendung der letzten Woche). Fügen Sie über den Arbeitsbereich eine Elementvariable in die Klasse CTag10View ein. Legen Sie den Typ als CPoint, den Namen als m_ptPrevPos und den Zugriff als Privat fest.

Die Mausereignisse hinzufügen

Die Benutzereingaben fangen Sie mit Mausereignissen ab. Öffnen Sie dazu den Klassen-Assistenten, und fügen Sie der Klasse CTag10View Funktionen für die Nachrichten WM_LBUTTONDOWN, WM_LBUTTONUP und WM_MOUSEMOVE hinzu. Den Code für diese Funktionen finden Sie in Listing 10.6.

Listing 10.6: Die Mausfunktionen der Klasse CTag10View

1: void CTag10View::OnLButtonDown(UINT nFlags, CPoint point)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Maus auffangen, damit sie keine andere Anwendung
10: // fangen kann, wenn sie den Fensterbereich verläßt
11: SetCapture();
12: // Punkte sichern
13: m_ptPrevPos = point;
14:
15: ///////////////////////
16: // EIGENER CODE, ENDE
17: ///////////////////////
18:
19: CView::OnLButtonDown(nFlags, point);
20: }
21:
22: void CTag10View::OnLButtonUp(UINT nFlags, CPoint point)
23: {
24: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
25:
26: ///////////////////////
27: // EIGENER CODE, ANFANG
28: ///////////////////////
29:
30: // Wurde Maus aufgefangen?
31: if (GetCapture() == this)
32: // Wenn ja, freigeben, damit sie anderen
33: // Anwendungen zur Verfügung steht.
34: ReleaseCapture();
35:
36: ///////////////////////
37: // EIGENER CODE, ENDE
38: ///////////////////////
39:
40: CView::OnLButtonUp(nFlags, point);
41: }
42:
43: void CTag10View::OnMouseMove(UINT nFlags, CPoint point)
44: {
45: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
46:
47: ///////////////////////
48: // EIGENER CODE, ANFANG
49: ///////////////////////
50:
51: // Prüfen, ob linke Maustaste gedrückt
52: if ((nFlags & MK_LBUTTON) == MK_LBUTTON)
53: {
54: // Wurde Maus aufgefangen?
55: if (GetCapture() == this)
56: {
57: // Gerätekontext holen
58: CClientDC dc(this);
59:
60: // Die Linie ins Dokument aufnehmen
61: CLine *pLine = GetDocument()->AddLine(m_ptPrevPos, point);
62:
63: // Aktuellen Linienzug zeichnen
64: pLine->Draw(&dc);
65:
66: // Aktuellen Punkt als vorherigen speichern
67: m_ptPrevPos = point;
68: }
69: }
70:
71: ///////////////////////
72: // EIGENER CODE, ENDE
73: ///////////////////////
74:
75: CView::OnMouseMove(nFlags, point);
76: }

Die Funktion OnLButtonDown ruft zuerst die Funktion SetCapture auf. Diese Funktion »fängt« die Maus und verhindert damit, daß irgendwelche anderen Anwendungen irgendwelche Mausereignisse erhalten, selbst wenn die Maus den Fensterbereich der Anwendung verläßt. Damit kann der Benutzer die Maus aus dem Anwendungsfenster herausziehen und dann wieder in das Fenster zurückführen, ohne die Zeichnung unterbrechen zu müssen. Alle Mausnachrichten gelangen zu dieser Anwendung, bis die Maus in der Funktion OnLButtonUp durch Aufruf der Funktion ReleaseCapture freigegeben wird. In der Zeit dazwischen können Sie ermitteln, ob die Anwendung die Maus gefangen hat, indem Sie die Funktion GetCapture in einer if-Anweisung einsetzen und den Rückgabewert der Funktion mit this vergleichen. Wenn die Maus gefangen wurde, führen Sie den übrigen Code in diesen Funktionen aus, andernfalls finden keine weiteren Aktionen statt.

Die Funktion OnMouseMove erzeugt zuerst den Gerätekontext und realisiert dann mehrere Dinge in einer einzigen Codezeile:

CLine *pLine = GetDocument()->AddLine(m_ptPrevPos, point);

Diese Zeile erzeugt einen neuen Zeiger auf eine Instanz der Klasse CLine. Als nächstes wird die Funktion GetDocument aufgerufen, die einen Zeiger auf das Dokumentobjekt zurückgibt. Mit diesem Zeiger ruft die Funktion OnMouseMove die Funktion AddLine der Dokumentklasse auf und übergibt die vorherigen und aktuellen Punkte als Argumente. Mit dem Rückgabewert der Funktion AddLine wird der Objektzeiger von CLine initialisiert. Der CLine-Zeiger läßt sich nun verwenden, um die Funktion Draw des Linienobjekts aufzurufen.

Ein Zeiger ist die Adresse eines Objekts. Mit Zeigern lassen sich Objekte in einem Programm effizienter übergeben. Wenn man einen Zeiger auf ein Objekt statt das Objekt selbst übergibt, verhält sich das genauso, wie wenn man sagt »Die Fernbedienung liegt auf dem Sofa zwischen dem zweiten und dritten Kissen neben dem herausgefallenen Kleingeld«, statt jemandem die Fernbedienung direkt in die Hand zu drücken. Gibt man die eigentliche Fernbedienung jemand anderem, muß man - um im Programmierjargon zu sprechen - eine genaue Kopie der Fernbedienung erstellen und die Kopie an die andere Person aushändigen. Offensichtlich ist es einfacher, jemandem nur die Stelle zu sagen, wo sich die Fernbedienung finden läßt, anstatt eine genaue Kopie der Fernbedienung zu bauen.

Die Notation -> bedeutet, daß der Zugriff auf die Funktionen oder Eigenschaften eines Objekts über einen Zeiger erfolgt. Mit einem Punkt (.) kennzeichnet man dagegen den Zugriff über das Objekt selbst.

Die Zeichnung darstellen

In der Ansichtsklasse wird die Funktion OnDraw immer dann aufgerufen, wenn das für den Benutzer ausgegebene Bild neu zu zeichnen ist. Es kann zum Beispiel sein, daß ein anderes Fenster vor dem Anwendungsfenster gelegen hat, das Fenster vom minimierten Zustand gerade wiederhergestellt worden ist oder ein neues Dokument aus einer Datei geladen wurde. Warum die Ansicht neu zu zeichnen ist, spielt keine Rolle. Als Anwendungsentwickler müssen Sie sich nur darum kümmern, den Code in die Funktion OnDraw aufzunehmen, um das Dokument wiederzugeben, für dessen Erstellung Ihre Anwendung vorgesehen ist.

Gehen Sie zur Funktion OnDraw in der Klasse CTag10View, und fügen Sie den Code aus Listing 10.7 ein.

Listing 10.7: Die Funktion OnDraw der Klasse CTag10View

1: void CTag10View::OnDraw(CDC* pDC)
2: {
3: CTag10Doc* pDoc = GetDocument();
4: ASSERT_VALID(pDoc);
5:
6: // ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten Âhinzufügen
7:
8: ///////////////////////
9: // EIGENER CODE, ANFANG
10: ///////////////////////
11:
12: // Anzahl der Linien im Dokument ermitteln
13: int liCount = pDoc->GetLineCount();
14:
15: // Gibt es Linien im Dokument?
16: if (liCount)
17: {
18: int liPos;
19: CLine *lptLine;
20:
21: // Schleife durch die Linien des Dokuments
22: for (liPos = 0; liPos < liCount; liPos++)
23: {
24: // Anfangs- und Endpunkte für alle Zeilen holen
25: lptLine = pDoc->GetLine(liPos);
26: // Linie zeichnen
27: lptLine->Draw(pDC);
28: }
29: }
30:
31: ///////////////////////
32: // EIGENER CODE, ENDE
33: ///////////////////////
34: }

Die Funktion ermittelt zuerst, wie viele Linien im Dokument zu zeichnen sind. Gibt es keine Linien, ist auch nichts zu tun. Sind im Dokument Linien gespeichert, geht die Funktion in einer for-Schleife durch die Linien, holt jede Linie aus dem Dokument und ruft dann die Funktion Draw des Linienobjekts auf.

Bevor Sie Ihre Anwendung kompilieren und ausführen können, müssen Sie noch die Header-Datei für die Klasse CLine in die Quellcodedatei für die Dokument- und Ansichtsklassen einbinden. Fügen Sie dazu in beide Dateien (Tag10Doc.cpp und Tag10View.cpp) die Datei Line.h als Include-Datei hinzu, wie es Listing 10.8 zeigt.

Listing 10.8: Die Include-Anweisungen von CTag10Doc

1: #include "stdafx.h"
2: #include "Tag10.h"
3: #include "MainFrm.h"
4: #include "Line.h"
5: #include "Tag10Doc.h"

Jetzt sollten Sie Ihre Anwendung kompilieren und ausführen können. Es ist nun möglich, Figuren zu zeichnen, wie es Abbildung 10.4 zeigt. Wenn Sie das Fenster minimieren und dann wiederherstellen, oder wenn Sie das Fenster einer anderen Anwendung vor das Fenster Ihrer Anwendung legen, sollte Ihre Zeichnung wieder erscheinen, sobald das Fenster Ihrer Anwendung wieder sichtbar ist (im Gegensatz zur Anwendung, die Sie vor einer Woche erstellt haben).

Abbildung 10.4:
Mit der Anwendung zeichnen

Die Zeichnung speichern und laden

Nachdem Ihre Gemälde nun nicht mehr verschwinden, sobald Sie sie aus den Augen lassen, wäre es schön, wenn man sie auch für die Ewigkeit festhalten könnte. Im Menü Datei Ihrer Anwendung sind bereits die Menübefehle Öffnen, Speichern und Speichern unter vorhanden, die aber momentan noch nichts bewirken. Die Menübefehle zum Drucken funktionieren zwar schon, aber die Befehle zum Speichern und Laden einer Zeichnung noch nicht. Nicht einmal beim Menübefehl Neu passiert etwas. Machen wir uns also an die Arbeit.

Die aktuelle Zeichnung löschen

Wenn Sie die Klasse CTag10Doc untersuchen, fällt Ihnen sicherlich die Funktion OnNewDocument auf, in der Sie den Code einbauen könnten, um die aktuelle Zeichnung zu löschen. Weit gefehlt! Diese Funktion ist dafür vorgesehen, alle Klasseneinstellungen zu initialisieren, um mit der Arbeit an einer neuen Zeichnung zu beginnen, und nicht um eine vorhandene Zeichnung zu löschen. Statt dessen müssen Sie den Klassen-Assistenten öffnen und eine Funktion für die Nachricht DeleteContents hinzufügen. Diese Nachricht ist dafür vorgesehen, den aktuellen Inhalt der Dokumentklasse zu löschen. Nehmen Sie in die neue Funktion den Code aus Listing 10.9 auf.

Listing 10.9: Die Funktion DeleteContents der Klasse CTag10Doc

1: void CTag10Doc::DeleteContents()
2: {
3: // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Anzahl der Linien im Objektarray ermitteln
10: int liCount = m_oaLines.GetSize();
11: int liPos;
12:
13: // Gibt es Objekte im Array?
14: if (liCount)
15: {
16: // Schleife durch Array, dabei jedes Objekt löschen
17: for (liPos = 0; liPos < liCount; liPos++)
18: delete m_oaLines[liPos];
19: // Array zurücksetzen
20: m_oaLines.RemoveAll();
21: }
22:
23: ///////////////////////
24: // EIGENER CODE, ENDE
25: ///////////////////////
26:
27: CDocument::DeleteContents();
28: }

Die Funktion geht in einer Schleife durch das Objektarray und löscht alle Linienobjekte im Array. Nachdem alle Linien gelöscht sind, setzt die Funktion das Array durch Aufruf der Methode RemoveAll zurück. Wenn Sie die Anwendung kompilieren und ausführen, können Sie den Befehl Datei / Neu wählen, und wenn Sie sich nicht dafür entscheiden, die aktuelle Zeichnung zu speichern, wird der Fensterinhalt gelöscht.

Die Zeichnung speichern und wiederherstellen

Die Funktionalität zum Speichern und Wiederherstellen der Zeichnungen läßt sich relativ einfach implementieren, ist aber vielleicht nicht ohne weiteres verständlich. Momentan soll das nicht weiter stören, in drei Tagen führen Sie sich eine ganze Lektion zu Gemüte, die sich mit dem Speichern und Wiederherstellen von Dateien - der sogenannten Serialisierung - beschäftigt. Suchen Sie zunächst die Funktion Serialize in der Klasse CTag10Doc. Die Funktion sieht folgendermaßen aus:

1: void CTag10Doc::Serialize(CArchive& ar)
2: {
3: if (ar.IsStoring())
4: {
5: // ZU ERLEDIGEN: Hier Code zum Speichern einfügen
6: }
7: else
8: {
9: // ZU ERLEDIGEN: Hier Code zum Laden einfügen
10: }
11: }

Löschen Sie den gesamten Inhalt der Funktion, und nehmen Sie dafür den Code aus Listing 10.10 auf.

Listing 10.10: Die Funktion Serialize der Klasse CTag10Doc

1: void CTag10Doc::Serialize(CArchive& ar)
2: {
3: ///////////////////////
4: // EIGENER CODE, ANFANG
5: ///////////////////////
6:
7: // Serialisierung an Objektarray weitergeben
8: m_oaLines.Serialize(ar);
9:
10: ///////////////////////
11: // EIGENER CODE, ENDE
12: ///////////////////////
13: }

Die Funktion stützt sich auf die Funktionalität der Klasse CObArray. Dieses Objektarray reicht sein Array von Objekten nach unten weiter und ruft dabei die Funktion Serialize auf jedem Objekt auf. Das bedeutet, daß Sie eine Funktion Serialize in die Klasse CLine aufnehmen müssen. Legen Sie den Funktionstyp als void und die Deklaration mit Serialize(CArchive& ar) fest. In die Funktion schreiben Sie den Code aus Listing 10.11.

Listing 10.11: Die Funktion Serialize der Klasse CLine

1: void CLine::Serialize(CArchive &ar)
2: {
3: CObject::Serialize(ar);
4:
5: if (ar.IsStoring())
6: ar << m_ptFrom << m_ptTo;
7: else
8: ar >> m_ptFrom >> m_ptTo;
9: }

Die Funktion weist grundsätzlich den gleichen Programmfluß auf, wie die ursprüngliche Version der Serialize-Funktion in der Klasse CTag10Doc. Die Funktion greift auf die Funktionalität der E/A-Streams von C++ zurück, um die Inhalte zu speichern und wiederherzustellen.

Wenn Sie die Anwendung zum jetzigen Zeitpunkt kompilieren und ausführen, erwarten Sie sicherlich, daß die Funktionen zum Speichern und Öffnen arbeiten. Leider tun sie das aber (noch) nicht. Wenn Sie die Anwendung ausführen und versuchen, eine Zeichnung zu speichern, weist Sie ein Meldungsfeld darauf hin, daß die Anwendung die Datei nicht speichern konnte (siehe Abbildung 10.5).

Abbildung 10.5:
Die Zeichnungen lassen sich momentan noch nicht speichern.

Der Grund für das Scheitern liegt darin, daß man Visual C++ mitteilen muß, daß eine Klasse serialisierbar sein soll. Zu diesem Zweck fügen Sie eine Zeile in die Header-Datei und eine Zeile in die Quellcodedatei der Klasse CLine ein. Öffnen Sie die Header- Datei der Klasse CLine (Line.h), und schreiben Sie die Zeile DECLARE_SERIAL gemäß Listing 10.12 unmittelbar nach der ersten Zeile der Klassendefinition.

Listing 10.12: Die Header-Datei Line.h für Serialisierung angepaßt

1: class CLine : public CObject
2: {
3: DECLARE_SERIAL (CLine)
4: public:
5: void Serialize(CArchive& ar);
6: void Draw(CDC *pDC);
7: CLine(CPoint ptFrom, CPoint ptTo);

Als nächstes öffnen Sie die Quellcodedatei von CLine und fügen die Zeile IMPLEMENT_SERIAL gemäß Listing 10.13 unmittelbar vor die Konstruktorfunktionen der Klasse ein.

Listing 10.13: Die Quellcodedatei Line.cpp für Serialisierung angepaßt

1: // Line.cpp: Implementierung der Klasse CLine.
2: //
3: //////////////////////////////////////////////////////////////////////
4:
5: #include "stdafx.h"
6: #include "Tag10.h"
7: #include "Line.h"
8:
9: #ifdef _DEBUG
10: #undef THIS_FILE
11: static char THIS_FILE[]=__FILE__;
12: #define new DEBUG_NEW
13: #endif
14:
15: IMPLEMENT_SERIAL (CLine, CObject, 1)
16: //////////////////////////////////////////////////////////////////////
17: // Konstruktion/Destruktion
18: //////////////////////////////////////////////////////////////////////
19:
20: CLine::CLine()
21: {
22:
23: }

Wenn Sie die Anwendung jetzt kompilieren und ausführen, sollten Sie Ihr Selbstporträt zeichnen und für die Ewigkeit speichern können (siehe Abbildung 10.6).

Abbildung 10.6:
Ein Selbstporträt

Interaktion mit Menüs

Nachdem Sie nun über ein funktionsfähiges Zeichenprogramm verfügen, wäre es schön, wenn der Benutzer die Farbe auswählen könnte, mit der er zeichnen will. Dazu sind Änderungen an der Klasse CLine erforderlich, um die Farbe mit der Linie zu verbinden, sowie an der Klasse CTag10Doc, um die momentan ausgewählte Farbe zu verwalten. Schließlich ist noch ein Pulldown-Menü vorzusehen, aus dem sich die gewünschte Farbe auswählen läßt.

Farben in die Klasse CLine aufnehmen

Die Änderungen in der Klasse CLine sind recht unkompliziert. Als erstes fügen Sie eine weitere Member-Variable hinzu, um die Farbe jeder Linie zu speichern. Als nächstes modifizieren Sie den Klassenkonstruktor, um die Liste der Attribute durch die übergebene Farbe zu erweitern. Drittens ist die Funktion Draw zu modifizieren, damit diese die angegebene Farbe verwendet. Und schließlich müssen Sie noch an der Funktion Serialize arbeiten, um die Farbinformation zusammen mit den Punktkoordinaten speichern und wiederherstellen zu können. Führen Sie dazu folgende Schritte aus:

1. Markieren Sie im Arbeitsbereich auf der Registerkarte Klassen die Klasse CLine. Klicken Sie mit der rechten Maustaste, und wählen Sie den Befehl Member-Variable hinzufügen aus dem Kontextmenü.

2. Legen Sie den Variablentyp als COLORREF, den Namen als m_crColor und den Zugriff als Privat fest. Klicken Sie auf OK, um die Variable hinzuzufügen.

3. Klicken Sie mit der rechten Maustaste in der Baumansicht der Registerkarte Klassen auf den Konstruktor von CLine. Aus dem Kontextmenü wählen Sie den Befehl Gehe zu Deklaration.

4. Fügen Sie COLORREF crColor als drittes Argument in die Konstruktordeklaration ein.

5. Klicken Sie mit der rechten Maustaste in der Baumansicht der Registerkarte Klassen auf den Konstruktor von CLine. Aus dem Kontextmenü wählen Sie den Befehl Gehe zu Definition.

6. Modifizieren Sie den Konstruktor gemäß Listing 10.14, um das dritte Argument hinzuzufügen und das Element m_crColor auf das neue Argument zu setzen.

Listing 10.14: Der modifizierte Konstruktor von CLine

1: CLine::CLine(CPoint ptFrom, CPoint ptTo, COLORREF crColor)
2: {
3: // Anfangs- und Endpunkte initialisieren
4: m_ptFrom = ptFrom;
5: m_ptTo = ptTo;
6: m_crColor = crColor;
7: }

7. Gehen Sie nach unten zur Funktion Draw, und nehmen Sie die Änderungen gemäß Listing 10.15 vor.

Listing 10.15: Die modifizierte Funktion Draw

1: void CLine::Draw(CDC * pDC)
2: {
3: // Stift erzeugen
4: CPen lpen (PS_SOLID, 1, m_crColor);
5:
6: // Neuen Stift als Zeichenobjekt festlegen
7: CPen* pOldPen = pDC->SelectObject(&lpen);
8: // Linie zeichnen
9: pDC->MoveTo(m_ptFrom);
10: pDC->LineTo(m_ptTo);
11: // Vorherigen Stift zurücksetzen
12: pDC->SelectObject(pOldPen);
13: }

8. Blättern Sie weiter nach unten zur Funktion Serialize, und ändern Sie die Funktion entsprechend Listing 10.16 ab.

Listing 10.16: Die modifizierte Funktion Serialize

1: void CLine::Serialize(CArchive &ar)
2: {
3: CObject::Serialize(ar);
4:
5: if (ar.IsStoring())
6: ar << m_ptFrom << m_ptTo << (DWORD) m_crColor;
7: else
8: ar >> m_ptFrom >> m_ptTo >> (DWORD) m_crColor;
9: }

Bei diesen Schritten fällt eigentlich nur auf, daß man den Rückgabewert der Funktion SelectObject übernimmt, wenn man den Zeichenstift für die Linien spezifiziert. Diesen Schritt haben Sie letzte Woche nicht ausgeführt. Der Rückgabewert der Funktion SelectObject ist der Stift, der benutzt wurde, bevor Sie ihn geändert haben. Damit läßt sich der ursprüngliche Stift im Gerätekontext wiederherstellen, wenn der Benutzer die Zeichnung mit dem neuen Stift beendet hat.

Farbige Dokumente

Die an der Klasse CTag10Doc vorzunehmenden Änderungen sind nur etwas umfangreicher als die an der Klasse CLine. Es ist eine Elementvariable für die aktuelle Farbe hinzuzufügen sowie eine Farbtabelle, um die Farb-IDs in RGB-Werte zu konvertieren. In der Funktion OnNewDocument ist die Variable für die aktuelle Farbe zu initialisieren. Dann müssen Sie die Funktion AddLine modifizieren, um die aktuelle Farbe in den Konstruktor von CLine hinzuzufügen. Schließlich nehmen Sie noch eine Funktion auf, um die aktuelle Farbe zurückzugeben. Das ist momentan alles, bis Sie die Behandlungsroutinen für die Menübefehle in Angriff nehmen. Führen Sie zunächst die folgenden Schritte aus:

1. Markieren Sie im Arbeitsbereich auf der Registerkarte Klassen die Klasse CTag10Doc. Klicken Sie mit der rechten Maustaste, und wählen Sie den Befehl Member-Variable hinzufügen aus dem Kontextmenü.

2. Legen Sie den Variablentyp als UINT, den Namen mit m_nColor und den Zugriff als Privat fest. Klicken Sie auf OK, um die Variable hinzuzufügen.

3. Wiederholen Sie Schritt 1.

4. Legen Sie den Variablentyp als static const COLORREF, den Namen als m_crColors[8] und den Zugriff als Public fest.

5. Öffnen Sie den Quellcode von CTag10Doc (Datei Tag10Doc.cpp), und fügen Sie die Farbtabelle gemäß Listing 10.17 ein.

Listing 10.17: Die Spezifikation der Farbtabelle

1: //}}AFX_MSG_MAP
2: END_MESSAGE_MAP()
3:
4: const COLORREF CTag10Doc::m_crColors[8] = {
5: RGB( 0, 0, 0), // Schwarz
6: RGB( 0, 0, 255), // Blau
7: RGB( 0, 255, 0), // Grün
8: RGB( 0, 255, 255), // Cyan
9: RGB( 255, 0, 0), // Rot
10: RGB( 255, 0, 255), // Magenta
11: RGB( 255, 255, 0), // Gelb
12: RGB( 255, 255, 255) // Weiß
13: };
14:
15: ///////////////////////////////////////////////////////////////////////////
16: // CTag10Doc Konstruktion/Destruktion
17:
18: CTag10Doc::CTag10Doc()
19: .
20: .
21: .
22: }

6. Blättern Sie nach unten zur Funktion OnNewDocument, und nehmen Sie die Änderungen gemäß Listing 10.18 vor.

Listing 10.18: Die modifizierte Funktion OnNewDocument

1: BOOL CTag10Doc::OnNewDocument()
2: {
3: if (!CDocument::OnNewDocument())
4: return FALSE;
5:
6: // ZU ERLEDIGEN: Hier Code zur Reinitialisierung einfügen
7: // (SDI-Dokumente verwenden dieses Dokument)
8:
9: ///////////////////////
10: // EIGENER CODE, ANFANG
11: ///////////////////////
12:
13: // Farbe mit Schwarz initialisieren
14: m_nColor = ID_COLOR_BLACK - ID_COLOR_BLACK;
15:
16: ///////////////////////
17: // EIGENER CODE, ENDE
18: ///////////////////////
19:
20: return TRUE;
21: }

7. Gehen Sie weiter zur Funktion AddLine, und passen Sie die Funktion entsprechend Listing 10.19 an.

Listing 10.19: Die modifizierte Funktion AddLine

1: CLine* CTag10Doc::AddLine(CPoint ptFrom, CPoint ptTo)
2: {
3: // Ein neues CLine-Objekt erzeugen
4: CLine *pLine = new CLine(ptFrom, ptTo, m_crColors[m_nColor]);
5: try
6: {
7: // Die neue Linie in das Objektarray einfügen
8: m_oaLines.Add(pLine);
9: // Dokument als bearbeitet markieren
10: SetModifiedFlag();
11: }
12: // Ist Speicherausnahme aufgetreten?
13: catch (CMemoryException* perr)
14: {
15: // Meldung für Benutzer, schlechte Neuigkeiten
16: // mitteilen
17: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
18: // Wurde Linienobjekt erzeugt?
19: if (pLine)
20: {
21: // Objekt löschen
22: delete pLine;
23: pLine = NULL;
24: }
25: // Ausnahmeobjekt löschen
26: perr->Delete();
27: }
28: return pLine;
29: }

8. Fügen Sie der Klasse CTag10doc eine neue Member-Funktion hinzu. Legen Sie den Funktionstyp als UINT, die Deklaration mit GetColor und den Zugriff als Public fest.

9. In die Funktion GetColor schreiben Sie den Code aus Listing 10.20.

Listing 10.20: Die Funktion GetColor

1: UINT CTag10Doc::GetColor()
2: {
3: // Aktuelle Farbe zurückgeben
4: return ID_COLOR_BLACK + m_nColor;
5: }

Die Funktionen OnNewDocument und GetColor führen eine Subtraktion bzw. Addition der Farbe mit dem Wert ID_COLOR_BLACK durch. Der Wert ID_COLOR_BLACK ist die kleinste ID des durchnumerierten Farbmenüs, wenn Sie die Menübefehle hinzufügen. Diese Berechnungen verwalten die Variable als Zahl zwischen 0 und 7, während diese Funktionen beim Umgang mit den Menüs wieder Vergleiche mit den eigentlichen Menü-IDs zulassen.

Das Menü modifizieren

Nun kommt der interessantere Teil. In das Hauptmenü fügen Sie ein neues Pulldown- Menü hinzu. In dieses Menü nehmen Sie Befehle für alle Farben der Farbtabelle auf. Für die Menübefehle müssen Sie noch Behandlungsroutinen erstellen, um den Befehl für die momentan ausgewählte Farbe mit einem Kontrollhäkchen zu versehen. Das Ganze realisieren Sie in folgenden Schritten:

1. Gehen Sie im Arbeitsbereich auf die Registerkarte Ressourcen. Erweitern Sie den Baum, so daß der Inhalt des Ordners Menu zu sehen ist. Doppelklicken Sie auf die Menüressource.

2. Ziehen Sie das leere Menükästchen (am rechten Ende der Menüleiste) nach links, und legen Sie es vor dem Menüeintrag Ansicht ab.

3. Öffnen Sie das Dialogfeld Eigenschaften für den leeren Menüeintrag. Legen Sie den Titel mit &Farben fest. Schließen Sie den Eigenschaftsdialog.

4. Fügen Sie die Untermenüeinträge in das Menü Farbe hinzu. Erstellen Sie die Untermenüeinträge in der Reihenfolge von Tabelle 10.2, und verwenden Sie die dort angegebenen Eigenschaften. Das fertige Menü sollte dann Abbildung 10.7 entsprechen.

Abbildung 10.7:
Das Menü Farbe in der Entwurfsansicht

Tabelle 10.2: Eigenschaftseinstellungen des Menüs

Objekt

Eigenschaft

Einstellung

Menübefehl

ID

Titel

ID_COLOR_BLACK

&Schwarz

Menübefehl

ID

Titel

ID_COLOR_BLUE

&Blau

Menübefehl

ID

Titel

ID_COLOR_GREEN

Gr&ün

Menübefehl

ID

Titel

ID_COLOR_CYAN

&Cyan

Menübefehl

ID

Titel

ID_COLOR_RED

&Rot

Menübefehl

ID

Titel

ID_COLOR_MAGENTA

&Magenta

Menübefehl

ID

Titel

ID_COLOR_YELLOW

&Gelb

Menübefehl

ID

Titel

ID_COLOR_WHITE

&Weiß

5. Öffnen Sie den Klassen-Assistenten. Markieren Sie CTag10Doc im Kombinationsfeld Klassenname.

6. Fügen Sie für die beiden Nachrichten COMMAND und UPDATE_COMMAND_UI Funktionen für alle Befehle des Farbmenüs hinzu.

7. Nachdem Sie die Funktion für den letzten Menübefehl hinzugefügt haben, klicken Sie auf Code bearbeiten.

8. Bearbeiten Sie die Funktionen des Menüs Schwarz gemäß Listing 10.21.

Listing 10.21: Die Funktionen des Menüs Schwarz

1: void CTag10Doc::OnColorBlack()
2: {
3: // TODO: Code für Befehlsbehandlungsroutine hier einfügen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Aktuelle Farbe auf Schwarz setzen
10: m_nColor = ID_COLOR_BLACK - ID_COLOR_BLACK;
11:
12: ///////////////////////
13: // EIGENER CODE, ENDE
14: ///////////////////////
15: }
16:
17: void CTag10Doc::OnUpdateColorBlack(CCmdUI* pCmdUI)
18: {
19: // TODO: Code für die Befehlsbehandlungsroutine zum Aktualisieren der ÂBenutzeroberfläche hier einfügen
20:
21: ///////////////////////
22: // EIGENER CODE, ANFANG
23: ///////////////////////
24:
25: // Prüfen, ob Menübefehl Schwarz mit Kontrollhäkchen zu versehen ist
26: pCmdUI->SetCheck(GetColor() == ID_COLOR_BLACK ? 1 : 0);
27:
28: ///////////////////////
29: // EIGENER CODE, ENDE
30: ///////////////////////
31: }

9. Bearbeiten Sie die Funktionen des Menüs Blau gemäß Listing 10.22. Die restlichen Menüfunktionen behandeln Sie in der gleichen Weise, wobei Sie für ID_COLOR_BLUE die jeweilige Menü-ID einsetzen.

Listing 10.22: Die Funktionen des Menüs Blau

1: void CTag10Doc::OnColorBlue()
2: {
3: // TODO: Code für Befehlsbehandlungsroutine hier einfügen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Aktuelle Farbe auf Blau setzen
10: m_nColor = ID_COLOR_BLUE - ID_COLOR_BLACK;
11:
12: ///////////////////////
13: // EIGENER CODE, ENDE
14: ///////////////////////
15: }
16:
17: void CTag10Doc::OnUpdateColorBlue(CCmdUI* pCmdUI)
18: {
19: // TODO: Code für die Befehlsbehandlungsroutine zum Aktualisieren der ÂBenutzeroberfläche hier einfügen
20:
21: ///////////////////////
22: // EIGENER CODE, ANFANG
23: ///////////////////////
24:
25: // Prüfen, ob Menübefehl Blau mit Kontrollhäkchen zu versehen ist
26: pCmdUI->SetCheck(GetColor() == ID_COLOR_BLUE ? 1 : 0);
27:
28: ///////////////////////
29: // EIGENER CODE, ENDE
30: ///////////////////////
31: }

In der ersten der beiden Menüfunktionen, der COMMAND-Funktion, wird die Variable der aktuellen Farbe auf die neue Farbe gesetzt. Wenn Sie die Menübefehle in der richtigen Reihenfolge hinzugefügt haben, weisen sie fortlaufende ID-Nummern auf, die mit ID_COLOR_BLACK beginnen. Zieht man den Wert ID_COLOR_BLACK von der Menü-ID ab, erhält man dann die Position in der Farbtabelle für die ausgewählte Farbe. Beispielsweise hat Schwarz die Position 0 in der Farbtabelle. ID_COLOR_BLACK - ID_COLOR_BLACK = 0. Blau steht auf der Position 1 in der Farbtabelle. Da ID_COLOR_BLUE um 1 größer als ID_COLOR_BLACK sein sollte, liefert ID_COLOR_BLUE - ID_COLOR_BLACK das Ergebnis 1.

Die zweite Funktion, die Funktion UPDATE_COMMAND_UI, erfordert eine nähere Erläuterung. Das Ereignis UPDATE_COMMAND_UI wird für jeden Menübefehl aufgerufen, unmittelbar bevor dieser angezeigt wird. Mit dieser Behandlungsfunktion können Sie in Abhängigkeit von der aktuellen Farbe neben dem betreffenden Menübefehl ein Kontrollhäkchen setzen oder dieses entfernen. Mit diesem Ereignis lassen sich auch Menübefehle aktivieren oder deaktivieren oder bei Bedarf andere Modifikationen realisieren. Der Code in dieser Funktion

pCmdUI->SetCheck(GetColor() == ID_COLOR_BLUE ? 1 : 0);

bewirkt mehreres. Erstens ist das als einziges Argument übergebene pCmdUI-Objekt ein Zeiger auf ein Menüobjekt. Die Funktion SetCheck kann das Kontrollhäkchen vor den Menübefehl setzen oder entfernen, je nachdem, ob das übergebene Argument 1 oder 0 ist (1 setzt das Kontrollhäkchen, 0 entfernt es). Der Argumentabschnitt für die Funktion SetCheck stellt eine Konstruktion zur Programmsteuerung dar, die Ihnen vielleicht etwas verwirrend vorkommt, wenn Sie noch nicht allzuviel Zeit in die Programmierung mit C/C++ investiert haben. Die erste Hälfte

GetColor() == ID_COLOR_BLUE

ist eine einfache Boolesche Bedingungsanweisung, die entweder TRUE (wahr) oder FALSE (falsch) liefert. Der sich an diese Bedingungsanweisung anschließende Teil

? 1 : 0

stellt grundsätzlich eine if...else-Anweisung dar, allerdings in Kurzform. Wenn die Bedingungsanweisung TRUE liefert, dann ist der Wert gleich 1, und wenn die Anweisung FALSE ergibt, dann gilt der Wert 0. Damit läßt sich auf geschickte Weise eine if...else-Steuerstruktur im Argument an eine andere Funktion übergeben.

Nachdem Sie die Anwendung kompiliert und gestartet haben, sollten Sie die Zeichenfarbe ändern können. Wenn Sie das Farbmenü öffnen, sollte die aktuelle Zeichenfarbe mit einem Kontrollhäkchen versehen sein, wie es Abbildung 10.8 zeigt.

Abbildung 10.8:
Die aktuelle Farbe im Menü festlegen.

Zusammenfassung

Was für ein Tag! Diese Lektion war mit Informationen voll gepackt. Zuerst wurde gezeigt, was eine SDI-Anwendung ist und welche bekannten Standardanwendungen in diesem Stil aufgebaut sind. Als nächstes haben Sie die Dokument/Ansicht-Architektur kennengelernt, die Visual C++ für SDI-Anwendungen verwendet. Sie haben eine eigene einfache Klasse erstellt, die sich für eine Zeichenanwendung eignet. Dann haben Sie eine Zeichenanwendung erstellt, mit der Sie Bilder zeichnen und verwalten konnten. Es wurde gezeigt, wie man Dokumente in der Dokument/Ansicht-Architektur speichert und wiederherstellt. Weiterhin wurde die Klasse CObArray behandelt und wie man sie einsetzen kann, um ein dynamisches Objektarray für das Speichern verschiedenartiger Klassen zu erzeugen. Schließlich haben Sie gelernt, wie man Kontrollhäkchen in Menübefehlen von MFC-Anwendungen setzt und entfernt.

Fragen und Antworten

Frage:
Gibt es eine Möglichkeit, die Anzahl der Funktionen für die Nachrichten COMMAND und UPDATE_COMMAND_UI für die Menüs zu verringern?

Antwort:
Ja. Man kann alle Ereignisse des Farbmenüs an dieselbe Funktion senden. In dieser Funktion läßt sich der Wert nID (der als Argument übergeben wird) untersuchen und mit den Menü-IDs vergleichen, um zu ermitteln, welcher Menübefehl die Funktion aufgerufen hat. Letztendlich kann man die COMMAND- Funktion für die Farbmenüs wie folgt formulieren:

void CTag10Doc::OnColorCommand(UINT nID)
{
// TODO: Code für Befehlsbehandlungsroutine hier einfügen

///////////////////////
// EIGENER CODE, ANFANG
///////////////////////

// Aktuelle Farbe setzen
m_nColor = nID - ID_COLOR_BLACK;

///////////////////////
// EIGENER CODE, ENDE
///////////////////////
}

Bei den UPDATE_COMMAND_UI-Funktionen kann man analog vorgehen, allerdings mit einem kleinen Unterschied. Bei diesen Funktionen kann man den Wert pCmdUI->m_nID untersuchen, um zu bestimmen, für welchen Menübefehl die Funktion aufgerufen wurde. Damit sieht die Funktion UPDATE_COMMAND_UI folgendermaßen aus:

void CTag10Doc::OnUpdateColor(CCmdUI* pCmdUI)
{
// TODO: Code für die Befehlsbehandlungsroutine zum Aktualisieren der Benutzeroberfläche hier einfügen

///////////////////////
// EIGENER CODE, ANFANG
///////////////////////

// Prüfen, ob Menübefehl mit Kontrollhäkchen zu versehen ist
pCmdUI->SetCheck(GetColor() == pCmdUI->m_nID ? 1 : 0);

///////////////////////
// EIGENER CODE, ENDE
///////////////////////
}

Frage:
Worin unterscheiden sich SDI- und MDI-Anwendungen?

Antwort:
Während SDI-Anwendungen nur eine Aufgabe ausführen können, lassen sich bei MDI-Anwendungen (MDI - Multiple Document Interface) mehrere Dokumente gleichzeitig öffnen. Darüber hinaus müssen in einer MDI-Anwendung die Dokumente nicht alle vom gleichen Typ sein. Mehr über MDI-Anwendungen lernen Sie morgen.

Workshop

Kontrollfragen

1. Wofür steht SDI?

2. Welche Funktionalität ist in der Ansichtsklasse realisiert?

3. Welche Funktion wird aufgerufen, um das Dokument neu zu zeichnen, wenn das Fenster hinter einem anderen Fenster verborgen war?

4. Wo bringt man den Code unter, um das aktuelle Dokument zu löschen, bevor man mit einem neuen Dokument beginnt?

5. Welchen Zweck erfüllt die Dokumentklasse?

Übung

Fügen Sie ein weiteres Pulldown-Menü hinzu, um die Breite des Zeichenstifts zu steuern. Verwenden Sie die Einstellungen gemäß Tabelle 10.3:

Tabelle 10.3: Menü für Auswahl der Stiftbreite

Menüeintrag

Breite

Sehr dünn

1

Dünn

8

Mittel

16

Dick

24

Sehr dick

32

Das zweite Argument des Stiftkonstruktors gibt die Breite an.



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