ASCII
Dateien
Vielleicht haben Sie es schon bemerkt. Über die Serialisierung können Sie nur
binäre Dateien verarbeiten. Oftmals ist es jedoch notwendig Daten zu verarbeiten
die in Textform (ASCII-Dateien) vorliegen. Wie Sie solche ASCII-Daten innerhalb
des Doc/View Modells einlesen und wieder abspeichern erfahren Sie hier.
Fangen wir mit dem Einlesen von ASCII-Daten an. Der MFC-Rahmen ruft zum Einlesen
von Daten die CDocument-Methode OnOpenDocument(...) auf. In der
Standard-Implementierung ruft die Methode OnOpenDocument(...) zuerst die
Methode DeleteContents(...) auf um die bisherigen Dokumentdaten zu löschen.
Anschließend wird die Serialize(...) Methode aufgerufen um die neuen Daten
einzulesen. Durch Überschreiben der OnOpenDocument(...) Methode kann nun
das Verhalten beim Einlesen der Daten verändert werden. Als Parameter erhält die
Methode den kompletten Dateinamen der Eingabedatei, d.h. einschließlich Pfad, übergeben.
Wurden die Daten erfolgreich eingelesen, so muss die Methode TRUE zurückliefern
und im Fehlerfall FALSE. Wenn Sie diese Methode überladen um das Einlesen
der Daten selbst durchzuführen, so erstellen Sie zunächst über den Klassen-Assistenten
die entsprechende Methode (unter Virtuelle Funktion hinzufügen....). Anschließend
entfernen Sie den vom Assistenten eingefügten Code und fügen Ihren eigenen ein.
Sehen wir uns nun eine typische überladene OnOpenDocument(...) Methode
einmal an:
// OnOpenDocument()
// Wird aufgerufen wenn Daten fuer ein Dokument eingelesen werden.
// Der vom Wizard generierte Code wurde kompl. entfernt
BOOL CSerialAscDoc::OnOpenDocument(LPCTSTR
lpszPathName)
{
// Alte Dok-Daten loeschen
DeleteContents();
// Eingabedatei oeffnen
ifstream InFile(lpszPathName);
// Fehler abfangen
if (InFile.fail())
{
AfxGetMainWnd()->MessageBox("Fehler beim
Öffnen der Datei!");
return FALSE;
}
// neues Datenobjekt erstellen
m_pCData = new CData;
// Daten einlesen und Datei
danach schliessen
m_pCData->ReadData(InFile);
InFile.close();
// Modified-Flag loeschen da
Daten noch nicht geaendert wurden
SetModifiedFlag(FALSE);
return TRUE;
} |
Als erstes muss die überladene Methode die Methode DeleteContents(...)
aufrufen um die bisherigen Dokumentdaten zu löschen. Anschließend kann die Eingabedatei
geöffnet werden. Im obigen Beispiel wird für die Eingabe eine Stream-Datei (ASCII-Datei)
geöffnet. War das Öffnen der Datei erfolgreich können die Daten eingelesen werden.
Im Beispiel liegen die Dokumentdaten in einem CData-Objekt, weswegen zuerst
das entsprechende Objekt erstellt werden muss. Anschließend wird die Methode
ReadData(...) des Datenobjektes aufgerufen um die Daten einzulesen. Die
ReadData(...) Methode erhält als Parameter eine Referenz auf das Stream-Objekt
der Eingabedatei. Nachdem die Daten eingelesen wurden wird die Eingabedatei wieder
geschlossen. Zum Schluss wird noch das Modified-Flag des Dokuments zurückgesetzt
da die eingelesenen Daten noch nicht verändert wurden. Selbstverständlich sind auch
andere Verfahren möglich um die Daten letztendlich einzulesen; es soll hier ja nur
ums Prinzip gehen.
Kommen wir jetzt zum Speichern der Daten. Das MFC-Rahmenprogramm ruft zum Speichern
der Daten die CDocument-Methode OnSaveDocument(...) auf. Der Standard-Rahmen
ruft nur die Methode Serialize(...) auf um die Daten abzuspeichern. Durch
Überschreiben der Methode OnSaveDocument(...) kann das Format der zu speichernden
Daten selbst definiert werden. Als Parameter erhält die Methode wiederum den vollständigen
Namen der Datei, in der die Daten abzulegen sind. Wurden die Daten erfolgreich abgespeichert,
so muss die Methode ebenfalls den Wert TRUE zurückliefern und ansonsten
FALSE.
Auch diese Methode ist zunächst wieder durch den Klassen-Assistenten zu erstellen
und danach der vom Assistenten erstellte Code durch den eignen zu ersetzen. Nachfolgend
finden Sie die zum obigen Listing gehörende OnSaveDocument(...) Methode:
// OnSaveDocument()
// Wird aufgerufen wenn Daten fuer ein Dokument geschrieben werden.
// Der vom Wizard generierte Methoden-Code wurde kompl. entfernt
BOOL CSerialAscDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
// Ausgabedatei oeffnen
ofstream OutFile(lpszPathName);
// Fehler abfangen
if (OutFile.fail())
{
AfxGetMainWnd()->MessageBox("Fehler beim
Öffnen der Datei!");
return FALSE;
}
// Daten schreiben und Datei
danach schliessen
m_pCData->WriteData(OutFile);
OutFile.close();
// Modified-Flag loeschen da
Daten gesichert wurden
SetModifiedFlag(FALSE);
return TRUE;
} |
Und auch hier wird wieder zuerst die Ausgabedatei geöffnet. War dies erfolgreich,
so werden die Daten geschrieben und anschließend die Datei wieder geschlossen. Zum
Schluss wird erneut das Modified-Flag des Dokuments zurückgesetzt.
Damit wären wir eigentlich am Ende dieses Tipps, wenn nicht beim Einlesen der
Daten eine Stolperfalle lauern würde. Im Beispiel soll die Datenklasse (hier
CData) Daten vom Typ String (char-Feld), einen short-Wert und
ein CString-Objekt in die Ausgabedatei schreiben. Sehen wir uns zunächst
das Schreiben der Daten an.
// Daten schreiben
void CData::WriteData(ofstream & OutFile)
{
// Textlaenge und Text schreiben
OutFile << strlen(m_pszText)
<< " " << m_pszText << endl;
// short-Variable schreiben
OutFile << m_nVar << endl;
// CString muss auf char* konvertiert
werden da CString den
// Operator << nur fuer CArchive ueberlaedt und nicht fuer Dateistreams
// Stringlaenge und String schreiben
OutFile << m_CText.GetLength()
<< " " << (LPCTSTR)m_CText << endl;
} |
Die obige Methode WriteData(...) dürfte nicht Unbekanntes enthalten.
Das einzige was Sie beim Schreiben beachten müssen ist, dass zwischen dem Schreiben
der Textlänge und den ASCII-String ein Trennzeichen eingefügt werden muss damit
später diese Daten getrennt eingelesen werden können. Sehen wir uns die geschriebenen
Daten einmal an:

Damit diese Daten wieder einzulesen können, ist die ReadData(...) Methode
wie folgt zu implementieren:
// Daten lesen
void CData::ReadData(ifstream & InFile)
{
int nLength;
// Zuerst alten Speicherbereich
freigeben
delete [] m_pszText;
// Textlaenge einlesen und char-Feld
allokieren
InFile >> nLength;
m_pszText = new char[nLength+1];
// Blank ueberspringen und Text
einlesen
InFile.ignore(1);
InFile.get(m_pszText,nLength+1);
// int-Variable einlesen
InFile >> m_nVar;
// Stringlaenge in temp. char-Feld
einlesen
InFile >> nLength;
char* pszTemp = new char[nLength+1];
// Blank ueberspringen und Text
einlesen
InFile.ignore(1);
InFile.get(pszTemp,nLength+1);
// Text dem CString-Objekt zuweisen
m_CText = pszTemp;
// temp. Speicherbereich freigeben
delete [] pszTemp;
} |
Worauf es in dieser Methode ankommt sind die kursiv und rot hervorgehobenen Anweisungen
Infile.ignore(1). Sie dienen dazu, das Trennzeichen zwischen den Textlängen
und den ASCII-Strings zu überspringen. Ohne diese Anweisungen würde die Datei nicht
richtig eingelesen werden.
Ein Programm zu diesem Thema finden Sie unter 04DocView\SerialAsc.
|