vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Tag F

MFC-Hilfsklassen

Auflistungsklassen

Sie werden schnell feststellen, daß in jedem objektorientierten Programm, das Sie schreiben, die Objekte gruppiert und in Auflistungen unterschiedlicher Typen und Größen gespeichert werden müssen. Auch hier kommt wieder die MFC zu Hilfe und stellt Gruppen einfach anzuwendender Klassen und Vorlagen bereit.

Die Auflistungsklassen fallen in drei große Kategorien - Felder, Listen und Tabellen - mit jeweils speziellen Aufgabengebieten.

Felder (oder Arrays, diese Begriffe werden hier gleichberechtigt verwendet) bilden die Hauptstütze der Auflistungsklassen und eignen sich für die Implementierung von Objektcontainern. Jedes Objekt in einem Array hat eine bei null beginnende Position (Index), über die man sich auf das Objekt bezieht.

Die Listenklasse organisiert Elemente in Form einer doppelt verketteten Liste, bei der die Daten sequentiell miteinander verbunden sind. Listen bieten sich vor allem an, wenn man Elemente am Anfang (dem »Kopf«) oder am Ende der Liste schnell hinzufügen oder entfernen muß. Man kann die Liste auch vorwärts oder rückwärts von einem Element zum nächsten durchlaufen.

Bei Tabellen (Maps) verknüpft man ein Schlüsselobjekt, etwa einen String oder eine Zahl, mit einem Wertobjekt, wo Verknüpfungen von Haus aus nur selten oder zufällig vorkommen. Beispielsweise kann man eine Tabelle verwenden, um Objekte mit Postleitzahlen zu verknüpfen. Tabellen eignen sich hervorragend bei schnellen Abfragen von Objekten, wenn der Verknüpfungsschlüssel gegeben ist, und können für die temporäre Speicherung von Daten bei großen Datenbanken verwendet werden.

Feldklassen

MFC stellt mehrere vordefinierte Feldklassen und eine allgemeine Feldvorlage bereit, so daß man Felder erstellen kann, die eigene benutzerdefinierte Objekte aufnehmen. (Auf den letzten Punkt gehen wir später in diesem Kapitel ein.)

Verschiedene vordefinierte Feldklassen bieten einen schnellen und einfachen Zugriff auf gebräuchliche Typen von Variablen und Objekten (siehe Tabelle F.1).

Tabelle F.1: Vordefinierte Array-basierte Klassen

Feldklasse

Typen der gespeicherten Variablen

Numerischer Bereich des Typs

CByteArray

BYTE - vorzeichenlose 8 Bit-Werte

0 bis 255

CWordArray

WORD - vorzeichenlose 16 Bit-Werte

0 bis 65535

CUIntArray

UINT - vorzeichenlose Ganzzahlen mit 32 Bit

0 bis 4 294 967 295

CDWordArray

DWORD - vorzeichenlose Ganzzahlen mit 32 Bit

0 bis 4 294 967 295

CStringArray

CString - Textobjekte

CObArray

CObject - von CObject abgeleitete Objekte

CPtrArray

void* - Objektzeiger oder Speicheradressen

Für jede Feldklasse gibt es mehrere Member-Funktionen, die sich nur durch den Typ der aufgenommenen Variablen unterscheiden. Jede hier behandelte Funktion läßt sich mit allen Feldklassen verwenden, um die Variablen des entsprechenden Typs zu behandeln.

Einer der nützlichsten Aspekte dieser Feldklassen ist deren Fähigkeit, dynamisch zu wachsen. Normale C/C++-Arrays sind in ihrer Größe vordefiniert und lassen sich nur durch umfangreiche Neuzuweisungen von Speicher erweitern. Die Auflistungsklassen verbergen diese Neuzuweisungen, so daß man einfach die Member-Funktion Add eines Feldobjekts aufrufen kann, um einen neuen Wert hinzuzufügen. Will man beispielsweise Strings in ein CStringArray aufnehmen, verwendet man etwa folgenden Code:

CStringArray myStringArray;
myStringArray.Add("Rot");
myStringArray.Add("Grün");
myStringArray.Add("Blau");

Die Größe eines Arrays läßt sich dann mit der Funktion GetSize ermitteln. Führt man im Anschluß an die obigen Zeilen die folgende Zeile aus, erhält man in nNumberOfItems drei Elemente zurück:

int nNumberOfItems = myStringArray.GetSize();

Ein Array kann man mit der korrespondierenden Funktion SetSize auch auf eine bestimmte Größe einstellen. Dabei wird das Array entweder abgeschnitten oder erweitert, um der übergebenen Größe zu entsprechen.

Mit der Funktion SetAt, der man einen bei null beginnenden Index und den zu speichernden Wert übergibt, kann man Werte in das Array eintragen. Man muß sicherstellen, daß der Index innerhalb der Feldgrenzen liegt, da SetAt ansonsten einen Assertion-Fehler auslöst. Mit der Funktion GetAt kann man Werte aus dem Array abrufen. Die Funktion liefert den Wert an der angegebenen Indexposition zurück. Bei einem CWordArray setzt man diese Funktionen zum Beispiel folgendermaßen ein:

CWordArray myWordArray;
myWordArray.SetSize(20);
myWordArray.SetAt(0,200);
myWordArray.SetAt(19,500);
TRACE("Der Wert an Indexposition 19 lautet %d\n",
myWordArray.GetAt(19));

Diese Zeilen setzen das erste Element eines 20elementigen Arrays auf 200 und das letzte auf 500. Die letzte Zeile zeigt dann das letzte Element - den Wert 500 - an. Mit der Funktion Add läßt sich das Array weiter vergrößern. Den größten gültigen Index ermittelt man mit der Funktion GetUpperBound. Die Funktion liefert einen bei null beginnenden Index zurück, oder -1, wenn keine Elemente vorhanden sind.

Mit dem Indexoperator [ ] kann man Werte an einer bestimmten Indexposition wie bei einem normalen C++-Array setzen und abrufen. Beispielsweise lassen sich die Funktionen GetAt und SetAt in den obigen Zeilen folgendermaßen durch den Indexoperator ersetzen:

myWordArray[0] = 200;
myWordArray[19] = 500;
TRACE("Der Wert an Indexposition 19 lautet %d\n", myWordArray.GetAt[19]);

Mit den Funktionen InsertAt und RemoveAt kann man Elemente an einer bestimmten Position einfügen bzw. entfernen. Dabei werden alle Elemente ab dieser Position um ein oder mehrere Elemente nach oben bzw. unten verschoben.

Die Funktion InsertAt hat zwei Formen: die erste benötigt eine Indexposition und das an dieser Stelle einzufügende Element. Optional kann man eine Anzahl übergeben, um mehrere Kopien des angegebenen Elements einzufügen. Die zweite Form erlaubt es, ein anderes komplettes Array an der angegebenen Indexposition einzufügen.

Die Funktion RemoveAt braucht nur einen Parameter, den Index des zu entfernenden Elements. Man kann auch hier einen optionalen zweiten Parameter übergeben, um die entsprechende Anzahl von Elementen zu entfernen. Die verbleibenden Feldelemente werden dann nach unten verschoben, um die Lücke aufzufüllen.

Alle Elemente lassen sich aus einem Array mit der Funktion RemoveAll entfernen.

Den Speicher mit CObArray und CPtrArray verwalten

Bei Objekten, die Sie mit new erzeugt und in einem CObArray oder CPtrArray gespeichert haben, müssen Sie beim Löschen sorgfältig vorgehen, da diese Arrays lediglich Zeiger auf die Elemente (und nicht die Elemente selbst) speichern. Demzufolge entfernt ein Aufruf von RemoveAll die Zeiger auf die Objekte, gibt aber nicht den von den Objekten selbst belegten Speicher frei.

Listenklassen

Wie Tabelle F.2 zeigt, gibt es nur drei Kategorien von Listen und eine Vorlage (Template) für Ihre eigenen - später noch zu behandelnden - Typen. Listen mit einfachen Ganzzahlen benötigt man nur selten. Statt dessen haben Sie es eher mit verketteten Listen zu tun, in denen Sie Ihre von CObject abgeleiteten Klassen, Zeiger auf C++- Klassen oder Strukturen verwalten.

Tabelle F.2: Die Auflistungsklassen für Listen

Klassenname

Typ der gespeicherten Variablen

CObList

CObject - Zeiger auf von CObject abgeleitete Objekte.

CPtrList

void* - Zeiger auf Speicheradressen, die beliebige Daten aufnehmen.

CStringList

CString - Zeichenfolgen.

Bei verketteten Listen sind mehrere Objekte miteinander in sequentieller Art wie die Waggons eines Zuges verkoppelt. Es gibt eine eindeutige Anfangs- und Endposition, aber jedes andere Element kennt nur seinen unmittelbaren Nachbarn. Eine POSITION- Variable protokolliert eine aktuelle Position in einer Liste. Man kann mehrere POSITION -Variablen deklarieren, um verschiedene Stellen in derselben Liste zu verfolgen.

Die Member-Funktionen der Listen verwenden dann eine POSITION-Variable, um den Anfang (den »Kopf«), das Ende oder das nächste bzw. vorherige Element in der Liste zu ermitteln.

Mit den Funktionen AddHead und AddTail kann man Elemente in eine Liste am Anfang bzw. am Ende hinzufügen oder mit den Funktionen InsertBefore und InsertAfter vor bzw. nach einer bestimmten Position einfügen. Alle Funktionen liefern dann einen POSITION-Wert zurück, der die Position des neu hinzugefügten Elements angibt.

Das folgende Beispiel konstruiert eine vierelementige Liste von CString-Elementen:

CStringList listMyStrings;
POSITION pos;
pos = listMyStrings.AddHead("Hand");
listMyStrings.AddTail("Unterarm");
listMyStrings.InsertBefore(pos, "Finger");
listMyStrings.AddTail("Ellbogen");

Diese Codezeilen liefern eine verknüpfte Liste von CStrings, die von Anfang bis Ende folgendes Aussehen hat:

Finger-Hand-Unterarm-Ellbogen

Man kann auch andere gleichartige Listenobjekte an die Funktionen AddHead und AddTail übergeben, um eine weitere Liste am Beginn oder Ende der aktuellen Liste hinzuzufügen.

Wenn Sie eine Liste aufgebaut haben, können Sie mit Hilfe einer POSITION-Variablen durch die Elemente der Liste laufen. Die Anfangs- oder Endposition läßt sich mit den Funktionen GetHeadPosition bzw. GetTailPosition ermitteln. Diese Funktionen geben einen POSITION-Wert zurück, der die aktuelle Position in der Liste bezeichnet. Dann kann man die POSITION-Variable als Referenz an die Funktionen GetNext oder GetPrev übergeben, um das nächste bzw. vorherige Element in der Liste zu finden. Diese Funktionen liefern dann das betreffende Objekt zurück und passen die aktuelle Position an. Wenn das Ende der Liste erreicht ist, wird die Variable POSITION auf NULL gesetzt.

Die Zeilen im folgenden Beispiel durchlaufen die oben erzeugte listMyStrings und zeigen die Elemente nacheinander an:

POSITION posCurrent = listMyStrings.GetHeadPosition();
while(posCurrent) TRACE("%s\n", listMyStrings.GetNext(posCurrent);

Bestimmte Listenelemente lassen sich mit der Funktion Find aufsuchen, die einen POSITION -Wert zurückliefert, wenn der übergebene Suchparameter gefunden wurde. Optional können Sie einen Positionswert übergeben, von dem aus die Suche beginnen soll.

Um zum Beispiel nach dem String Finger der oben angelegten Liste zu suchen, ruft man die Funktion Find wie folgt auf:

POSITION posFinger = Find("Finger");

Die Funktion Find liefert einen NULL-Wert zurück, wenn das gesuchte Element nicht vorhanden ist.

Mit der Funktion FindIndex kann man das nte Element vom Beginn der Liste an suchen (wobei man n als Parameter übergibt).

Die Anzahl der Elemente in der Liste liefert die Member-Funktion GetCount. Die Funktion benötigt keine Parameter und gibt die Anzahl der Elemente zurück.

Der Wert von Elementen an einer bestimmten Position läßt sich mit den Funktionen GetAt und SetAt abrufen bzw. zurücksetzen. Die Funktionen setzt man ähnlich ein wie die äquivalenten Feldfunktionen, übergibt aber einen POSITION-Wert statt eines Feldindexes.

Mit der Funktion RemoveAt entfernt man Elemente aus der Liste. Der Funktion übergibt man den POSITION-Wert, um das zu entfernende Element zu kennzeichnen. Mit dem folgenden Code löschen Sie zum Beispiel den Eintrag Finger aus der Liste des obigen Beispiels:

RemoveAt(posFinger);

Tabellenklassen

Tabellenklassen (oder Map-Klassen) verbinden einen Typenwert (oder Element) mit einem Schlüsselwert, mit dem man nach dem Element suchen kann. Die verschiedenen Tabellenklassen sowie deren Schlüsselwerte und zugeordneten Elementtypen sind in Tabelle F.3 aufgeführt.

Tabelle F.3: Die Auflistungsklassen für Tabellen (Maps)

Klassenname

Schlüsseltyp

Elementtyp

CMapWordToOb

WORD - vorzeichenloser 16-Bit-Wert

CObject - von CObject abgeleitete Objekte

CMapWordToPtr

WORD - vorzeichenloser 16-Bit-Wert

void* - Zeiger auf Speicher

CMapPtrToPtr

void* - Zeiger auf Speicher

void* - Zeiger auf Speicher

CMapPtrToWord

void* - Zeiger auf Speicher

WORD - vorzeichenloser 16-Bit-Wert

CMapStringToOb

CString - Zeichenfolgen

CObject - von CObject abgeleitete Objekte

CMapStringToPtr

CString - Zeichenfolgen

void* - Zeiger auf Speicher

CMapStringToString

CString - Zeichenfolgen

CString - Textstrings

In eine Tabelle kann man mit der Funktion SetAt Elemente einfügen. Der Funktion übergibt man einen Schlüsselwert als ersten Parameter und den Wert des Elementes als zweiten. Wollen Sie zum Beispiel Ihre von CObject abgeleiteten Objekte, die mit einem Zeichenfolgenwert indiziert sind, speichern, können Sie die Klasse CMapStringToOb verwenden und Elemente wie folgt hinzufügen:

CMapStringToOb mapPlanetDetails;
mapPlanetDetails.SetAt("Merkur", new CPlanetDets (4878, 0.054, 57.91, 87.969));
mapPlanetDetails.SetAt("Venus", new CPlanetDets (12100, 0.815, 108.21, 224.701));
mapPlanetDetails.SetAt("Erde", new CPlanetDets (12756, 1.000, 149.60, 365.256));

In diesem Beispiel ist CPlanetDets eine von CObject abgeleitete Klasse mit einem Konstruktor, der vier Detailparameter für Planeten übernimmt. Den neuen Objekten sind dann die Planetennamen als Schlüssel zugeordnet.

Statt der Funktion SetAt können Sie auch den Indexoperator [ ] verwenden, indem Sie den Schlüsselwert in den eckigen Klammern angeben:

mapPlanetDetails["Mars"] = new CPlanetDets (6762, 0.107, 227.94, 686.98);

Nachdem Sie Daten in eine Tabelle eingetragen haben, können Sie sie mit der Member-Funktion Lookup wieder abrufen. Der Funktion übergeben Sie den Schlüsselwert als Referenz auf eine Variable, die das zugehörige Element aufnimmt, falls es existiert. Ist das Element nicht vorhanden, liefert die Funktion Lookup den Wert FALSE zurück. Um zum Beispiel die Angaben über einen Planet aus dem vorherigen Beispiel abzurufen, können Sie etwa folgende Zeilen verwenden:

CPlanetDets* pMyPlanet = NULL;
if (mapPlanetDetails.Lookup("Erde", (CObject*&)pMyPlanet))
TRACE("Umlaufzeit = %d Tage\n", pMyPlanet->m_dSidereal);

Die Typumwandlung (CObject*&) dient dazu, den Objektzeiger pMyPlanet in eine allgemeine Zeigerreferenz auf CObject umzuwandeln.

Der Funktion GetCount liefert die Anzahl der momentan in der Tabelle vorhandenen Elemente zurück. Die Elemente lassen sich durch Aufruf der Funktion RemoveKey entfernen. Der Funktion übergibt man den Schlüssel des zu entfernenden Elements:

mapPlanetDetails.RemoveKey("Jupiter");

Denken Sie daran, die reservierten Objekte zu löschen. RemoveKey entfernt einfach den Zeiger auf das Objekt - und nicht das Objekt selbst - und gibt den verwendeten Speicher nicht frei. Durch Aufruf der Funktion RemoveAll können Sie alle Elemente auf einmal entfernen.

Mit Hilfe der Funktion GetNextAssoc kann man die Liste der Zuordnungen durchlaufen. Die Funktion benötigt Parameter, die eine Variable mit der aktuellen Position referenzieren, eine Schlüsselvariable und eine Elementvariable. Die Position des ersten Elements ermittelt man mit der Funktion GetFirstPosition. Die Funktion liefert den POSITION-Wert für das erste Element zurück. Um durch die Zuordnungen zu gehen, kann man etwa den folgenden Code verwenden:

POSITION pos = mapPlanetDetails.GetStartPosition();
while(pos!=NULL)
{
CString strPlanet;
CPlanet* pMyPlanet;
mapPlanetDetails.GetNextAssoc(pos, strPlanet, CObject*&)pMyPlanet);
TRACE("%s hat einen Durchmesser von %d km\n", strPlanet, pMyPlanet- >m_dDiameter);
}

Nach Rückkehr aus GetNextAssoc enthält pos die Position für die nächste Zuordnung oder NULL, wenn es keine Zuordnungen mehr gibt. Die Schlüssel- und Elementwerte (strPlanet und pMyPlanet im obigen Beispiel) werden nacheinander auf das jeweilige Schlüssel/Element-Paar gesetzt.

Durch die Fähigkeit der Tabelle, schwach besetzte Datenbestände schnell und effizient abzurufen, ist es oftmals von Vorteil, eine Tabelle als Zwischenspeicher bei einer langsamen Datenbanksuche zu verwenden.

Beispielsweise sind in den folgenden Zeilen die mit strPlanetName verbundenen Angaben erforderlich. Beim ersten Aufruf verfügt dieser Code noch nicht über eine abgebildete Version des angeforderten Planeten, so daß man ihn mit GetPlanetFromSlowDB suchen muß. Da der Code dann den abgerufenen Planeten in der Tabelle mapPlanetDetails speichert, lassen sich beim nächsten Aufruf mit demselben strPlanetName die Angaben schnell aus der zwischengespeicherten Version abrufen:

CPlanetDets* pMyPlanet = NULL;
if (mapPlanetDetails.Lookup(strPlanetName, (CObject*&)pMyPlanet) == FALSE)
{
pMyPlanet = GetPlanetFromSlowDB(strPlanetName);
mapPlanetDetails.SetAt(strPlanetName,pMyPlanet);
}
return pMyPlanet;

Dieses Verfahren ist leicht zu implementieren und kann die Geschwindigkeit der Anwendung erhöhen, wenn man mit langsamen Abfragegeräten wie etwa Datenbanken oder Dateien arbeitet.

Benutzerdefinierte Auflistungsklassen

Vielleicht möchten Sie die Auflistungsklassen anpassen, um Ihre eigenen Objekte anstelle der allgemeinen, von CObject abgeleiteten Klassen zu verwenden. Die Anpassung bietet mehrere Vorteile, da man ein Feld, eine Liste oder eine Tabelle erstellen kann, die nur den speziellen Typ des Objekts akzeptiert und zurückgibt. Wenn man versehentlich versucht, die falsche Objektart in ein benutzerdefiniertes Feld, eine Liste oder eine Tabelle hinzuzufügen, löst der Compiler eine Fehlermeldung aus, um Sie darüber zu informieren. Der andere Vorteil besteht darin, daß man keine Typumwandlungen von allgemeinen Zeigern auf CObject* (das heißt, von einem CObArray) zurück auf das verwendete Objekt vornehmen muß.

Diese Art der Anpassung bezeichnet man als Typsicherheit. In großen Programmen kann es unschätzbar sein, bei versehentlichen Zuweisungen der falschen Klasse zu stoppen. Mit einer Gruppe von Vorlagen, CArray, CList und CMap, kann man in einfacher Weise ein Array, eine Liste oder eine Tabelle erstellen, um Objekte nur des spezifizierten Typs zu speichern, zu verwenden und zurückzugeben. Vorlagen (Templates) sind ein kompliziertes Thema, aber Sie brauchen Vorlagen nicht selbst zu schreiben. Die in der Header-Datei afxtempl.h definierten und von MFC bereitgestellten Vorlagen genügen für diese typsicheren Auflistungsklassen. In bezug auf den vorliegenden Abschnitt stellt man sich Templates einfach als große Makros vor, die beim Kompilieren auf der Basis Ihrer Parameter eine Menge Code generieren.

Die Vorlagen bieten den Zugriff auf alle normalen Funktionen in den Feld-, Listen- oder Tabellenklassen, die in den vorherigen Abschnitten behandelt wurden. Statt allerdings die auf dem allgemeinen Objekt CObject basierenden Parameter und Rückgabewerte zu verwenden, können Sie Ihre eigenen Typen als Parameter und Rückgabewerte definieren.

Um die Vorlagen in Ihrem Programm einzusetzen, müssen Sie die folgende Header- Zeile in alle Module (.cpp/.h-Dateien) einbinden, die auf die Vorlagendefinitionen zurückgreifen:

#include "afxtempl.h"

Sie können dann Ihre eigene benutzerdefinierte, typsichere Klasse gemäß der folgenden Vorlagensyntax für ein Array von benutzerdefinierten Objekten definieren:

CArray<CMyCustomClass*, CMyCustomClass *>myCustomClassArray;

Die Symbole < und > in der obigen Definition sind als spitze Klammern (und nicht als Bedingungsoperatoren für Größer als bzw. Kleiner als) zu interpretieren. Die obige Zeile verwendet die Vorlage CArray, um eine Instanz von myCustomClassArray zu erzeugen. Der erste Parameter CMyCustomClass* spezifiziert Typen von Objektzeigern, die das Array zurückgeben soll, wenn man mit GetAt und anderen Zugriffsfunktionen arbeitet. Der zweite Parameter CMyCustomClass* legt den Typ fest, der für die Definitionen der Eingabeparameter zu verwenden ist. Dann akzeptieren alle Funktionen, die Objekte speichern, wie etwa SetAt und Add, nur Zeiger auf Objekte der speziellen CMyCustomClass.

Zum Beispiel kann man ein Array erzeugen, das nur Zeiger auf die spezielle Klasse CPlanetDets übernimmt und zurückgibt. Die Klasse ist wie folgt definiert (und implementiert):

class CPlanetDets : public CObject
{
public:
CPlanetDets(double dDiameter, double dGravity, double dDistFromSun, double dSidereal):
m_dDiameter(dDiameter), m_dGravity(dGravity),
m_dDistFromSun(dDistFromSun), m_dSidereal(dSidereal) {}
double m_dDiameter,m_dGravity,m_dDistFromSun,m_dSidereal;
};

Um ein typsicheres auf CArray basierendes Array namens myPlanetArray zu deklarieren, können Sie dann den folgenden Code schreiben:

CArray<CPlanetDets*, CPlanetDts*> myPlanetArray;

Diese Zeile deklariert, daß myPlanetArray nur Zeiger auf ein CPlanetDets-Objekt akzeptiert und Zeiger auf ein CPlanetDets-Objekt zurückgibt. Das neue Array kann man dann wie folgt einsetzen:

myPlanetArray.Add(new CPlanetDets (4878, 0.054, 57.91, 87.969));
myPlanetArray.Add(new CPlanetDets (12100, 0.815, 108.21, 224.701));
myPlanetArray.Add(new CPlanetDets (12756, 1.000, 149.60, 365.256));
for(int i=0;i<myPlanetArray.GetSize();i++)
TRACE("Durchmesser = %f\n", myPlanetArray[i]->m_dDiameter);

Diese Zeilen erzeugen drei neue Objekte vom Typ CPlanetDets und fügen sie in das Array ein. Die letzte Zeile zeigt den Durchmesser im Makro TRACE an, ohne daß man den Typ des Rückgabewerts von myPlanetArray[i] umwandeln muß, da es sich bereits um einen Zeiger auf den Typ CPlanetDets* handelt.

Allerdings vergessen Sie vielleicht im Zuge der Programmentwicklung die genaue Natur von myPlanetArray und versuchen, statt dessen ein CStatic-Objekt hinzuzufügen:

myPlanetArray.Add(new CStatic());

Zum Glück bemerkt der Compiler diese Übertretung und löst einen Compiler-Fehler wie etwa den folgenden aus:

... error C2664: 'Add' : Konvertierung des Parameters 1 von 'class CStatic *' in 'class CPlanetDets *' nicht moeglich

Der Fehler bliebe jedoch unbemerkt, wenn man ein CObArray verwendet hätte, um die Planetendaten zu speichern:

CObArray myPlanetArray;

Das CStatic-Objekt läßt sich ohne weiteres zusammen mit den CPlanetDets-Objekten speichern, bewirkt aber unvorhersehbares Unheil, wenn man versucht, das CStatic- Objekt abzurufen und annimmt, daß es sich um ein CPlanetDets-Objekt handelt.

Die Vorlage zur Generierung von typsicheren Listen ist CList. Sie weist die gleiche allgemeine Form wie CArray auf:

CList<CMyCustomClass*, CMyCustomClass *> myCustomClassList;

Auch hier ist der erste Parameter der erforderliche Rückgabetyp des Objekts. Der zweite Parameter spezifiziert die akzeptierten Objekttypen für Funktionen, die Elemente für die Speicherung akzeptieren.

Alle Funktionen, die es für Listen gibt, sind auch für Ihre eigenen speziellen typsicheren benutzerdefinierten Listen verfügbar. Auch diese Funktionen prüfen und geben die spezifizierten Typen zurück. Demzufolge sieht der äquivalente Code mit einer Listenklasse für das Speichern der Planetendaten etwa folgendermaßen aus:

CList<CPlanetDets*,CPlanetDets*> myPlanetList;
myPlanetList.AddTail(new CPlanetDets (4878, 0.054, 57.91, 87.969));
myPlanetList.AddTail(new CPlanetDets (12100, 0.815, 108.21, 224.701));
myPlanetList.AddTail(new CPlanetDets (12756, 1.000, 149.60, 365.256));
POSITION pos = myPlanetList.GetHeadPosition();
while(pos) TRACE("Durchmesser = %f\n", myPlanetList.GetNext(pos)->m_dDiameter);

Die Vorlage für benutzerdefinierte Tabellen unterscheidet sich von Listen und Arrays darin, daß vier Parameter erforderlich sind: ein Eingabe- und ein Rückgabewert sowohl für den Schlüssel als auch den Elementwert. Die allgemeine Form lautet damit:

CMap<MyType, MyArgType, CMyCustomClass *, CMyCustomClassArg *> myCustomClassMap;

Der erste Parameter, MyType, legt den intern gespeicherten Schlüsselwert für jede Tabellenzuordnung fest. Das kann einer der Basistypen wie int, WORD, DWORD, double, float oder CString sein oder ein Zeiger auf einen eigenen speziellen Typ.

Der zweite Parameter, MyArgType, spezifiziert den Argumenttyp, der bei der Übergabe der Schlüsselwerte in und aus Tabellenfunktionen zu verwenden ist.

Mit dem dritten Parameter, CMyCustomClasss *, legt man fest, wie die internen Elementwerte zu speichern sind (als spezielle typsichere Zeiger auf Ihre Objekte).

Der vierte Parameter, CMyCustomClassArg *, spezifiziert den Argumenttyp, der für die Übergabe der Elementwerte in und aus den Tabellenfunktionen zu verwenden ist. Um beispielsweise die Daten der Planeten mit deren Namen zu verbinden, könnte man folgendes kodieren:

CMap<CString, LPCSTR, CPlanetDets*, CPlanetDets*> myPlanetMap;
myPlanetMap.SetAt("Merkur",
new CPlanetDets(4878, 0.054, 57.91, 87.969));
myPlanetMap.SetAt("Venus",
new CPlanetDets(12100, 0.815, 108.21, 224.701));
myPlanetMap.SetAt("Erde",
new CPlanetDets(12756, 1.000, 149.60, 365.256));
CPlanetDets* pPlanet = NULL;
if (myPlanetMap.Lookup("Venus", pPlanet))
TRACE("Durchmesser = %f\n", pPlanet->m_dDiameter);

Die Tabellendeklaration gibt an, daß die Objekte intern als CStrings zu speichern sind, aber LPCSTR (Zeiger auf konstante Zeichenarrays) als Übergabewerte in und aus der Tabelle zu verwenden sind. Die Angaben zu den Planeten selbst werden sowohl intern gespeichert als auch als Zeiger auf CPlanetDets-Objekte zugänglich gemacht (wie zum Beispiel als CPlanetDets*).

Mögliche Probleme beim Einsatz von Hash-Schlüsseln für Tabellen

