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 ...
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:
CWinApp
abgeleitete Klasse
CFrameView
abgeleitete Klasse
CDocument
abgeleitete Klasse
CView
abgeleitete Klasse
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.
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
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 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.
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.
Abbildung 10.3:
Warnung, daß Header-Dateien der Basisklassen nicht gefunden werden konnten
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.
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.
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
.
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.
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: }
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: }
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 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.
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
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.
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 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
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.
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.
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.
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.
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
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.
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.
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.
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?
Fügen Sie ein weiteres Pulldown-Menü hinzu, um die Breite des Zeichenstifts zu steuern. Verwenden Sie die Einstellungen gemäß Tabelle 10.3: