vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 16

Eigene Klassen und Module

Manchmal ist eine Gruppe von Funktionen zu erstellen, die in einer Anwendung, an der ein anderer Programmierer arbeitet, zum Einsatz kommen. Es kann sein, daß die Funktionen in einer ganzen Reihe von Anwendungen benötigt werden. Weiterhin ist es möglich, daß Sie eine bestimmte Funktionsgruppe aus organisatorischen Gründen von der übrigen Anwendung abtrennen möchten. Diese funktionellen Einheiten können Sie dann separat entwickeln und den Code an die betreffenden Mitarbeiter weitergeben. Das hat allerdings den Nachteil, daß Änderungen, die Sie an der Funktionsgruppe vornehmen, auch noch in den bereits weitergegebenen Code eingebunden werden müssen. Es wäre praktischer, wenn man eine kompilierte Version der Funktionsgruppe an den anderen Programmierer weitergeben könnte, so daß Sie bei einer Aktualisierung auf Ihrer Seite lediglich die neu kompilierte Datei aushändigen müßten. Die neue Datei könnte einfach die vorherige Version ersetzen, ohne daß der andere Programmierer seinen bisher erstellten Code ändern muß.

Es ist natürlich möglich, einen ganzen Satz von Funktionalität in eine eigenständig kompilierte Datei zu verpacken, diese zur Anwendung eines anderen Programmierers zu linken und damit das Hinzufügen irgendwelcher neuer Dateien zur fertiggestellten Anwendung zu vermeiden. Heute lernen Sie, wie man ...

Klassen entwerfen

In den vergangenen Tagen haben Sie bereits eigene Klassen entworfen und erstellt, so daß dieses Thema nicht grundsätzlich neu für Sie ist. Warum haben Sie diese Klassen erzeugt? Die neu erzeugten Klassen haben alle eine Gruppe von Funktionalität verkapselt, die als selbständige Einheit agiert. Diese Einheiten bestehen sowohl aus Daten als auch als Funktionen, die in ihrer Gesamtheit das Objekt definieren.

Kapselung

Der objektorientierte Softwareentwurf verfolgt das Konzept, nach dem alles in der uns umgebenden Welt aufgebaut ist. Betrachten Sie zum Beispiel ein Auto, das aus einer Sammlung von Objekten besteht: Motor, Karosserie, Radaufhängung usw. Jedes dieser Objekte besteht aus einem Bündel anderer Objekte. Beispielsweise enthält der Motor entweder den Vergaser oder die Einspritzventile, die Verbrennungskammer und die Kolben, den Anlasser, die Lichtmaschine, den Keilriemen usw. Auch hier bestehen die einzelnen Objekte wiederum aus noch mehr Objekten.

Alle diese Objekte haben eine Funktion, die sie ausführen. Jedes Objekt weiß, wie die eigenen Funktionen auszuführen sind, ohne oder nur mit geringen Kenntnissen, wie die anderen Objekte ihre Funktionen wahrnehmen. Jedes Objekt weiß, wie es mit den anderen Objekten in Wechselwirkung treten kann und wie es mit den anderen Objekten verbunden ist, mehr ist aber nicht über die anderen Objekte bekannt. Wie jedes dieser Objekte intern arbeitet, bleibt vor den anderen Objekten verborgen. Die Bremsen im Auto wissen zum Beispiel nichts darüber, wie das Getriebe funktioniert, bei einem Automatikgetriebe wissen aber die Bremsen, wie sie dem Getriebe mitteilen, daß sie aktiv sind, und das Getriebe entscheidet, wie auf diese Informationen zu reagieren ist.

Auf die gleiche Weise gehen Sie den Entwurf neuer Klassen für Ihre Anwendungen an. Die übrigen Anwendungsobjekte brauchen nicht zu wissen, wie Ihre Objekte arbeiten, sie müssen nur wissen, wie sie mit Ihren Objekten in Wechselwirkung treten. Diese sogenannte Kapselung gehört zu den Grundprinzipien der objektorientierten Software.

Vererbung

Ein weiteres Schlüsselprinzip des objektorientierten Softwareentwurfs ist die Vererbung . Ein Objekt kann von einem anderen Objekt geerbt sein. Das abgeleitete Objekt erbt die gesamte Funktionalität des Basisobjekts. Damit läßt sich das abgeleitete Objekt in Form von Änderungen gegenüber dem Basisobjekt definieren.

Sehen wir uns dieses Konzept am Beispiel eines Thermostaten an. Nehmen wir einen einfachen Thermostaten an, den Sie in nahezu jeder Einstellung verwenden können. Es läßt sich die Temperatur vorgeben, die der Thermostat regeln soll. Zu diesem Zweck schaltet er die Heizung oder Klimaanlage ein. Jetzt wollen wir einen Thermostat für einen Gefrierschrank erstellen. Man könnte nun einen eigens entwickelten Thermostaten von Grund auf neu aufbauen - oder man nimmt den vorhandenen Thermostaten und legt fest, wie sich die Gefrierschrankversion vom Original unterscheidet. Zu diesen Unterschieden kann gehören, daß der Thermostat nur noch die Klimaanlage einschalten muß und die Heizung überhaupt nicht mehr. Vielleicht möchten Sie auch den Temperaturbereich streng eingrenzen, auf den sich der Thermostat einstellen läßt, beispielsweise auf Temperaturen unterhalb 0º Celsius. Wenn Sie analog dazu einen Thermostat für eine Büroheizung brauchen, grenzen Sie wahrscheinlich den Temperaturbereich auf die üblichen Raumtemperaturen ein und erlauben keine Werte für extreme Kälte oder Hitze.

Das gleiche Prinzip verfolgen Sie, wenn Sie eigene Klassen per Vererbung erstellen. Nach Möglichkeit sollten Sie mit einer vorhandenen C++-Klasse, die bereits die erforderliche Basisfunktionalität mitbringt, beginnen. Dann programmieren Sie, wie sich Ihre Klasse von der Basisklasse, von der Sie erben, unterscheidet. Dabei können Sie neue Datenelemente hinzufügen, die vorhandene Funktionalität erweitern oder vorhandene Funktionen überschreiben, wie es gerade in Ihr Konzept paßt.

Klassentypen in Visual C++

Wenn Sie eine neue Klasse erzeugen, stehen Ihnen in den meisten Anwendungsprojekten verschiedene Optionen zum Typ der zu erzeugenden Klasse zur Verfügung. Zu diesen Optionen gehören:

Von welchem Typ Sie Ihre Klasse erzeugen, hängt von Ihren Anforderungen und den Aufgaben der Klasse ab. Weiterhin ist zu beachten, ob die Klasse von einer MFC-Klasse abzuleiten ist.

Allgemeine Klassen

Aus einer allgemeinen (oder generischen) Klasse erstellen Sie eine Klasse, die von einer bereits erstellten Klasse abgeleitet ist. Mit diesem Klassentyp erzeugen Sie in erster Linie Klassen, die von keiner MFC-Klasse abgeleitet sind (obwohl Sie bereits gesehen haben, wo man mit diesem Typ Klassen erstellt, die auf MFC-Klassen basieren). Wenn Sie eine speziellere Version der Klasse CLine erstellen möchten, zum Beispiel eine Klasse CRedLine, die nur in Rot zeichnet, erzeugen Sie eine allgemeine Klasse, weil sie von einer anderen Klasse, die Sie erzeugt haben, abgeleitet ist.

Wenn Sie eine allgemeine Klasse erzeugen, versucht der neue Klassen-Assistent (nicht zu verwechseln mit dem Klassen-Assistenten, der auf MFC-Klassen beschränkt ist) die Deklaration der Basisklasse - die Header-Datei mit der deklarierten Klasse - zu lokalisieren. Wenn der Assistent die entsprechende Header-Datei nicht finden kann, teilt er Ihnen in einem Meldungsfeld mit, daß Sie die Header-Datei mit der Definition der Basisklasse selbst in das Projekt einbinden müssen. Wenn es sich bei der Basisklasse um eine MFC-Klasse handelt, die nicht als MFC-Klasse zugänglich ist (wie etwa CObject ), dann können Sie diese Warnung ignorieren, da die korrekte Header-Datei bereits Teil des Projekts ist.

MFC-Klassen

Wenn Sie eine wiederverwendbare Klasse erstellen möchten, die auf einer vorhandenen MFC-Klasse aufbaut, wie etwa ein Eingabefeld, das automatisch Zahlen als Währung formatiert, erstellen Sie eine MFC-Klasse. Die Option MFC-Klasse dient der Erstellung neuer Klassen, die von existierenden MFC-Klassen abgeleitet sind.

Formularklassen

Die Formularklasse ist ein spezialisierter Typ von MFC-Klasse. Diesen Klassentyp müssen Sie erzeugen, wenn Sie ein neues Formularfenster erstellen. Dabei kann es sich um eine Ansichtsklasse für ein Dialogfeld, ein Formular oder eine Datenbank handeln. Diese neue Klasse wird mit einer Dokumentklasse verbunden, die zusammen mit der Ansichtsklasse zum Einsatz kommt. Wenn Sie eine Datenbankanwendung erstellen, werden Sie wahrscheinlich eine Reihe derartiger Klassen erzeugen.

Bibliotheksmodule erstellen

Wenn Sie neue Klassen für Ihre Anwendung erstellen, können diese Klassen auch in anderen Anwendungen nützlich sein. Oftmals lassen sich die zu erzeugenden Klassen mit etwas Nachdenken und geringem Mehraufwand so flexibel gestalten, daß man sie in anderen Anwendungen einsetzen kann. In diesem Fall brauchen Sie eine Möglichkeit, um die Klassen für andere Anwendungen zusammenzupacken, ohne dabei den Quellcode aus der Hand geben zu müssen. Für diesen Zweck sind Bibliotheksmodule prädestiniert. Hiermit können Sie Ihre Klassen und Module in eine kompilierte Objektcodebibliothek kompilieren, die sich mit jeder anderen Visual C++-Anwendung linken läßt.

Bibliotheksmodule waren als eines der ersten Mittel verfügbar, um anderen Programmierern für deren Anwendungen kompilierten Code bereitzustellen. Der Linker kombiniert den Code mit dem Code der übrigen Anwendung im letzten Schritt beim Erstellungsvorgang. Bibliotheksmodule sind auch heute noch ein probates Mittel, um Module mit anderen Entwicklern gemeinsam nutzen zu können. Der Entwickler benötigt lediglich die Bibliotheksdatei (mit der Erweiterung .lib) und die entsprechenden Header-Dateien, die alle freigelegten Klassen, Methoden, Funktionen und Variablen zeigen, auf die der andere Programmierer zugreifen kann. Am einfachsten läßt sich das bewerkstelligen, indem man dieselbe Header-Datei, die man für das Erstellen der Bibliotheksdatei verwendet, bereitstellt. Man kann die Header-Datei aber auch bearbeiten, damit nur die von anderen Programmierern benötigten Teile eingebunden sind.

Wenn Sie auf Bibliotheksdateien zurückgreifen, um Module mit anderen Programmierern gemeinsam zu nutzen, ist praktisch vereinbart, daß Ihr Teil der Anwendung in derselben ausführbaren Datei wie die übrige Anwendung eingebunden ist. Ihre Module sind nicht in einer separaten Datei enthalten, wie etwa bei einer DLL oder einem ActiveX-Steuerelement. Dadurch ist eine Datei weniger mit der Anwendung zu vertreiben. Es bedeutet auch, daß bei allen Änderungen am Modul - bei der Beseitigung von Fehlern oder der Erweiterung der Funktionalität - die Anwendung, die das Modul verwendet, neu gelinkt werden muß. Gegenüber DLLs sind Bibliotheksdateien etwas im Nachteil. Eine neue DLL können Sie ohne weiteres vertreiben, ohne daß irgendwelche Änderungen an der Anwendung erforderlich sind. Aber darauf gehen wir morgen ein.

Bibliotheksmodule einsetzen

Um eine Vorstellung davon zu bekommen, wie man Bibliotheksmodule verwendet, erstellen Sie heute ein Bibliotheksmodul, verwenden es in einer anderen Anwendung und nehmen dann einige Modifikationen am Bibliotheksmodul vor. Für die heutige Beispielanwendung erstellen Sie ein Modul, das eine Zufallszeichnung im angegebenen Fensterbereich generiert. Es wird möglich sein, alle diese Zeichnungen zu speichern und wiederherzustellen. Dann verwenden Sie dieses Modul in einer SDI-Anwendung, wo immer dann, wenn man ein neues Dokument anlegt, eine neue Zeichnung generiert wird. Das anfängliche Modul verwendet nur acht Farben und erzeugt nur eine begrenzte Zahl von Linienfolgen. Später modifizieren Sie das Modul, so daß es eine beliebige Anzahl von Farben verwendet und eine größere Anzahl von Liniensequenzen generiert.

Das Bibliotheksmodul erstellen

Um das Projekt eines Bibliotheksmoduls zu erstellen, wählen Sie auf der Registerkarte Projekte des Dialogfelds Neu den Eintrag Win32-Bibliothek (statische), wie es Abbildung 16.1 zeigt. Damit teilen Sie Visual C++ mit, daß das Kompilat des Projekts ein Bibliotheksmodul und keine ausführbare Anwendung ist. Von da an brauchen Sie nur noch die Klassen zu definieren und den Code hinzuzufügen. Im einzigen Dialogfeld des Projektassistenten haben Sie die Wahl, Unterstützung für MFC einzubinden und vorkompilierte Header in Ihrem Projekt zu verwenden (siehe Abbildung 16.2).

Abbildung 16.1:
Das Projekt eines Bibliotheksmoduls spezifizieren

Die Bibliothek, die Sie für die heutige Beispielanwendung erstellen, besteht aus zwei Klassen. Die erste Klasse ist die Klasse CLine, die Sie bereits am Tag 10 erstellt haben. Die zweite Klasse erzeugt zufällige Zeichnungen im Zeichenbereich. Diese Klasse enthält ein Objektarray von CLine-Objekten, die die Klasse erzeugt und mit jedem neuen Zeichenversuch füllt. Die zweite Klasse muß auch über die Möglichkeit verfügen, die Zeichnung zu speichern und wiederherzustellen sowie die vorhandene Zeichnung zu löschen, damit eine neue Zeichnung begonnen werden kann. Die Klasse muß die Abmessungen des Zeichenbereichs kennen, damit sie eine Zeichnung generieren kann, die in den Zeichenbereich paßt. Nachdem Sie dieses Modul erstellt haben, sehen Sie sich an, wie es sich in einem Anwendungsprojekt einsetzen läßt.

Abbildung 16.2:
Unterstützungsoptionen für das Projekt festlegen

Ein Bibliotheksprojekt erstellen

Um das Bibliotheksprojekt für das heutige Beispiel zu beginnen, legen Sie ein neues Projekt vom Typ Win32-Bibliothek (statische) an. Geben Sie dem Projekt einen passenden Namen, und klicken Sie auf OK, um das Projekt zu erzeugen.

Im ersten und einzigen Schritt des Assistenten legen Sie fest, daß sowohl MFC-Unterstützung als auch vorkompilierte Header einzubinden sind. Obwohl die Unterstützung für vorkompilierte Header nicht erforderlich ist, beschleunigt das in der Regel den Erstellungsvorgang des Moduls.

Nach diesen Schritten haben Sie ein Projekt vor sich, das keinerlei Klassen enthält. Sie haben einen Rohdiamanten, aus dem Sie nach Belieben die verschiedensten Modultypen erzeugen können.

Da Sie bereits die Klasse CLine erstellt haben, kopieren Sie sie für das heutige Beispielprojekt aus dem Projektverzeichnis von Tag 10 in das Projektverzeichnis für das heutige Projekt. Nehmen Sie sowohl die Header-Datei als auch die Quellcodedatei in das heutige Projekt auf. Wählen Sie dazu Projekt / Dem Projekt hinzufügen / Dateien . Wenn Sie beide Dateien dem Projekt hinzugefügt haben, sollte die Klasse CLine in der Klassenansicht des Projekts erscheinen.

Klassen definieren

Nachdem Sie nun über ein einsatzbereites Gerüst eines Bibliotheksmoduls verfügen, ist das Modul noch mit Leben zu erfüllen. Mit der Klasse CLine bietet sich eine gute Möglichkeit, eine bereits mit anderen Vorgaben realisierte Funktionalität wiederzuverwenden. Allerdings soll die eigentliche Funktionalität des Moduls darin bestehen, zufällige Zeichnungen - oder Squiggles - zu erzeugen. Für diese Funktionalität benötigen Sie eine neue Klasse.

Um eine neue Klasse in das Projekt einzufügen, klicken Sie mit der rechten Maustaste auf der Registerkarte Klassen und wählen Neue Klasse aus dem Kontextmenü. Als erstes fällt im Dialogfeld Neue Klasse auf, daß als Klassentyp lediglich Allgemeine Klasse zur Verfügung steht. Da Sie eine statische Bibliothek erzeugen, die mit der Anwendung gelinkt wird, trifft Visual C++ einige Annahmen über den Klassentyp, den Sie erstellen möchten. Da es sich nicht um ein MFC-Projekt handelt, auch wenn Sie die MFC-Unterstützung eingebunden haben, läßt sich keine neue MFC- oder Formularklasse erzeugen. Wollen Sie eine neue Klasse von einer MFC-Klasse ableiten, müssen Sie sie so hinzufügen, als ob es eine allgemeine Klasse wäre.

Über das Dialogfeld Neue Klasse legen Sie Ihre neue Klasse an. Geben Sie der Klasse einen Namen, der ihre Funktionalität widerspiegelt, beispielsweise CModArt, und legen Sie fest, daß sie von der Klasse CObjekt als public abgeleitet wird. Sie erhalten nun die gleiche Warnung, daß der neue Klassen-Assistent die Header-Dateien der Basisklasse nicht finden konnte. Weil Sie aber die MFC-Unterstützung eingebunden haben, können Sie diese Meldung ignorieren.

Nachdem Sie Ihre Klasse angelegt haben, müssen Sie noch ein paar Variablen in die Klasse aufnehmen. Zuerst ist dafür Sorge zu tragen, daß alle Linien, aus denen die Zeichnung besteht, gespeichert werden können. Zu diesem Zweck legen Sie ein Objektarray an. Zweitens müssen Sie die Abmessungen des Zeichenbereichs kennen. Die entsprechenden Angaben speichern Sie in einer CRect-Struktur. Fügen Sie beide Variablen in Ihre neue Klasse ein, und verwenden Sie dabei die Einstellungen gemäß Tabelle 16.1.

Tabelle 16.1: Die Variablen von CModArt

Typ

Name

Zugriff

static const COLORREF

m_crColors[8]

Public

CRect

m_rDrawArea

Privat

CObArray

m_oaLines

Privat

Den Zeichenbereich einrichten

Bevor man überhaupt etwas zeichnen kann, muß man den verfügbaren Zeichenbereich kennen. Dazu fügen Sie in Ihre Klasse eine öffentliche Funktion ein, die die übergebene CRect-Struktur in die Elementvariable CRect kopiert. Für diese Funktion legen Sie den Typ als void, die Deklaration mit SetRect(CRect rDrawArea) und den Zugriff als Public fest. In die Funktion schreiben Sie den Code gemäß Listing 16.1.

Listing 16.1: Die Funktion SetRect der Klasse CModArt

1: void CModArt::SetRect(CRect rDrawArea)
2: {
3: // Rechteck für Zeichenbereich festlegen
4: m_rDrawArea = rDrawArea;
5: }

Eine neue Zeichnung erstellen

Eines der Schlüsselmerkmale dieses Moduls ist die Fähigkeit, zufällige Squiggles zu generieren, die im Zeichenbereich erscheinen. Durch das Generieren einer ganzen Folge dieser Squiggles kann das Modul eine vollständige Zeichnung erzeugen. Beginnend mit einem einzelnen Squiggle können Sie eine Funktion entwerfen, die ein Squiggle generiert, und dann diese Funktion mehrmals aufrufen, um die gesamte Zeichnung zu erstellen.

Die erste Funktion, der Squiggle-Generator, muß ermitteln, wie viele Linien zu einem Squiggle gehören. Außerdem sind die Farbe und Breite des Zeichenstifts für das Squiggle zu bestimmen. Weiterhin braucht die Funktion den Anfangspunkt des Squiggles. Von diesem Punkt aus kann die Funktion in einer Schleife die entsprechende Anzahl von Linien durchlaufen und jeweils einen neuen Zielpunkt generieren, um das Squiggle vom vorherigen Zielpunkt fortzusetzen.

Um diese Funktionalität in Ihrem Projekt zu realisieren, fügen Sie eine neue Member- Funktion in die Zeichenklasse ein. Legen Sie den Funktionstyp als void und die Definition mit NewLine fest. Als Zugriffsstatus wählen Sie die Option Privat, da die Funktion nur durch die Hauptschleife aufgerufen wird, die bestimmt, wie viele Squiggles in der endgültigen Zeichnung enthalten sein sollen. In die neue Funktion übernehmen Sie den Code aus Listing 16.2.

Listing 16.2: Die Funktion NewLine der Klasse CModArt

1: void CModArt::NewLine()
2: {
3: int lNumLines;
4: int lCurLine;
5: int nCurColor;
6: UINT nCurWidth;
7: CPoint pTo;
8: CPoint pFrom;
9:
10: // Rechteck normalisieren, dann erst Breite und Höhe bestimmen
11: m_rDrawArea.NormalizeRect();
12: // Breite und Höhe des Zeichenbereichs ermitteln
13: int lWidth = m_rDrawArea.Width();
14: int lHeight = m_rDrawArea.Height();
15:
16: // Anzahl der Teile dieses Squiggles bestimmen
17: lNumLines = rand() % 100;
18: // Umfaßt das Squiggle mindestens ein Teil?
19: if (lNumLines > 0)
20: {
21: // Farbe bestimmen
22: nCurColor = rand() % 8;
23: // Stiftbreite bestimmen
24: nCurWidth = (rand() % 8) + 1;
25: // Anfangspunkt für Squiggle bestimmen
26: pFrom.x = (rand() % lWidth) + m_rDrawArea.left;
27: pFrom.y = (rand() % lHeight) + m_rDrawArea.top;
28: // Schleife durch Anzahl der Segmente
29: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
30: {
31: // Endpunkt des Segments bestimmen
32: pTo.x = ((rand() % 20) - 10) + pFrom.x;
33: pTo.y = ((rand() % 20) - 10) + pFrom.y;
34: // Neues CLine-Objekt erzeugen
35: CLine *pLine = new CLine(pFrom, pTo, m_crColors[nCurColor], ÂnCurWidth);
36: try
37: {
38: // Neue Linie in das Objektarray hinzufügen
39: m_oaLines.Add(pLine);
40: }
41: // Speicherausnahme?
42: catch (CMemoryException* perr)
43: {
44: // Meldung an Benutzer mit schlechten
45: // Neuigkeiten
46: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
47: // Wurde ein Linienobjekt erzeugt?
48: if (pLine)
49: {
50: // Löschen
51: delete pLine;
52: pLine = NULL;
53: }
54: // Ausnahmeobjekt löschen
55: perr->Delete();
56: }
57: // Anfangspunkt auf Endpunkt setzen
58: pFrom = pTo;
59: }
60: }
61: }

Die Funktion ermittelt zuerst mit den folgenden drei Zeilen den verfügbaren Zeichenbereich:

m_rDrawArea.NormalizeRect();
int lWidth = m_rDrawArea.Width();
int lHeight = m_rDrawArea.Height();

Die erste Zeile normalisiert das Rechteck, damit garantiert ist, daß die in den nächsten beiden Zeilen zurückgegebenen Werte für Breite und Höhe positiv sind. Aufgrund des in Windows verwendeten Koordinatensystems kann das Ermitteln der Breite durch Subtraktion der linksseitigen Position von der rechtsseitigen Position zu negativen Zahlen führen. Das gleiche trifft auf die Höhe zu. Durch die Normalisierung des Rechtecks ist sichergestellt, daß man für die beiden Werte positive Ergebnisse erhält.

Nachdem der Zeichenbereich ermittelt ist, bestimmt die Funktion die Anzahl der Linienabschnitte, die im Squiggle zu verwenden sind:

lNumLines = rand() % 100;

Die Funktion rand kann Zahlen in einem weiten Bereich zurückgeben. Durch die Modulo-Division mit 100 stellt man sicher, daß die resultierende Zahl zwischen 0 und 100 liegt. Dieses Verfahren wendet man im allgemeinen an, um Zufallszahlen für einen bestimmten Bereich zu erzeugen, wobei man in der Modulo-Funktion die Obergrenze des Wertebereichs angibt. (Ist die Untergrenze ungleich 0, gibt man in der Modulo-Funktion das Ergebnis aus Untergrenze minus der Obergrenze an und addiert den Wert der Untergrenze auf das Ergebnis der Modulo-Rechnung.) Nach dem gleichen Verfahren bestimmen Sie die Farbe, Breite und Startposition für das Squiggle:

nCurColor = rand() % 8;
nCurWidth = (rand() % 8) + 1;
pFrom.x = (rand() % lWidth) + m_rDrawArea.left;
pFrom.y = (rand() % lHeight) + m_rDrawArea.top;

Beim Berechnen der Startposition wird die linke und obere Position des Zeichenbereichs zur generierten Position addiert. Damit ist sichergestellt, daß der Startpunkt innerhalb des Zeichenbereichs liegt. Sobald die Funktion in die Schleife eingetreten ist und alle Linienabschnitte im Squiggle generiert, wird der verfügbare Bereich für den nächsten Zielpunkt auf 10 von der aktuellen Position aus begrenzt:

pTo.x = ((rand() % 20) - 10) + pFrom.x;
pTo.y = ((rand() % 20) - 10) + pFrom.y;
CLine *pLine = new CLine(pFrom, pTo, m_crColors[nCurColor], nCurWidth);
m_oaLines.Add(pLine);

Diesen Abstand können Sie in einfacher Weise erhöhen, um die Zeichnung weiträumiger zu gestalten. Nachdem die Funktion das nächste Liniensegment generiert hat, erzeugt sie das Linienobjekt und fügt es in das Objektarray ein. Schließlich setzt die Funktion den Anfangspunkt auf den Endpunkt des eben generierten Liniensegments:

pFrom = pTo;

Nun kann die Funktion die Schleife erneut durchlaufen und das nächste Liniensegment generieren, bis alle Liniensegmente in diesem Squiggle generiert sind.

Nachdem Sie nun ein einzelnes Squiggle erzeugen können, ist der verbleibende Rest einfach zu erledigen. Zuerst ermittelt man, aus wie vielen Squiggles die Zeichnung bestehen soll. Als nächstes durchläuft man eine Schleife für die Anzahl der zu generierenden Squiggles und ruft in jedem Durchlauf die Funktion NewLine einmal für jedes Squiggle auf. Um diese Funktionalität im Beispielprojekt zu realisieren, fügen Sie in die Zeichenklasse eine neue Member-Funktion ein. Legen Sie den Typ als void, die Deklaration mit NewDrawing und den Zugriff als Public fest. In die Funktion schreiben Sie den Code aus Listing 16.3.

Listing 16.3: Die Funktion NewDrawing der Klasse CModArt

1: void CModArt::NewDrawing()
2: {
3: int lNumLines;
4: int lCurLine;
5:
6: // Anzahl der zu erzeugenden Linien bestimmen
7: lNumLines = rand() % 10;
8: // Sind Linien zu erzeugen?
9: if (lNumLines > 0)
10: {
11: // Schleife durch Anzahl der Linien
12: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
13: {
14: // Die neue Linie erzeugen
15: NewLine();
16: }
17: }
18: }

Die Zeichnung anzeigen

Um die Gruppe der Squiggles im Zeichenbereich darzustellen, können Sie eine Funktion hinzufügen, die das Objektarray in einer Schleife durchläuft und dabei die Funktion Draw für jedes Liniensegment im Array aufruft. Diese Funktion muß den Gerätekontext als einziges Argument erhalten und es an die einzelnen Liniensegmente weiterreichen. Fügen Sie dazu der Zeichenklasse eine neue Funktion hinzu. Legen Sie den Funktionstyp als void, die Funktionsdeklaration mit Draw(CDC *pDC) und den Zugriff als Public fest. In die Funktion übernehmen Sie den Code aus Listing 16.4.

Listing 16.4: Die Funktion Draw der Klasse CModArt

1: void CModArt::Draw(CDC *pDC)
2: {
3: // Anzahl der Liinien im Objektarray ermitteln
4: int liCount = m_oaLines.GetSize();
5: int liPos;
6:
7: // Enthält das Array Objekte?
8: if (liCount)
9: {
10: // Schleife durch Array, dabei jedes Objekt zeichnen
11: for (liPos = 0; liPos < liCount; liPos++)
12: ((CLine*)m_oaLines[liPos])->Draw(pDC);
13: }
14: }

Die Zeichnung serialisieren

Da Sie auf die Liniensegmentklasse zurückgreifen, die Sie bereits an früherer Stelle erzeugt und serialisierbar gemacht haben, brauchen Sie keine Makros für die Serialisierung in die Zeichenklasse einzubinden. Allerdings müssen Sie eine Serialize-Funktion hinzufügen, die das Archivobjekt an das Objektarray weiterreicht und dem Objektarray sowie den Liniensegmentobjekten die Serialisierung überträgt. Zu diesem Zweck fügen Sie eine neue Member-Funktion in die Zeichenklasse ein. Legen Sie den Funktionstyp mit void, die Deklaration als Serialize(CArchive &ar) und den Zugriff als Public fest. In die Funktion schreiben Sie den Code gemäß Listing 16.5.

Listing 16.5: Die Funktion Serialize der Klasse CModArt

1: void CModArt::Serialize(CArchive &ar)
2: {
3: // Archivobjekt an Array übergeben
4: m_oaLines.Serialize(ar);
5: }

Die Zeichnung löschen

Um die volle Funktionalität bereitzustellen, müssen Sie in der Lage sein, die Zeichnung aus der Zeichenklasse zu löschen, so daß sich eine neue Zeichnung generieren oder eine vorhandene Zeichnung laden läßt. Dazu durchläuft man einfach das Objektarray, zerstört alle Liniensegmentobjekte und setzt dann das Objektarray zurück. Nehmen Sie also eine weitere Member-Funktion in das Projekt auf. Spezifizieren Sie den Typ als void, die Deklaration als ClearDrawing und den Zugriff als Public. Übernehmen Sie in diese Funktion den Code aus Listing 16.6.

Listing 16.6: Die Funktion ClearDrawing der Klasse CModArt

1: void CModArt::ClearDrawing()
2: {
3: // Anzahl der Linien im Objektarray bestimmen
4: int liCount = m_oaLines.GetSize();
5: int liPos;
6:
7: // Enthält das Array Objekte?
8: if (liCount)
9: {
10: // Schleife durch das Array, dabei alle Objekte löschen
11: for (liPos = 0; liPos < liCount; liPos++)
12: delete m_oaLines[liPos];
13: // Array zurücksetzen
14: m_oaLines.RemoveAll();
15: }
16: }

Die Klasse fertigstellen

Um die Zeichenklasse zu komplettieren, ist noch der Zufallszahlengenerator zu initialisieren. Die als Zufallszahlengenerator arbeitende Funktion rand generiert eine statistisch zufällige Zahlenfolge, die auf einer Reihe von mathematischen Berechnungen beruht. Wenn man den Zufallszahlengenerator jedesmal mit derselben Zahl startet, erhält man immer wieder die gleiche Zahlenfolge. Damit der Zufallszahlengenerator bei jedem Start der Anwendung eine anderen Zufallszahlenfolge liefert, muß man ihm einen Anfangswert übergeben, der jedesmal unterschiedlich ist. Normalerweise übergibt man zu diesem Zweck die Systemzeit an die Funktion srand, die den Zufallszahlengenerator bei jedem Start der Anwendung mit einer anderen Zeit initiiert. Den Zufallszahlengenerator muß man nur einmal während der Ausführung einer Anwendung initialisieren, so daß man diese Funktionalität im Konstruktor der Zeichenklasse mit dem Code gemäß Listing 16.7 unterbringen kann.

Listing 16.7: Der Konstruktor der Klasse CModArt

1: CModArt::CModArt()
2: {
3: // Zufallszahlengenerator initialisieren
4: srand((unsigned)time(NULL));
5: }

Um die Klasse zu vervollständigen, sind noch die erforderlichen Header-Dateien für die Funktionalität, die Sie in diese Klasse aufgenommen haben, einzubinden. Der Zufallszahlengenerator braucht die Header-Dateien stdlib.h und time.h, das Objektarray benötigt die Header-Datei für die Klasse CLine. Weiterhin müssen Sie die Farbtabelle füllen, die zur Generierung der Squiggles erforderlich ist. Diese letzten Feinheiten fügen Sie hinzu, indem Sie an den Beginn der Quellcodedatei für die Zeichenklasse gehen und die Zeilen 5, 6, 9 und 12 bis 21 aus Listing 16.8 einfügen.

Listing 16.8: Die Include-Anweisungen und die Farbtabelle der Klasse CModArt

1: // ModArt.cpp: Implementierung der Klasse CModArt.
2: //
3: //////////////////////////////////////////////////////////////////////
4:
5: #include <stdlib.h>
6: #include <time.h>
7:
8: #include "stdafx.h"
9: #include "Line.h"
10: #include "ModArt.h"
11:
12: const COLORREF CModArt::m_crColors[8] = {
13: RGB( 0, 0, 0), // Schwarz
14: RGB( 0, 0, 255), // Blau
15: RGB( 0, 255, 0), // Grün
16: RGB( 0, 255, 255), // Cyan
17: RGB( 255, 0, 0), // Rot
18: RGB( 255, 0, 255), // Magenta
19: RGB( 255, 255, 0), // Gelb
20: RGB( 255, 255, 255) // Weiß
21: };

Das Bibliotheksmodul ist damit fertiggestellt. Bevor Sie weitergehen können, müssen Sie Ihr Projekt kompilieren. Nach diesem Schritt können Sie noch nichts starten, da Sie erst eine Anwendung erstellen müssen, die Ihr Bibliotheksmodul verwendet. Nur so läßt sich der Code ausführen und testen. Um diese Testanwendung zu erstellen, schließen Sie zunächst den gesamten Arbeitsbereich, damit Sie einen leeren Arbeitsbereich für die Testanwendung vorfinden.

Eine Testanwendung erstellen

Damit Sie Ihr Modul testen können, brauchen Sie eine Anwendung, die das Modul verwendet. Diese einfache Anwendung muß nur soviel Funktionalität bieten, daß sich das Modul ausreichend testen läßt. Wir wollen lediglich die gesamte Funktionalität des Moduls ausprobieren und können auf eine ausgewachsene Anwendung verzichten.