Beachten Sie die Umwandlung der übergebenen Parameter in das interne Speichersystem auf der Basis von Hash-Schlüsseln. Wenn Sie zum Beispiel den CString im obigen Beispiel durch einen anderen LPCSTR für das interne Speicherobjekt ersetzen müssen, scheitert die Funktion Lookup bei der Suche nach "Venus", da sie die Zeigerwerte (auf verschiedene Instanzen von "Venus") und nicht den Inhalt der Strings vergleicht.

Klassen zur Behandlung von Koordinaten

Da Windows eine grafisch orientierte Umgebung darstellt, muß man oftmals Punktpositionen, Rechtecke und Größen speichern. Drei MFC-Klassen unterstützen das Speichern und Manipulieren dieser Koordinaten: CPoint, CRect und CSize. Jede Klasse verfügt über mehrere Elementfunktionen und überladene Operatoren, die den größten Teil der Arbeit beim Addieren, Konstruieren und Finden von Ableitungen dieser Koordinaten realisieren.

Darüber hinaus verstehen verschiedene MFC- und GDI-Funktionen diese Typen oder die zugrundeliegenden Typen als Parameterwerte, so daß man keine umständlichen Operationen auszuführen hat, um Werte an diese Funktionen zu übergeben.

Die Klasse CPoint

CPoint kapselt eine POINT-Struktur, die lediglich eine x- und eine y-Position speichert, um einen Punkt auf einer zweidimensionalen Oberfläche darzustellen. Auf die Elemente x und y kann man immer direkt zugreifen, um deren aktuelle Werte zu erhalten oder zu setzen:

CPoint ptEins;
ptEins.x = 5;
ptEins.y = 20;
TRACE("Koordinate = (%d, %d)\n", ptEins.x, ptEins.y);

Diese Werte setzen Sie, wenn Sie ein CPoint-Objekt konstruieren, indem Sie die Werte an einen der verschiedenen Konstruktoren von CPoint übergeben, wie sie Tabelle F.4 zeigt.

Tabelle F.4: Konstruktortypen für die Klasse CPoint

Konstruktordefinition

Beschreibung

CPoint()

Konstruiert ein nicht initialisiertes Objekt.

CPoint(POINT ptInit)

Kopiert die Einstellungen aus einer POINT-Struktur oder einem anderen CPoint-Objekt.

CPoint(int x, int y)

Initialisiert das Objekt mit den Parameterwerten von x und y.

CPoint(DWORD dwInit)

Verwendet die unteren 16 Bit für den x-Wert und die höherwertigen 16 Bit für den y-Wert.

CPoint(SIZE sizeInit)

Kopiert die Einstellungen aus einer SIZE-Struktur oder einem CSize-Objekt.

Zum Beispiel können Sie die letzten Beispielzeilen durch die folgenden ersetzen und das gleiche Ergebnis erzielen:

CPoint ptEins(5,20);
TRACE("Koordinate = (%d, %d)\n", ptEins.x, ptEins.y);

Einer der nützlichsten Aspekte der Klasse CPoint sind die vielen überladenen Operatoren. Indem man die Operatoren +, -, += und -= mit anderen CPoint-, CRect- oder CSize-Objekten verwendet, kann man Koordinatenpaare zu/von anderen Koordinatenpaaren, Rechtecken oder Größen addieren/subtrahieren. Der längere herkömmliche Weg, um zwei Punkte voneinander zu subtrahieren, um einen dritten zu erhalten, sieht zum Beispiel folgendermaßen aus:

CPoint ptEins(5,20);
CPoint ptZwei(25,40);
CPoint ptDrei;
ptDrei.x = ptZwei.x - ptEins.x;

Das Ganze läßt sich mit den überladenen Operatoren folgendermaßen vereinfachen:

CPoint ptEins(5,20);
CPoint ptZwei(25,40);
CPoint ptDrei = ptZwei - ptEins;

Man kann auch die Koordinaten des einen Punktes zu einem anderen addieren:

ptZwei += ptEins;

Mit den überladenen logischen Operatoren == und != lassen sich auch Vergleiche ausführen. Um zum Beispiel zu prüfen, ob ptZwei sowohl im x- als auch im y-Wert mit ptEins gleich ist, schreibt man folgenden Code:

if(ptEins == ptZwei) TRACE("Die Punkte sind identisch.");

Die Funktion Offset addiert einen Verschiebungswert (Offset), der durch die übergebenen x- und y-Werte, eine CPoint-Klasse, eine POINT-Struktur, eine CSize-Klasse oder eine SIZE-Struktur spezifiziert ist. Demzufolge sind die beiden folgenden Zeilen funktionell identisch:

ptEins.Offset(75, -15);
ptEins-=CPoint(-75, 15);

Die Klasse CRect

Die Klasse CRect kapselt eine RECT-Struktur, um zwei Koordinatenpaare aufzunehmen, die ein Rechteck durch die Punkte der linken oberen und unteren rechten Ecke beschreiben. Ein CRect-Objekt können Sie mit mehreren Konstruktoren erstellen, wie sie Tabelle F.5 zeigt.

Tabelle F.5: Konstruktortypen für die Klasse CRect

Konstruktordefinition

Beschreibung

CRect()

Konstruiert ein nicht initialisiertes Objekt.

CRect(const RECT& rcInit)

Kopiert die Einstellungen aus einer anderen RECT-Struktur oder einem CRect-Objekt.

CRect(LPRECT lprcInit)

Kopiert die Einstellungen über einen RECT- oder CRect-Zeiger.

CRect(int l, int t, int r, int b)

Initialisiert die Koordinaten des linken, oberen, rechten und unteren Parameters.

CRect(POINT point, SIZE size)

Initialisiert von einer POINT-Struktur oder einem CPoint-Objekt und einer SIZE-Struktur oder einem CSize-Objekt.

CRect(POINT ptTL, POINT ptBR)

Initialisiert von einem POINT links oben und einem POINT rechts unten.

Nachdem Sie ein CRect-Objekt konstruiert haben, können Sie einzeln auf die Elemente top, left, bottom und right zugreifen, indem Sie die Typumwandlung (LPRECT) verwenden, um die Werte in eine RECT-Struktur wie in den folgenden Zeilen umzuwandeln:

CRect rcEins(15,15,25,20);
((LPRECT)rcEins)->bottom += 20;
TRACE("Rechteck: (%d,%d)-(%d,%d)",
((LPRECT)rcEins)->left,((LPRECT)rcEins)->top,
((LPRECT)rcEins)->right,((LPRECT)rcEins)->bottom);

Alternativ können Sie auf die Elemente entweder über den CPoint für oben links oder den CPoint für unten rechts zugreifen. Die Funktionen TopLeft und BottomRight liefern Referenzen auf diese Elementobjekte zurück. Wenn man entweder auf den Punkt oben links oder unten rechts zugreift, kann man die Punkte dann mit einer der CPoint-Funktionen aus dem vorherigen Abschnitt manipulieren. Beispielsweise sind die folgenden Zeilen funktionell zu den vorherigen identisch, unterscheiden sich aber darin, daß sie das Rechteck mit CPoint-Objekten konstruieren und darauf zugreifen:

CRect rcEins(CPoint(15,15),CPoint(25,20));
rcEins.BottomRight().y += 20;
TRACE("Rechteck: (%d,%d)-(%d,%d)",
rcOne.TopLeft().x,rcOne.TopLeft().y,
rcOne.BottomRight().x,rcOne.BottomRight().y);

Mit der Funktion SetRect können Sie auch die Koordinaten setzen, wobei Sie vier Integer-Werte für die x- und y-Koordinaten der oberen linken und der unteren rechten Ecke übergeben. Die Funktion SetRectEmpty setzt alle Koordinaten auf null, um ein NULL-Rechteck zu erzeugen. Die Funktion IsRectNull liefert TRUE, wenn sie auf einem derartigen NULL-Rechteck aufgerufen wird, und IsRectEmpty gibt TRUE zurück, wenn sowohl die Breite als auch die Höhe gleich null sind (selbst wenn einzelne Werte ungleich null sind).

Mehrere Hilfsfunktionen unterstützen die Berechnung verschiedener Aspekte der Rechteckgeometrie. Die Breite und Höhe kann man mit den Funktionen Width bzw. Height ermitteln. Beide Funktionen liefern den diesbezüglichen Integer-Wert zurück. Alternativ kann man ein CSize suchen, das sowohl Breite als auch Höhe repräsentiert, indem man die Funktion Size aufruft. Beispielsweise zeigt die folgende Zeile die Breite und Höhe des Rechtecks rcEins an:

TRACE("Breite = %d, Höhe = %d\n", rcEins.Width(), rcEins.Height());

Oftmals muß man den Punkt im Zentrum des Rechtecks kennen. Dazu kann man die Funktion CenterPoint aufrufen, die ein CPoint-Objekt zurückgibt, das den Mittelpunkt des Rechtecks darstellt.

Das folgende Beispiel bestimmt mit dieser Funktion den Mittelpunkt des Client-Bereichs eines Fensters und zeichnet an dieser Stelle einen Punkt:

CRect rcClient;
GetClientRect(&rcClient);
dc.SetPixel(rcClient.CenterPoint(),0);

Mit den Funktionen UnionRect und InterSectRect kann man auch die Vereinigungs- bzw. Schnittmenge zweier Rechtecke ermitteln. Beide Funktionen übernehmen zwei Quellrechtecke als Parameter und setzen die Koordinaten des aufrufenden CRect-Objekts auf die Vereinigung oder den Schnitt. Die Vereinigung ist das kleinste Rechteck, das die beiden Quellrechtecke umschließt. Der Schnitt ist das größte Rechteck, daß von beiden Quellrechtecken umschlossen wird. Die Zeichnung in Abbildung F.1 zeigt Vereinigung und Schnitt zweier Quellrechtecke mit den Bezeichnern A und B.

Zeichnung kommt nach.

Abbildung F.1: Vereinigungs- und Schnittmenge zweier Rechtecke

Die folgenden Zeilen berechnen den Schnitt und die Vereinigung der Quellrechtecke rcEins und rcZwei:

CRect rcEins(10,10,100,100);
CRect rcZwei(50,50,150,200);
CRect rcUnion, rcIntersect;
rcUnion.UnionRect(rcEins, rcZwei);
rcIntersect.IntersectRect(rcEins, rcZwei);

Wenn Sie diesen Code ausführen, wird rcUnion auf die Koordinaten (10,10)- (150,200) und rcIntersect auf die Koordinaten (50,50)-(100,100) gesetzt.

Mit der Funktion SubtractRect kann man ein Rechteck von einem anderen abziehen. Das Ergebnis ist das kleinste Rechteck, das alle Punkte enthält, die nicht von den beiden Quellrechtecken geschnitten werden (oder der kleinste nicht überlappende Bereich). Wenn Sie beispielsweise die folgenden Zeilen in eine OnPaint-Behandlungsroutine einfügen, können Sie sich die Wirkung von SubtractRect ansehen. Der Code zieht rcZwei von rcEins ab und liefert rcDrei. Das Ergebnis der Subtraktion ist der Bereich, der am unteren Rand der Zeichnung in Blau dargestellt ist, wie es Abbildung F.2 zeigt.

CRect rcEins(10,10,220,220), rcZwei(50,50,150,260), rcDrei;
rcDrei.SubtractRect(rcZwei, rcEins);
dc.FillSolidRect(rcEins, RGB(255,0,0)); // Rot
dc.FillSolidRect(rcZwei, RGB(0,255,0)); // Grün
dc.FillSolidRect(rcDrei, RGB(0,0,255)); // Blau

Wenn Sie diesen Code ausführen, enthält das resultierende Rechteck rcDrei die Koordinaten (50,220)-(150,26).

Abbildung F.2:
Die Wirkung einer Subtraktionsoperation auf zwei teilweise überlappende Rechtecke

Die Größe eines Rechtecks läßt sich mit den Funktionen InflateRect und DeflateRect vergrößern bzw. verkleinern. Beide Funktionen haben mehrere Formen, die verschiedene Typen von Parametern gemäß Tabelle F.6 akzeptieren.

Tabelle F.6: Parameterformen für InflateRect und DeflateRect

Parameter

Beschreibung

(int x, int y)

Vergrößert oder verkleinert die linke und rechte Seite nach dem Wert x sowie die obere und untere Seite nach dem Wert y.

(SIZE size)

Vergrößert oder verkleinert die linke und rechte Seite nach size.cx sowie die obere und untere Seite nach dem Wert von size.cy.

(LPCRECT lpRect)

Vergrößert jede Seite um die entsprechenden Werte von left, top, right und bottom der Struktur lpRect.

(int l, int t, int r, int b)

Vergrößert jede Seite um die entsprechenden Werte für links (l), oben (t), rechts (r) und unten (b).

Der folgende Beispielcode vergrößert rcEins und verkleinert rcZwei:

CRect rcEins(10,10,100,100);
CRect rcZwei(50,50,150,200);
rcEins.InflateRect(5,5);
rcZwei.DeflateRect(10,20,30,40);

Die Ausführung dieser Zeilen setzt rcEins auf die Koordinaten (5,5)-(105,105) und rcZwei auf die Koordinaten (60,70)-(120,160).

Eine Trefferprüfung läßt sich ausführen, indem man ermittelt, ob ein bestimmter Punkt (etwa bei einem Mausklick) innerhalb der Grenzen eines Rechtecks liegt. Dazu ruft man die Funktion PtInRect auf und übergibt den zu testenden Punkt. Wenn der Punkt innerhalb des Rechtecks liegt, gibt die Funktion TRUE zurück, andernfalls FALSE.

In den folgenden Zeilen wird die Meldung Treffer! - ptTest1 angezeigt, da ptTest1 innerhalb des Testbereichs von rcTestArea liegt, während ptTest2 nicht in diesen Bereich fällt. Demzufolge liefert PtInRect den Wert TRUE für ptTest1 und FALSE für ptTest2:

CRect rcTestArea(10,20,440,450);
CPoint ptTest1(200,200), ptTest2(500,500);
if (rcTestArea .PtInRect(ptTest1)) AfxMessageBox("Treffer! - ptTest1");
if (rcTestArea .PtInRect(ptTest2)) AfxMessageBox("Treffer! - ptTest2");

Für CRect-Objekte gibt es verschiedene überladene Operatoren, die in Tabelle F.7 aufgeführt sind.

Tabelle F.7: Überladene Operatoren für CRect-Objekte

Operator

Beschreibung

=

Kopiert wie bei einer normalen numerischen Zuweisung alle Koordinaten vom rechten Operanden (Rechteck) in das linke Rechteck.

+

Der Operator führt entweder eine Verschiebung der Rechteckposition aus, wenn zum Rechteck ein CPoint- oder CSize-Objekt addiert wird, oder vergrößert die Koordinaten für die entsprechenden Seiten, wenn ein CRect-Objekt addiert wird.

-

Analog zu +, außer daß die Koordinaten in negativer Richtung verschoben oder bei einem CRect verkleinert werden.

+=

Wirkung wie bei +, beeinflußt aber nur das aktuelle Rechteck.

-=

Wirkung wie bei -, beeinflußt aber nur das aktuelle Rechteck.

&

Erzeugt ein Rechteck als Schnittmenge zweier Rechteckoperanden.

|

Erzeugt ein Rechteck als Vereinigungsmenge zweier Rechteckoperanden.

&=

Wirkung wie &, beeinflußt aber nur das aktuelle Rechteck.

|=

Wirkung wie bei |, beeinflußt aber nur das aktuelle Rechteck.

==

Liefert TRUE, wenn die Rechtecke identisch sind, ansonsten FALSE.

!=

Liefert FALSE, wenn die Rechtecke identisch sind, andernfalls TRUE.

Die folgenden Zeilen zeigen, wie man mit überladenen CRect-Operatoren das Rechteck rcStart manipuliert:

CRect rcStart(10,10,100,100);
rcStart = rcStart + CPoint(5,5);
rcStart -= CSize(5,5);
rcStart += CRect(1,2,3,4);
if (rcStart == CRect(9,8,103,104)) AfxMessageBox("TRUE");

Die letzte Bedingung liefert TRUE, weil die Koordinaten nach Ausführung der Anweisungen auf (9,8)-(103,104) gesetzt sind. Demzufolge erscheint das Meldungsfeld.

Die Funktion NormalizeRect

Bei manchen Operationen entstehen Werte für den Punkt oben links, die größer als für den Punkt unten rechts sind. In diesem Fall kann die Breite oder Höhe einen negativen Wert annehmen, so daß andere Funktionen einen Fehler liefern. Wenn Sie mit derartigen Ergebnissen rechnen müssen, rufen Sie die Funktion NormalizeRect auf, die die Koordinaten korrigiert, so daß die Koordinaten für den Punkt oben links kleinere Werte als die Koordinaten für den Punkt unten rechts aufweisen.

 

Die Klasse CSize

Die Klasse CSize kapselt die Struktur SIZE und bietet verschiedene Konstruktoren und überladene Operatoren, mit denen sich die internen Werte cx und cy, die eine Größe definieren, manipulieren lassen. Tabelle F.8 zeigt die Konstruktoren, mit denen man eine Instanz eines CSize-Objekts erzeugen kann.

Tabelle F.8: Konstruktortypen für die Klasse CSize

Konstruktordefinition

Beschreibung

CSize()

Erzeugt ein nicht initialisiertes CSize-Objekt.

CSize(SIZE sizeInit)

Kopiert die Werte cx und cy aus einem anderen CSize-Objekt oder einer SIZE-Struktur.

CSize(initCX, initCY)

Initialisiert das Objekt mit initCX für die horizontale Größe und initCY für die vertikale.

CSize(POINT ptInit)

Initialisiert das Objekt mit den Werten x und y aus einem CPoint-Objekt oder einer POINT-Struktur.

CSize(DWORD dwSize)

Setzt den Wert cx auf das niederwertige Wort (die unteren 16 Bit) von dwSize und cy auf das höherwertige Wort (die oberen 16 Bit).

Die Elemente cx und cy kann man direkt manipulieren:

CSize tstSize(10,10);
tstSize.cx = tstSize.cy * 2;

Die einzigen Funktionen, die die Klasse CSize bietet, sind die überladenen Operatoren gemäß Tabelle F.9.

Tabelle F.9: Überladene Operatoren für CSize

Operator

Beschreibung

+

Addiert zwei SIZE-Objekte.

-

Subtrahiert ein SIZE-Objekt von einem anderen.

+=

Addiert ein SIZE-Objekt.

-=

Subtrahiert ein SIZE-Objekt.

==

Bestimmt, ob die beiden Größen gleich sind, und liefert in diesem Fall TRUE zurück.

!=

Bestimmt, ob die beiden Größen unterschiedlich sind, und liefert in diesem Fall TRUE zurück.

Diese Operatoren kann man wie normale arithmetische Operatoren einsetzen. Sie beeinflussen sowohl cx als auch cy, wie es die folgenden Zeilen zeigen, die den Inhalt von tstSize manipulieren:

CSize tstSize(10,15);
tstSize += tstSize + tstSize - CSize(1,2);
if (tstSize == CSize(29,43)) AfxMessageBox("TRUE");

Bei Ausführung des Codes zeigt das Meldungsfeld TRUE, weil tstSize am Ende eine Größe von 29 mal 43 hat.

Klassen zur Behandlung der Zeit

In vielen Anwendung sind Datums- und Zeitwerte zu speichern. Manchmal muß man die verstrichene Zeit und Zeitspannen zwischen gespeicherten Datums-/Zeitwerten berechnen und diese Werte in verständliche Zeichenfolgen formatieren können.