Wenn Sie die Testanwendung erstellen, müssen Sie die Header-Datei für die Zeichenklasse in die relevanten Klassen in Ihrer Anwendung einbinden. Bei einer typischen SDI- oder MDI-Anwendung bedeutet das, daß man die Header-Datei zumindest in die Quelldateien der Dokumentklasse und gegebenenfalls noch in die der Ansichtsklasse und der Anwendungsklasse einbindet. Weiterhin ist die Bibliotheksdatei, die Ihr Modul erzeugt hat, in das Anwendungsprojekt einzubinden, damit sie zur Anwendung gelinkt wird.

Das Gerüst der Testanwendung

Der Rahmen für einen Test läßt sich auf einfache Weise mit einem normalen SDI- oder MDI-Anwendungsgerüst realisieren. Um die Beispielanwendung so einfach wie möglich zu halten, empfiehlt es sich, eine SDI-Anwendung zu wählen. Wenn Sie allerdings bestimmte Funktionen in Ihrem Modul vorgesehen haben, die sich speziell auf eine MDI-Anwendung beziehen, dann liegt es auf der Hand, daß Sie mit dem Gerüst einer MDI-Anwendung arbeiten.

Erstellen Sie als Testanwendung für das Beispielmodul das Gerüst einer normalen SDI-Anwendung mit dem Klassen-Assistenten. Benennen Sie das Projekt etwa mit TestApp oder einem ähnlichen Namen. Klicken Sie im vierten Schritt des Assistenten auf die Schaltfläche Weitere Optionen, und legen Sie eine Dateierweiterung fest. Für die übrigen Einstellungen können Sie die Vorgaben übernehmen.

Nachdem Sie das Anwendungsgerüst erstellt haben, müssen Sie das Bibliotheksmodul in das Projekt einbinden. Wählen Sie dazu Projekt / Dem Projekt hinzufügen / Dateien. Im Dialogfeld Dateien in Projekt einfügen legen Sie als Dateityp Bibliothekdateien fest, wie es Abbildung 16.3 zeigt. Gehen Sie in das Debug-Verzeichnis des Modulprojekts, um das Bibliotheksmodul zu lokalisieren, das Sie im vorherigen Projekt erstellt haben. Dazu müssen Sie normalerweise eine Verzeichnisebene höher gehen, das Projektverzeichnis für das Modul aufsuchen und in diesem Verzeichnis nach unten zum Debug-Verzeichnis wechseln. (Wenn Sie die Release-Version des Moduls und der Anwendung erstellen, müssen Sie sinngemäß in die entsprechenden Release-Verzeichnisse wechseln.) Die Bibliotheksdatei für das von Ihnen erstellte Modul sollten Sie entsprechend Abbildung 16.4 lokalisieren können. Markieren Sie das Modul, und klicken Sie auf OK, um es in das Projekt einzufügen.

Abbildung 16.3:
Bibliotheksdateien spezifizieren

Abbildung 16.4:
Eine Bibliotheksdatei in das Projekt aufnehmen

Nachdem Sie eine Bibliotheksdatei in das Projekt aufgenommen haben, müssen Sie noch die Header-Dateien für alle Klassen im Modul, die in den entsprechenden Quellcodedateien der Anwendung zum Einsatz kommen, einbinden. Für die zu erstellende Testanwendung ist dazu Zeile 7 von Listing 16.9 hinzuzufügen. Die gleiche Zeile nehmen Sie auch in die Include-Abschnitte der Quellcodedateien für die Ansichts- und Anwendungsklasse auf.

Listing 16.9: Die Include-Anweisungen der Klasse CTestAppDoc

1: // TestAppDoc.cpp : Implementierung der Klasse CTestAppDoc
2: //
3:
4: #include "stdafx.h"
5: #include "TestApp.h"
6:
7: #include "..\ModArtMod\ModArt.h"
8: #include "TestAppDoc.h"

Um die Vorbereitungen am Anwendungsgerüst abzuschließen, nehmen Sie als letztes noch eine Variable für alle Klassen aus dem Bibliotheksmodul auf, die in irgendwelche Klassen der Anwendung eingebunden werden. Für die Testanwendung ist das eine Variable in der Dokumentklasse der Zeichenklasse, die Sie im Projekt des Bibliotheksmoduls erzeugt haben. Um diese Variable in Ihre Anwendung einzufügen, nehmen Sie eine neue Member-Variable in die Dokumentklasse auf. Legen Sie den Variablentyp als Zeichenklasse aus dem Bibliotheksmodul (in diesem Fall CModArt), den Namen mit m_maDrawing und den Zugriff als Privat fest.

Eine neue Zeichnung erstellen

Auf die Funktionalität des Moduls greifen Sie zuerst beim Erzeugen eines neuen Dokuments zurück. Das ist der Moment, wo eine neue Zeichnung zu generieren ist. Letztendlich sind zwei Dinge zu realisieren. Erstens ist der Zeichenbereich der Ansichtsklasse zu ermitteln und an das Zeichenobjekt zu übergeben. Zweitens ist das Zeichenobjekt anzuweisen, eine neue Zeichnung zu generieren. Das ist alles ziemlich unkompliziert. Um diese Funktionalität in Ihre Anwendung aufzunehmen, bearbeiten Sie die Funktion OnNewDocument in der Dokumentklasse und fügen die Zeilen 9 bis 23 aus Listing 16.10 hinzu.

Listing 16.10: Die Funktion OnNewDocument der Klasse CTestAppDoc

1: BOOL CTestAppDoc::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: // Position der Ansicht ermitteln
10: POSITION pos = GetFirstViewPosition();
11: // Ist Position gültig?
12: if (pos != NULL)
13: {
14: // Zeiger auf die Ansicht holen
15: CView* pView = GetNextView(pos);
16: RECT lWndRect;
17: // Rechteck des Anzeigebereichs holen
18: pView->GetClientRect(&lWndRect);
19: // Zeichenbereich festlegen
20: m_maDrawing.SetRect(lWndRect);
21: // Neue Zeichnung erzeugen
22: m_maDrawing.NewDrawing();
23: }
24:
25: return TRUE;
26: }

Eine Zeichnung speichern und löschen

In die Dokumentklasse sind noch die Funktionen einzubauen, um die Zeichnung zu speichern und wiederherzustellen sowie zu löschen. Diese Aufgaben sind die letzten der dokumentbezogenen Funktionalität Ihres Bibliotheksmoduls.

Um die Funktionalität zum Speichern und Wiederherstellen in der Anwendung zu realisieren, bearbeiten Sie die Funktion Serialize in der Dokumentklasse. Löschen Sie den gesamten Inhalt der Funktion, und ersetzen Sie ihn durch einen Aufruf der Serialize -Funktion des Zeichenobjekts, wie es Listing 16.11 zeigt.

Listing 16.11: Die Funktion Serialize der Klasse CTestAppDoc

1: void CTestAppDoc::Serialize(CArchive& ar)
2: {
3: // Zeichnung serialisieren
4: m_maDrawing.Serialize(ar);
5: }

Zum Löschen der Zeichnung - damit sich eine neue Zeichnung generieren oder eine gespeicherte Zeichnung laden läßt - brauchen Sie eine Behandlungsroutine für die Funktion DeleteContents der Dokumentklasse. In dieser Funktion rufen Sie die Funktion ClearDrawing des Zeichenobjekts auf. Um diese Funktionalität in die Anwendung aufzunehmen, fügen Sie mit dem Klassen-Assistenten die Behandlungsroutine für das Ereignis DeleteContents in die Dokumentklasse ein. Ergänzen Sie die Funktion mit Zeile 5 von Listing 16.12.