MFC stellt vier Klassen bereit, um Datum und Uhrzeit manipulieren und speichern zu können. Ursprünglich gab es nur zwei Klassen: CTime und CTimeSpan, die auf dem unter UNIX üblichen System time_t (langer 4 Byte-Wert) basieren (die Anzahl der seit 1970 verstrichenen Sekunden). Allerdings erweist sich die Auflösung von 1 Sekunde und der begrenzte Datumsbereich zwischen 1970 und 2038 bei vielen Anwendungen als unzureichend. Aus diesen Gründen wurden die beiden neuen Klassen COleDateTime und COleDateTimeSpan eingeführt, auf die man vorzugsweise statt CTime und CTimeSpan in neueren Anwendungen zurückgreifen sollte.

COleDateTime basiert auf einer zugrundeliegenden DATE-Struktur (die eigentlich nur ein double-Wert ist). Durch die größere Kapazität dieses Typs kann COleDateTime einen Datumsbereich zwischen dem 1. Januar 100 und dem 31. Dezember 9999 bei einer Auflösung von 1 Millisekunde abdecken. Die Differenz zwischen zwei COleDateTime- Werten läßt sich mit dem COleDateTimeSpan-Objekt darstellen und manipulieren.

Aufgrund der Ähnlichkeit zwischen der Klasse CTime und der neueren COleDateTime beschreiben die folgenden Abschnitte lediglich COleDateTime, auch wenn viele der Funktionen in den Versionen von CTime identisch sind.

CTime bei Datenbanken

Bei ODBC-basierten Datenbanken kann es bequemer sein, mit CTime zu arbeiten, da die RFX-Transfermakros des Recordsets direkt nur mit CTime- Objekten umgehen und COleDateTime-Objekte ohne Umwandlungen nicht bearbeiten können. Wenn Sie mit DAO-Datenbanken arbeiten, können Sie COleDateTime ohne Umwege einsetzen.

Die Klasse COleDateTime

Die Verbindung von COleDateTime mit OLE ergibt sich aus der Tatsache, daß man diese Klasse mit der Struktur VARIANT einsetzen kann, die man häufig in der OLE-Automatisierung antrifft. Durch den breiten Bereich von Datums-/Zeitwerten muß COleDateTime insbesondere in OLE-Umgebungen in der Lage sein, zwischen all diesen verschiedenen Typen konvertieren zu können. Diese Unterstützung spiegelt sich in den zahlreichen Konstruktorformen wider, die Tabelle F.10 zeigt.

Tabelle F.10: Mit COleDateTime verwendete Konstruktortypen

Konstruktordefinition

Beschreibung

COleDateTime()

Erzeugt ein nicht initalisiertes COleDateTime-Objekt.

COleDateTime(const COleDateTime& datesrc)

Kopiert die Werte aus einem anderen COleDateTime-Objekt.

COleDateTime(int nYear, int nMonth, int nDay, int nHour, int nMinute, int nSecond)

Initialisiert Datum und Uhrzeit mit den übergebenen Werten.

COleDateTime(const VARIANT& varSrc)

Konvertiert einen Datums-/Zeitwert aus einer VARIANT-Struktur.

COleDateTime(DATE dtSrc)

Kopiert einen Datums-/Zeitwert aus einer DATE-Struktur.

COleDateTime(time_t timeSrc)

Kopiert einen Datums-/Zeitwert aus einer time_t-Struktur im Stil von UNIX.

COleDateTime(WORD wDosDate)

Kopiert einen Datums-/Zeitwert aus den WORD-Werten wDosTime im Stil von MS-DOS.

COleDateTime(const SYSTEMTIME& systimeSrc))

Kopiert einen Datums-/Zeitwert aus einer SYSTEMTIME-Struktur.

COleDateTime(const FILETIME& filetimeSrc)

Kopiert einen Datums-/Zeitwert aus einer FILETIME-Struktur.

Wenn Sie COleDateTime mit einem gültigen Datums-/Zeitwert konstruiert haben, ist das Objekt mit einem Statusflag für den gültigen Zustand markiert (COleDateTime::valid ). Andernfalls ist das Statusflag ungültig (COleDateTime::invalid). Dieser Status läßt sich mit der Elementfunktion GetStatus ermitteln. Die Funktion liefert den relevanten Flagwert zurück. Man kann das Flag auch explizit durch Übergabe des gewünschten Wertes an die Funktion SetStatus setzen.

Das Statusflag wird auch aktualisiert, wenn man Datums-/Zeitwerte in das Objekt mit Hilfe der Funktion SetDateTime setzt. Die Funktion übernimmt sechs Parameter für das Jahr (100 bis 9999), den Monat (1 bis 12), den Tag (1 bis 31), die Stunde (0 bis 23), die Minute (0 bis 59) und die Sekunde (0 bis 59). Die Datums- oder Zeitkomponenten lassen sich auch separat festlegen: mit der Funktion SetDate, der man Jahr, Monat und Tag übergibt, bzw. mit der Funktion SetTime, der man nur die Werte für Stunde, Minute und Sekunde übergibt.

Die aktuelle Systemzeit läßt sich mit der statischen Funktion GetCurrentTime ermitteln und mit dem überladenen Operator = in ein COleDateTime-Objekt übertragen:

COleDateTime dtCurrent;
dtCurrent = COleDateTime::GetCurrentTIme();

Nach Ausführung dieser Zeilen enthält dtCurrent die aktuelle Systemzeit (Datum und Uhrzeit) des Computers.

Die gleichen Werte (in den gleichen Bereichen) lassen sich mit den Funktionen GetYear , GetMonth, GetDay, GetHour, GetMinute und GetSecond abrufen. Weiterhin gibt es die abgeleiteten Funktionen GetDayOfWeek und GetDayOfYear. Die Funktion GetDayOfWeek liefert den Wochentag mit den Werten 1 bis 7 zurück, wobei die 1 für Sonntag steht. Die Funktion GetDayOfYear liefert einen Wert im Bereich von 1 bis 366, beginnend mit dem 1. Januar.

Mit der Funktion Format läßt sich ein CString erstellen, der die Werte im gewünschten Anzeigeformat ausgibt. Diese Funktion gehört mit zu den nützlichsten COleDateTime- Funktionen, da man verschiedenartige - in Tabelle F.11 aufgeführte - Formatcodes übergeben kann, um das genaue Format festzulegen. Die Codes übergibt man entweder als String oder als Bezeichner von Stringressourcen. Um verschiedene Aspekte der Formatierung zu berücksichtigen, kann man verschiedene Einzelcodes verbinden.

Die Werte werden außerdem durch die Ländereinstellungen modifiziert. Das bezieht sich auf die Namen von Tagen und Monaten, die Schreibweise des Datums und die Angabe der Uhrzeit (24 Stunden, AM/PM).

Tabelle F.11: Formatcodes für die Formatierung der Textausgaben von COleDateTime

Code

Beispiel

Beschreibung

%a

Di

abgekürzter Wochentag

%A

Dienstag

Wochentag

%b

Apr

abgekürzter Monat

%B

April

Monat

%c

20.04.99 18:05:01

Datum und Uhrzeit gemäß aktueller Ländereinstellung

%d

04

Tag des Monats (01 bis 31)

%H

18

Stunde (00 bis 23) im 24-Stunden-Format

%I

06

Stunde (01 bis 12) im 12-Stunden-Format

%j

110

Tag des Jahres

%m

04

Monat (01 bis 12)

%M

05

Minute (01 bis 59)

%p

PM

AM/PM bei lokalen Einstellungen

%S

01

Sekunde (01 bis 59)

%U

16

Woche des Jahres (00 bis 51), wobei der Sonntag als erster Tag der Woche gilt

%w

2

Wochentag (0 bis 6), wobei 0 für Sonntag steht

%W

16

Woche des Jahres (00 bis 51), wobei Montag als erster Tag der Woche gilt

%x

20.04.99

Datum im aktuellen lokalen Format

%X

18:05:01

Zeit im aktuellen lokalen Format

%y

99

zweistelliges Jahr (00 bis 99)

%Y

1999

vierstelliges Jahr (0100 bis 9999)

%z oder %Z

MEZ (Mitteleurop. Sommerzeit)

Name der Zeitzone/Abkürzung

% %

%

Prozentzeichen

%#c

Dienstag, 20. April 1999 18:05:01

Langes Datum und Uhrzeit im aktuellen lokalen Format

%#x

Dienstag, 20. April 1999

Langes Datum im aktuellen lokalen Format

Die folgenden Zeilen generieren ein Meldungsfeld, das Datum und Uhrzeit des Computers anzeigt:

COleDateTime dtCurrent;
dtCurrent = COleDateTime::GetCurrentTime();
AfxMessageBox(dtCurrent.Format("Heute ist %a %b %d, %Y"));

Die Ausführung dieser Zeilen zeigt die Systemzeit im Meldungsfeld mit dem folgenden Format an:

Heute ist Di Apr 20, 1999

Mit COleDateTime können Sie versuchen, ein Datum und eine Uhrzeit zu bestimmen, indem Sie ParseDateTime aufrufen und einen zu analysierenden String sowie ein Flag übergeben, um festzulegen, daß nur das Datum bzw. die Uhrzeit gefordert wird. ParseDateTime untersucht dann den String nach der Uhrzeit im Format HH:MM:SS und einem Datum im Format DD/MM/YYYY oder in einem langen Format wie January 18th, 1998. Wenn man nur nach der Uhrzeit suchen möchten, kann man als zweiten Flag- Parameter den Wert VAR_TIMEVALUEONLY übergeben oder alternativ VAR_DATEVALUEONLY nur für das Datum. Soll lediglich das für den Computer gültige Standardformat in die Prüfung einbezogen werden und nicht die vom Benutzer überschriebenen Formate, übergeben Sie als Flag den Wert LOCALE_NOUSEROVERRIDE.