Listing 16.12: Die Funktion DeleteContents der Klasse CTestAppDoc

1: void CTestAppDoc::DeleteContents()
2: {
3: // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
4: // Zeichnung löschen
5: m_maDrawing.ClearDrawing();
6:
7: CDocument::DeleteContents();
8: }

Eine Zeichnung anzeigen

In die Testanwendung ist noch eine letzte Funktionsgruppe aufzunehmen, bevor Sie Ihr Bibliotheksmodul testen können: die Zeichenfunktionalität für die Anwendung. Diese Funktionalität gehört in die Ansichtsklasse, da ja das Objekt weiß, wann es sich selbst neu zeichnen muß. Bevor Sie die Funktionalität in der Ansichtsklasse realisieren können, müssen Sie eine Möglichkeit schaffen, damit die Ansichtsklasse auf das Zeichenobjekt zugreifen kann. Am einfachsten läßt sich das mit einer weiteren Funktion in der Dokumentklasse erreichen, die als Rückgabewert einen Zeiger auf das Zeichenobjekt liefert. Nachdem die Ansicht über diesen Zeiger verfügt, kann sie die zum Zeichenobjekt gehörende Draw-Funktion aufrufen.

Fügen Sie zu diesem Zweck eine neue Member-Funktion in die Dokumentklasse ein. Spezifizieren Sie den Funktionstyp als Zeiger auf das Zeichenobjekt, in diesem Fall CModArt*, die Deklaration als GetDrawing und den Zugriff als Public. In die Funktion übernehmen Sie den Code aus Listing 16.13.

Listing 16.13: Die Funktion GetDrawing der Klasse CTestAppDoc

1: CModArt* CTestAppDoc::GetDrawing()
2: {
3: // Das Zeichenobjekt zurückgeben
4: return &m_maDrawing;
5: }

Um die Zeichenfunktionalität der Ansichtsklasse zu realisieren, bearbeiten Sie einfach die Funktion OnDraw in der Ansichtsklasse. Die Funktion holt einen Zeiger auf das Zeichenobjekt und ruft dann dessen Draw-Funktion auf, wie es Listing 16.14 zeigt.

Listing 16.14: Die Funktion OnDraw der Klasse CTestAppView

1: CModArt* CTestAppDoc::GetDrawing()
2: {
3: // Das Zeichenobjekt zurückgeben
4: return &m_maDrawing;
5: }

Nachdem Sie die gesamte Funktionalität hinzugefügt haben, können Sie die Anwendung kompilieren und ausführen, um die Funktionalität des Bibliotheksmoduls zu testen. Immer, wenn Sie Datei / Neu aus dem Menü der Anwendung wählen, wird eine neue Zeichnung wie in Abbildung 16.5 erzeugt.

Abbildung 16.5:
Zufällige Schnörkelzeichnungen erzeugen

Das Bibliotheksmodul überarbeiten

Nachdem Sie nun über eine funktionsfähige Anwendung verfügen, gehen wir zurück ins Bibliotheksmodul und nehmen einige Änderungen vor. Wenn sich Änderungen - und seien es nur die geringsten - am Code des Bibliotheksmoduls erforderlich machen, müssen Sie alle Anwendungen neu linken, die auf dieses Modul zurückgreifen, damit die Überarbeitungen in diesen Anwendungen wirksam werden. Das hängt damit zusammen, daß das Bibliotheksmodul zur ausführbaren Datei der Anwendung gelinkt wird und nicht in einer separaten Datei abgelegt ist.

Um diese Arbeitsweise zu untersuchen, öffnen Sie das Projekt des Bibliotheksmoduls erneut. An diesem Modul nehmen Sie drei Änderungen vor. Erstens erhöhen Sie die Anzahl der Squiggles, die in einer einzelnen Zeichnung enthalten sein können. Zweitens vergrößern Sie die Zahl der Linienabschnitte, aus denen sich ein Squiggle aufbaut. Drittens erzeugen Sie zufällige Farben, und zwar mehr als nur die acht aus der Farbtabelle. Nach diesen Änderungen kompilieren Sie das Bibliotheksmodul neu und linken es zur Anwendung hinzu, damit Sie die Änderungen in die Anwendung einbinden können.

Für die erste Änderung des Moduls (Erhöhen der Anzahl Squiggles in einer Zeichnung) bearbeiten Sie die Funktion NewDrawing in der Zeichenklasse. Hier vergrößern Sie den Modulo-Wert in Zeile 7 der Funktion, wie es aus Listing 16.15 hervorgeht. Damit erhöht sich die mögliche Anzahl der Squiggles in einer Zeichnung von einem Maximalwert von 10 auf das neue Maximum von 50. Gelegentlich können noch Zeichnungen entstehen, die überhaupt keine Squiggles aufweisen, aber diesen Fall ignorieren wir vorerst.

Listing 16.15: Die Funktion NewDrawing der Klasse CModArt

1: void CModArt::NewDrawing()
2: {
3: int lNumLines;
4: int lCurLine;
5:
6: // Anzahl der zu erzeugenden Linien bestimmen
7: lNumLines = rand() % 50;
8: // Sind Linien zu erzeugen?
9: if (lNumLines > 0)
10: {
11: // Schleife durch Anzahl der Linien
12: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
13: {
14: // Die neue Linie erzeugen
15: NewLine();
16: }
17: }
18: }

Nach dieser Änderung vergrößern Sie noch die Anzahl der Liniensegmente, aus denen sich ein Squiggle zusammensetzt. Dazu bearbeiten Sie die Funktion NewLine und erhöhen den Modulo-Wert von 100 auf 200, wie es Zeile 20 von Listing 16.16 zeigt. In dieser Funktion können Sie gleich noch die Anzahl der Farben vergrößern, die für jede Zeichnung generiert werden. Deklarieren Sie als erstes drei Integer-Variablen für die drei Grundfarben Rot, Grün und Blau (siehe die Zeilen 9 bis 11 in Listing 16.16). Als nächstes erzeugen Sie zufällige Werte zwischen 0 und 255 für diese Integer-Variablen (in den Zeilen 26 bis 28). Schließlich übergeben Sie diese Werte beim Erzeugen des CLine-Objekts über die Funktion RGB, um die eigentliche Farbe für die Zeichnung zu erzeugen, wie es Zeile 41 von Listing 16.16 zeigt.

Listing 16.16: Die modifizierte Funktion NewLine der Klasse CModArt