Es gibt auch mehrere überladene Operatoren, mit denen man COleDateTimeSpans addieren und subtrahieren sowie Datums-/Zeitwerte mit anderen Datums-/Zeitwerten vergleichen kann, wie es aus Tabelle F.12 hervorgeht.

Tabelle F.12: Überladene Operatoren bei COleDateTime

Operator

Beschreibung

=

Kopiert einen Datums-/Zeitwert aus einem anderen COleDateTime-Objekt, einer VARIANT-Struktur, einer DATE-Struktur, einer time_t-Struktur, einer SYSTEMTIME-Struktur oder einer FILETIME-Struktur.

+

Addiert einen COleDateTimeSpan-Wert zu einem COleDateTime-Wert.

-

Subtrahiert einen COleDateTimeSpan-Wert von einem COleDateTime-Wert oder zwei COleDateTime-Objekte voneinander und liefert ein COleDateTimeSpan-Ergebnis.

+=

Addiert einen COleDateTimeSpan-Wert zum aktuellen COleDateTime-Objekt.

-=

Subtrahiert einen COleDateTimeSpan-Wert vom aktuellen COleDateTime-Objekt.

==

Prüft, ob zwei COleDateTime-Objekte identische Datums-/Zeitwerte speichern.

!=

Prüft, ob zwei COleDateTime-Objekte unterschiedliche Datums-/Zeitwerte speichern.

<

Prüft, ob ein COleDateTime-Objekt kleiner als ein anderes ist.

>

Prüft, ob ein COleDateTime-Objekt größer als ein anderes ist.

<=

Prüft, ob ein COleDateTime-Objekt größer oder gleich einem anderen ist.

>=

Prüft, ob ein COleDateTime-Objekt größer oder gleich einem anderen ist.

Die Klasse COleDateTimeSpan

Ein COleDateTimeSpan-Objekt kann Differenzen zwischen zwei COleDateTime-Objekten speichern. Die Differenz läßt sich bilden, indem man ein COleDateTime-Objekt von einem anderen subtrahiert oder eine der COleDateTimeSpan-Konstruktorformen gemäß Tabelle F.13 verwendet.

Tabelle F.13: Konstruktortypen bei COleDateTimeSpan

Konstruktordefinition

Beschreibung

COleDateTimeSpan()

Erzeugt eine Zeitspanne, die auf null gesetzt ist.

COleDateTimeSpan(const COleDateTimeSpan& srcSpan)

Kopiert die Zeitspanne aus einem anderen COleDateTimeSpan-Objekt.

COleDateTimeSpan(long lDays, int nHours, int nMins, int nSecs)

Initialisiert die Zeitspanne mit den als Parameter übergebenen Werten.

COleDateTimeSpan(double dSpanSrc)

Initialisiert die Zeitspanne mit der übergebenen Anzahl der Tage.

Nachdem Sie ein COleDateTimeSpan-Objekt erzeugt haben, können Sie dessen Status mit den Funktionen GetStatus und SetStatus genau wie beim COleDateTime-Objekt prüfen bzw. setzen. Die einzigen Unterschiede bestehen darin, daß die Flags mit COleDateTimeSpan::valid und COleDateTimeSpan::invalid benannt sind.

Eine Zeitspanne kann man auch setzen, indem man die Anzahl der Tage, Stunden, Minuten und Sekunden als Integer-Parameter an die Funktion SetDateTimeSpan übergibt. Diese Werte lassen sich dann aus einem gültigen COleDateTimeSpan-Objekt mit den Funktionen GetDays, GetHours, GetMinutes und GetSeconds abrufen, die alle Long-Werte zurückgeben, um die einzelnen Teile des Wertes für die Zeitspanne darzustellen. Die komplette Zeitspanne, ausgedrückt in Tagen, Stunden, Minuten oder Sekunden, kann man mit den Funktionen GetTotalDays, GetTotalHours, GetTotalMinutes bzw. GetTotalSeconds in einem Double-Wert abrufen.

COleDateTimeSpan-Werte kann man als Zeichenfolgen in der gleichen Weise wie COleDateTime-Werte formatieren, indem man einen Formatstring mit den passenden Codes für Zeitspannen gemäß Tabelle F.11 übergibt.

Arithmetische Berechnungen mit COleDateTimeSpan-Objekten unterstützen verschiedene überladene Operatoren, mit denen man Zeitspannen addieren, subtrahieren und in Bedingungen testen kann, wie es aus Tabelle F.14 hervorgeht.

Tabelle F.14: Überladene Operatoren in COleDateTimeSpan

Operator

Beschreibung

=

Kopiert Zeitspannen aus anderen Zeitspannenwerten.

+

Addiert zwei Zeitspannen.

-

Subtrahiert eine Zeitspanne von einer anderen.

+=

Addiert eine Zeitspanne zum aktuellen Objekt.

-=

Subtrahiert eine Zeitspanne vom aktuellen Objekt.

==

Prüft, ob zwei Zeitspannen identisch sind.

!=

Prüft, ob zwei Zeitspannen verschieden sind.

<

Prüft, ob eine Zeitspanne kleiner als eine andere ist.

>

Prüft, ob eine Zeitspanne größer als eine andere ist.

<=

Prüft, ob eine Zeitspanne kleiner oder gleich einer anderen ist.

>=

Prüft, ob eine Zeitspanne größer oder gleich einer anderen ist.

Der folgende Beispielcode zeigt, wie man zwei COleDateTime-Ojekte mit dem überladenen Minusoperator (-) voneinander subtrahiert, um als Ergebnis eine COleDateTimeSpan zu erhalten:

COleDateTime = dtMoonwalk;
dtMoonwalk = COleDateTime(1969, 7, 20, 0, 0, 0);
COleDateTimeSpan dtDiff = COleDateTime::GetCurrentTime() - dtMoonwalk;
CString strMessage;
strMessage.Format("Tage seit dem ersten Mondspaziergang: %d ", (int)dtDiff.GetTotalDays());
AfxMessageBox(strMessage);

Die Klasse zur Zeichenfolgenbehandlung

Vor mehreren Jahren beneideten die C-Programmierer heimlich ein (und nur ein) Werkzeug, das BASIC-Programmierer zur Verfügung hatten: ausgeklügelte und einfache Zeichenfolgenbehandlung. Bei C++ läßt sich diese Funktionalität natürlich nachbilden und steht mit der MFC-Klasse CString zur Verfügung.

Die Zeichenfolgenbehandlung ist in Anwendungen häufig gefragt, und Visual C++- Anwendungen tendieren dazu, mit Instanzen von Objekten auf Basis der Klasse CString durchsetzt zu sein, um diese Aufgabe zu realisieren.

Die Klasse CString

CString-Objekte kann man in einfacher Weise als leerer String konstruieren oder initialisieren, indem man einen der vielen verschiedenen Typen von Textdarstellungssystemen an den Konstruktor übergibt. Die verschiedenen Formen der CString-Konstruktion sind in Tabelle F.15 aufgeführt.

Tabelle F.15: Mit CString verwendete Konstruktortypen

Konstruktordefinition

Beschreibung

CString()

Erzeugt einen leeren String der Länge null.

CString(const CString& strSrc)

Kopiert den Inhalt aus einem anderen String.

CString(LPCSTR lpsz)

Kopiert den Inhalt aus einem nullterminierten String.

CString(const unsigned char* psz)

Kopiert den Inhalt aus einem nullterminierten String.

CString(LPCSTR lpch, int nLength)

Kopiert nLength Zeichen aus einem Zeichenfeld.

CString(TCHAR ch, int nRepeat = 1)

Füllt den String mit nRepeat Kopien des Zeichens ch.

CString(LPCWSTR lpsz)

Kopiert einen nullterminierten Unicode-String.

Wenn Sie ein CString-Objekt konstruiert haben, gibt es viele Möglichkeiten, um Text hinzuzufügen oder zuzuweisen. Die überladenen Operatoren erlauben einfache Zuweisungen über den Operator = oder die Verkettung von zwei Strings mit den Operatoren + und +=, wie es die folgenden Zeilen zeigen:

CString strTest;
strTest = "Mr Gorsky";
strTest = "Viel Glück " + strTest;
AfxMessageBox(strTest);

Dieses Beispiel setzt den String anfänglich auf "Mr Gorsky". Anschließend wird mit dem Operator + der Text "Viel Glück " vorangestellt.

Die Länge eines Strings läßt sich mit der Elementfunktion GetLength ermitteln. Die Funktion liefert eine Ganzzahl zurück, die die momentane Anzahl der Zeichen im String darstellt. Mit der Funktion IsEmpty kann man auch testen, ob ein String leer ist. Die Funktion liefert TRUE zurück, wenn der String keine Zeichen enthält.

Die Funktion Empty dient dazu, den Inhalt eines CString-Objekts zu löschen. Das Objekt weist dann die Länge null auf.

Viele Funktionen erfordern Strings im alten Stil von C und keine CString-Objekte. Die Typumwandlungen (const char *) oder LPCTSTR erlauben diesen Funktionen den Zugriff auf den internen Puffer des CString-Objekts als würde es sich um einen nullterminierten C-String handeln (allerdings nur für Lesezugriff). Visual C++ wandelt implizit den CString in einen nullterminierten String um, wenn der Prototyp einer bestimmten Funktion das erfordert. Da jedoch einige Funktionen einen void* Prototyp haben, übergibt der Compiler einen Zeiger auf das CString-Objekt statt den erwarteten nullterminierten String. Somit müssen Sie die Umwandlung (LPCTSTR) auf dem CString-Objekt in eigener Regie erledigen.

Nullterminierte Strings

Bei nullterminierten Strings handelt es sich um Zeichenfelder, die das Ende des Strings mit einem Null-Zeichen markieren. Daher ist die Länge des Strings die Anzahl der Zeichen vor dem Null-Zeichen. Es gibt verschiedene Funktionen im Stil von C wie strlen, strcpy (die Länge ermitteln und einen String kopieren) und viele andere, die man für die Behandlung von nullterminierten Strings verwendet.