1: void CModArt::NewLine()
2: {
3: int lNumLines;
4: int lCurLine;
5: // int nCurColor;
6: UINT nCurWidth;
7: CPoint pTo;
8: CPoint pFrom;
9: int cRed;
10: int cBlue;
11: int cGreen;
12:
13: // Rechteck normalisieren, dann erst Breite und Höhe bestimmen
14: m_rDrawArea.NormalizeRect();
15: // Breite und Höhe des Zeichenbereichs ermitteln
16: int lWidth = m_rDrawArea.Width();
17: int lHeight = m_rDrawArea.Height();
18:
19: // Anzahl der Teile dieses Squiggles bestimmen
20: lNumLines = rand() % 200;
21: // Umfaßt das Squiggle mindestens ein Teil?
22: if (lNumLines > 0)
23: {
24: // Farbe bestimmen
25: // nCurColor = rand() % 8;
26: cRed = rand() % 256;
27: cBlue = rand() % 256;
28: cGreen = rand() % 256;
29: // Stiftbreite bestimmen
30: nCurWidth = (rand() % 8) + 1;
31: // Anfangspunkt für Squiggle bestimmen
32: pFrom.x = (rand() % lWidth) + m_rDrawArea.left;
33: pFrom.y = (rand() % lHeight) + m_rDrawArea.top;
34: // Schleife durch Anzahl der Segmente
35: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++)
36: {
37: // Endpunkt des Segments bestimmen
38: pTo.x = ((rand() % 20) - 10) + pFrom.x;
39: pTo.y = ((rand() % 20) - 10) + pFrom.y;
40: // Neues CLine-Objekt erzeugen
41: CLine *pLine = new CLine(pFrom, pTo, RGB(cRed, cGreen, cBlue), ÂnCurWidth);
42: try
43: {
44: // Neue Linie in das Objektarray hinzufügen
45: m_oaLines.Add(pLine);
46: }
47: // Speicherausnahme?
48: catch (CMemoryException* perr)
49: {
50: // Meldung an Benutzer mit schlechten
51: // Neuigkeiten
52: AfxMessageBox("Speichermangel", MB_ICONSTOP | MB_OK);
53: // Wurde ein Linienobjekt erzeugt?
54: if (pLine)
55: {
56: // Löschen
57: delete pLine;
58: pLine = NULL;
59: }
60: // Ausnahmeobjekt löschen
61: perr->Delete();
62: }
63: // Anfangspunkt auf Endpunkt setzen
64: pFrom = pTo;
65: }
66: }
67: }

Nachdem Sie alle erforderlichen Änderungen am Bibliotheksmodul vorgenommen haben, kompilieren Sie das Modul, um es für den Einsatz in der Testanwendung vorzubereiten. Wenn Sie die Testanwendung über den Befehl Start / Ausführen der Taskleiste starten (siehe Abbildung 16.6), ist noch kein Unterschied im Verhalten der Anwendung festzustellen, weil Sie Anwendung noch nicht auf den neuesten Stand gebracht haben. Die Anwendung greift immer noch auf die alte Version des Bibliotheksmoduls zurück. Um die neue Version in die Anwendung einzubauen, öffnen Sie das Projekt der Testanwendung in Visual C++. Erstellen Sie das Projekt neu. Dabei passiert nichts weiter, als ein erneutes Linken des Projekts. Führen Sie dann die Anwendung aus. Jetzt ist ein deutlicher Unterschied bei den Zeichnungen zu bemerken, die Ihre Anwendung generiert (siehe Abbildung 16.7).

Abbildung 16.6:
Die Testanwendung über das Menü Start der Taskleiste ausführen

Abbildung 16.7:
Die aktualisierte Testanwendung

Zusammenfassung

Heute haben Sie neue Klassen für eigene Anwendungen entworfen und erstellt. Sie haben den Unterschied zwischen den verschiedenen Klassentypen kennengelernt, die Ihnen der neue Klassen-Assistent in Visual C++ bietet. Weiterhin wurde gezeigt, wie man ein Bibliotheksmodul mit einer Gruppen von Funktionen erzeugt, um es anderen Programmierern für deren Anwendungen bereitzustellen. Sie haben gesehen, wie dieses Modul in die eigentliche Anwendung gelinkt wird, wodurch sich keine getrennte Datei für den Vertrieb der Anwendung erforderlich macht.

Morgen lernen Sie eine andere Lösung kennen, um wiederverwendbare Funktionalität an andere Programmierer weitergeben zu können. Es wird gezeigt, wie man DLLs mit Visual C++ erzeugt, welche Unterschiede zwischen Bibliotheksmodulen und DLLs bestehen und wie man an jede Aufgabe herangeht.

Fragen und Antworten

Frage:
Ist heutzutage nicht der größte Teil der Funktionalität in DLLs verpackt? Warum soll ich überhaupt Bibliotheksmodule statt DLLs erstellen?

Antwort:
In den letzten Jahren zeichnet sich in der Tat der Trend ab, die Funktionalität nicht in Bibliotheksmodulen, sondern in DLLs zu verpacken. Allerdings gibt es noch Situationen, in denen Bibliotheksmodule vorzuziehen sind. Wenn Sie zum Beispiel ein Modul mit proprietären Funktionen erstellen, die Sie Dritten nicht zugänglich machen wollen, trotzdem aber in mehreren Anwendungen einsetzen, an denen Sie oder ein anderer Programmierer Ihrer Firma arbeiten, empfiehlt es sich, die gesamte Funktionalität in einem Bibliotheksmodul zu verpacken, damit sie intern zur Anwendung bleibt. Auf diese Weise erschweren Sie es Ihren Konkurrenten ungemein, die Anwendung zu disassemblieren, um den Code zu untersuchen.

Frage:
Warum muß ich die Header-Datei in die Anwendung, die auf mein Bibliotheksmodul zurückgreift, einbinden?

Antwort:
Die Anwendung muß die Objekte kennen, die in der Bibliotheksdatei enthalten sind. In der Beispielanwendung war es nicht erforderlich, die Header-Datei für die Klasse CLine einzubinden, weil die Anwendung nicht direkt auf die Klasse CLine zugreift oder diese referenziert. Allerdings verwendet die Anwendung das Zeichenobjekt aus dem Bibliotheksmodul, so daß sie dieses Objekt kennen muß, wie es definiert ist und welche Funktionen dafür verfügbar sind. Wenn Sie anderen Programmierern die interne Struktur Ihrer Klassen nicht offenlegen wollen, erzeugen Sie eine weitere Header-Datei, die Sie zusammen mit Ihrem Bibliotheksmodul vertreiben. Dieser Header enthält alle Definitionen der Klassen im Bibliotheksmodul, legt aber nur die öffentlichen Funktionen und Variablen frei, auf die andere Programmierer tatsächlich zugreifen können.

Workshop

Kontrollfragen

1. Wann bietet es sich an, eine neue MFC-Klasse zu erstellen?

2. Wenn man Änderungen an einer Bibliotheksdatei vornimmt, was ist dann mit der Anwendung zu tun, die auf die Bibliotheksdatei zurückgreift?

3. Welche verschiedenen Klassentypen können Sie erzeugen?

4. Wenn Sie bestimmte Funktionen in einer Bibliotheksdatei verpacken, was müssen Sie dann anderen Programmierern übergeben, die Ihr Bibliotheksmodul einsetzen möchten?

5. Nennen Sie zwei der grundlegenden Prinzipien des objektorientierten Softwareentwurfs.

Übung

Spalten Sie die Klasse CLine in ein separates Bibliotheksmodul von der Zeichenklasse ab, so daß Sie dann zwei Bibliotheksmodule und nicht nur ein Modul haben. Linken Sie die Module zur Testanwendung.



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