Mit den Funktionen GetAt und SetAt kann man auf einen String als Array von Zeichen zugreifen. GetAt liefert das Zeichen an der als Parameter übergebenen (von 0 an gerechneten) Position zurück. Die Funktion SetAt setzt ein Zeichen (als zweiten Parameter) an eine Position (erster Parameter) innerhalb der Länge des Strings. Anstelle der Funktion GetAt kann man auch mit dem Indexoperator [ ] einen Zeichenwert von einer bestimmten Position ermitteln. Beispielsweise tauschen die folgenden Zeilen die Zeichen b und g aus, um den Rechtschreibfehler zu korrigieren:

CString strText("Rechtschreigunb ");
TCHAR ch1 = strText.GetAt(11);
strText.SetAt(11, strText[14]);
strText.SetAt(14, ch1);

Mit den überladenen Operatoren <, <=, ==, !=, >= und > kann man Strings gemäß der lexikographischen Reihenfolge miteinander vergleichen. Bei Zeichenfolgenvergleichen werden die ASCII-Codes miteinander verglichen, so daß Ziffern kleiner als Buchstaben und Großbuchstaben kleiner als Kleinbuchstaben sind. Demzufolge bewirken die folgenden Codezeilen, daß im Meldungsfeld TRUE erscheint:

CString str1("123");
CString str2("ABC");
CString str3("abc");
CString str4("bcd");
if (str1 < str2 && str2 < str3 && str3 < str4) AfxMessageBox("TRUE");

Um den aktuellen String mit einem anderen zu vergleichen, kann man auch die Funktion Compare einsetzen. Die Funktion liefert null zurück, wenn zwei Strings gleich sind, einen negativen Wert, wenn der aktuelle String kleiner als der getestete String ist, oder einen positiven Wert, wenn der aktuelle String größer als der Teststring ist. Beispielsweise bewirken die folgenden Zeilen, daß ein Meldungsfeld mit dem Text TRUE erscheint:

CString strName("Peter");
if (strName.Compare("Piper")<0) AfxMessageBox("TRUE");

Dieser Vergleich beachtet auch die Groß-/Kleinschreibung der zu vergleichenden Strings. Vergleiche ohne Unterscheidung der Groß-/Kleinschreibung lassen sich mit der äquivalenten Funktion CompareNoCase durchführen.

Zeichenfolgenmanipulation

In der Sprache BASIC stehen drei nützliche Funktionen zur Manipulation von Strings zur Verfügung: Mid$, Left$ und Right$. Diese Funktionen sind nun auch als Mid, Left und Right in der Klasse CString verfügbar. Als Ergebnis liefern die Funktionen Kopien von Teilstrings zurück. Der Funktion Mid übergibt man eine Anfangsposition und optional die Anzahl der zu kopierenden Zeichen (fehlt das optionale Argument, liefert die Funktion alle Zeichen). Die Funktion gibt dann einen anderen CString mit dem angegebenen Teilstring zurück. Mit der Funktion Left extrahiert man eine Anzahl Zeichen vom linken Teil eines Strings. Dabei übergibt man der Funktion die Anzahl der gewünschten Zeichen. Die Funktion Right liefert die angegebene Anzahl der Zeichen von der rechten Seite des Strings (d.h. vom Ende des Strings an gerechnet) zurück. Dazu das folgende Beispiel:

CString strText("Ich habe heute drei Segelschiffe gesehen");
TRACE("%s\n", strText.Left(8));
TRACE("%s\n", strText.Mid(15, 17));
TRACE("%s\n", strText.Right(7));

Die Ausgabe der drei TRACE-Makros liefert:

Ich habe
drei Segelschiffe
gesehen

Das Wort heute zwischen habe und drei erscheint nicht in der Trace-Ausgabe, da dieser Teil des Strings überhaupt nicht extrahiert wird.

Mit den Funktionen MakeUpper und MakeLower ändert man alle Zeichen in einem String in Groß- bzw. Kleinbuchstaben, während die Funktion MakeReverse den String in umgekehrter Zeichenfolge liefert.

Leerzeichen, Zeichen für neue Zeile und Tabulatoren lassen sich vom linken Teil eines Strings mit der Funktion TrimLeft entfernen. Auf der rechten Seite realisiert die Funktion TrimRight diese Aufgabe.

Zeichenfolgen suchen

Mit den Funktionen Find, ReverseFind und FindOneOf kann man nach bestimmten Teilstrings oder Zeichen in einem String suchen.

Der Elementfunktion Find übergibt man ein einzelnes Zeichen oder einen String, um nach einem Vorkommen dieses Zeichens oder des Strings im Kontextstring zu suchen. Wenn das Zeichen oder der Teilstring gefunden wurde, liefert die Funktion die Position (gerechnet ab 0) zurück. Andernfalls lautet das Ergebnis -1. Es kennzeichnet, daß der Teilstring oder das Zeichen nicht vorhanden ist. Die folgenden Zeilen suchen nach dem Wort »du« und zeigen den Teilstring ab dieser Position an:

CString strTest("Fang an, wenn du denkst");
int nPosn = strTest.Find("du");
if (nPosn!=-1) TRACE(strTest.Mid(nPosn) + "?");

Die Funktion ReverseFind sucht nach einem bestimmten Zeichen (Teilstrings nicht möglich) vom Ende eines Strings. Wird das Zeichen gefunden, liefert die Funktion die Position bezüglich des Anfangs vom String (gerechnet ab Position 0) zurück, andernfalls -1.

Bei FindOneOf kann man eine Anzahl von Zeichen übergeben, die in einem String zu suchen sind. Zurückgegeben wird die Position des ersten Suchzeichens der Gruppe. Das Ergebnis -1 gibt an, daß kein Zeichen gefunden wurde. Die folgenden Beispielzeilen suchen nach den Zeichen d, e, k und w. Das w wird zuerst gefunden, so daß der String »wenn du denkst« in der Trace-Ausgabe erscheint:

CString strTest("Fang an, wenn du denkst");
int nPosn = strTest.FindOneOf("dekw");
if (nPosn!=-1) TRACE(strTest.Mid(nPosn));

Text zur Anzeige formatieren

Häufig setzt man CString-Objekte ein, um Text vor der Ausgabe zu formatieren. Dazu verwendet man die Funktion Format, der man im ersten Parameter eine Gruppe von Formatanweisungen als Prozentzeichencodes übergibt. Daran schließt sich die Übergabe von Wertparametern an. Die Anzahl der Wertparameter muß der Anzahl der angegebenen Formatcodes entsprechen. Einige dieser Formatflags und die jeweiligen Parametertypen sind in Tabelle F.16 aufgelistet. Mehrere Codes kann man zu einem Formatstring zusammenfassen, der dann im aufrufenden CString-Objekt gespeichert wird. Diesen Formatstring kann man auch als Stringressourcen-ID aus den Ressourcen der Anwendung übergeben.

Tabelle F.16: Formatcodes für die Funktion Format

Flag

Parametertyp

Beschreibung

%c

int

Zeigt ein einzelnes Textzeichen an.

%d

int

Zeigt eine Dezimalzahl mit Vorzeichen an.

%u

int

Zeigt eine Ganzzahl ohne Vorzeichen an.

%o

int

Zeigt eine Oktalzahl ohne Vorzeichen an.

%x

int

Zeigt eine ganze Hexadezimalzahl ohne Vorzeichen in Kleinbuchstaben an.

%X

int

Zeigt eine ganze Hexadezimalzahl ohne Vorzeichen in Großbuchstaben an.

%f

double

Zeigt eine Gleitkommazahl mit Vorzeichen an.

%s

string

Zeigt bei Übergabe eines Stringzeigers wie etwa char* einen Textstring an.

%%

keiner

Zeigt das Prozentzeichen % an.

Das folgende Beispiel kombiniert einige der gebräuchlichen Typen, um ein Meldungsfeld mit dem Text »Es kommen 1609.340000 Meter auf eine Meile.« anzuzeigen:

CString strFormatMe;
char *szM = "Meter";
double dMPerMile = 1609.34;
strFormatMe.Format("Es kommen %f %s auf %d %s",
dMPerMile,szM,1,"Meile.");
AfxMessageBox(strFormatMe);

Mit zusätzlichen Formatangaben kann man für jedes Ausgabefeld die Breite, Genauigkeit und Ausrichtung spezifizieren. Diese Attribute sind nach dem Prozentzeichen aber noch vor dem (in Tabelle F.16 angegebenen) Typzeichen in folgendem Format zu schreiben:

%[Flag][Breite][.Genauigkeit]Typ

Für den Flag-Wert kann man die folgenden Zeichen angeben:

- Richtet das Feld linksbündig aus (ansonsten rechtsbündig).

+ Zeigt vor einer Zahl ein Plus- oder Minuszeichen an (per Vorgabe erscheint nur das Minuszeichen bei negativen Zahlen).

0 Füllt die Feldbreite mit Nullen auf.

Mit Breite gibt man dann die Mindestzahl von Zeichen an, die das Feld anzeigen soll. Der Parameter .Genauigkeit bestimmt, wie viele Zeichen nach dem Dezimalpunkt stehen sollen.

Den Text im vorherigen Beispiel kann man zum Beispiel so formatieren, daß »Es kommen +1609.3 Meter auf 1 Meile.« in der Ausgabe erscheint:

strFormatMe.Format("Es kommen %+.1f %s auf %d %s",
dMPerMile,szM,1,"Meile");

Zeichenfolgenformatierung

Die Funktion Format der Klasse CString verwendet die gleichen Codes zur Festlegung der Formate wie die C-Funktionen printf und sprintf. C-Programmierer sind wahrscheinlich mit den Formatcodes der Funktion printf vertrauter und können die Codes ohne Umzulernen bei der Funktion Format der Klasse CString verwenden.



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