Vorheriges Kapitel: Serialisierung  Nächstes Kapitel: Einsatz von Timern       Inhaltsverzeichnis Inhaltsverzeichnis       Die Downloadseite Download       Kontakt Kontakt

 

Dokument und Ansicht

 

Die Dokumentklasse
Die Ansichtsklasse
Einige Funktionen für Dokument und Ansicht
Kommunikation zwischen Dokument und Ansicht
Erstellung eigener Klassen
Die Sammlungsklassen
Die nicht-vorlagenbasierten Sammlungsklassen
Die vorlagenbasierten Sammlungsklassen
Das Beispielprogramm Studlist
MDI oder SDI

 

 

In diesem Kapitel wird hauptsächlich auf SDI Dokumente eingegangen. Wenn Sie mit Hilfe von AppWizard eine SDI Anwendung erstellen, erzeugt AppWizard automatisch u.a. zwei Dateien mit dem Zusatz Doc und View. Dies hat einen bestimmten Grund. Die Trennung von Dokument und Ansicht spielt In einem Visual C++ Projekt eine wichtige Rolle. Gelagert werden die Daten im Dokument und gezeigt werden sie in der Ansicht. Es wird hier versucht anhand von drei Beispielen die Sache etwas zu vereinfachen. Doch zuerst etwas Theorie:

 

Die Dokumentklasse Ihres Projektes ist von der MFC Klasse CDocument abgeleitet. CDocument stellt die grundlegende Funktionalität für eine benutzerdefinierte Dokumentklasse zur Verfügung. Eine der nützlichsten Funktionen, die Sie in einem Dokument vorfinden ist die MFC Funktion Serialize(CArchive& ar). Mit Hilfe dieser Funktion können Sie die Daten in einem Dokument relativ einfach serialisieren. Ein Dokument stellt den Teil der Daten dar, der typischerweise mit dem Befehl Datei Öffnen, geöffnet und dem Befehl Datei Speichern, gespeichert wird. CDocument unterstützt die Sandart Operationen wie Erstellen, Laden und Speichern eines Dokuments. Das Anwendungsgerüst reagiert automatisch auf die Befehle Datei Öffnen und Datei Speichern und ruft die bekannten Windows Dialoge für diesen Zweck auf.

 

Die Ansichtsklasse Ihrer Anwendung ist von der MFC Klasse CView abgeleitet. Diese Klasse bietet die grundlegende Funktionalität für eine benutzerdefinierte Ansichtsklasse. Eine Ansicht ist einem bestimmten Dokument zugeordnet und dient als eine Art Vermittler zwischen dem Dokument und Benutzer. Die Ansicht gibt ein Abbild des Dokuments auf dem Bildschirm oder Drucker wieder und interpretiert die Eingaben des Benutzers als Operationen aufs Dokument. Ein Dokument kann unter Umständen mehrere Ansichten haben, eine Ansicht ist jedoch nur einem einzigen Dokument zugeordnet. Bevor wir auf die Art der Kommunikation zwischen Dokument und Ansicht eingehen, ist es zweckmäßig über die folgenden MFC Funktionen Bescheid zu wissen:

 

Diese Funktion finden Sie in der Ansichtsklasse. GetDocument() liefert einen Zeiger auf ein Dokumentobjekt, das mit der Ansicht assoziiert ist. Diese Funktion ermöglicht Ihnen, auf Member-Funktionen und öffentlichen Variablen des Dokuments zuzugreifen. Falls Sie vorhaben, auch auf die nichtöffentlichen Variablen des Dokuments zuzugreifen, müssen Sie die Ansichtsklasse als friend von Dokumentklasse deklarieren.

Rufen Sie diese Funktion immer dann auf, wenn Sie das Dokument modifiziert haben. Mit dem Aufruf dieser Funktion stellen Sie sicher, daß das Anwendungsgerüst(Framework) den Benutzer auffordert, die Änderungen zu speichern, bevor er das Dokument schließen kann.

Diese Funktion wird vom AppWizard in die Dokumentklasse erstellt. OnNewDocument() wird vom Anwendungsgerüst als Reaktion auf den Befehl Neu des Menübefehls aufgerufen. Sie sollten die Daten des Dokuments hier initialisieren.

Mit Hilfe dieser Funktion, die sich in der Dokumentklasse befindet, können Sie die Daten serialisieren. Sie haben den Mechanismus der Serialisierung im letzten Kapitel kennengelernt. In der Funktion Serialize() können Sie mit Hilfe des Parameters ar direkt ins Archive schreiben oder davon lesen.

  

 

Kommunikation zwischen Dokument und Ansicht

 

Die Ansicht ist für die Anzeige und Modifikation von Daten verantwortlich, jedoch nicht für deren Speicherung. Damit überhaupt eine funktionierende Kommunikation zwischen den beiden Klassen zustande kommen kann, muß das Dokument geeignete Funktionen und Wege der Ansicht zur Verfügung stellen, womit die Ansicht auf die Daten des Dokuments zugreifen kann. Wenn Sie in der Headerdatei der Ansichtsklasse nachschauen, werden Sie eine Funktion namens GetDocument() finden, die der Dokumentklasse angehört. Genau mit dieser Funktion greifen Sie auf die Daten des Dokuments zu. Die Beziehung zwischen der Dokumentklasse, Ansichtsklasse und Hauptrahmenfensterklasse wird in der sogenannten Dokumentvorlage in der Funktion InitInstance Ihrer Anwendungsklasse festgehalten. Die Anwendungsklasse befindet sich in der Datei, die den Namen Ihres Projektes trägt. Es soll darauf hingewiesen werden, daß AppWizard die Initialisierungen für Ihre Anwendung in dieser Funktion unternimmt. Sie werden normalerweise mit der Anwendungsklasse nichts zu tun haben. Schauen wir uns mal die Sache praktisch an: Das Beispielprogramm docview1 schreibt einen einzigen Satz ins Dokument und speichert ihn in einer Datei, die Sie angeben müssen. Falls Sie die Anwendung schließen, bevor Sie den Satz gespeichert haben, werden Sie gefragt, ob Sie die Änderungen speichern möchten. Wenn Sie die gespeicherte Datei öffnen, erscheint der Satz, den Sie gespeichert hatten, in der OnDraw() Funktion auf dem Bildschirm. Sie werden erstaunt sein, mit wie wenig Aufwand das alles realisiert werden kann. Legen Sie ein neues SDI Projekt namens docview1 an. Entwerfen Sie im Resource-Editor eine Dialogseite für die Eingabe des Satzes. Auf der Dialogseite befindet sich neben den OK und Cancel Schaltflächen eine Editbox, mit der die Member-Variable m_satz verbunden ist. Die Dialogseite für die Eingabe des Satzes wird mit der Betätigung der linken Maustaste auf den Bildschirm aufgerufen. Erstellen Sie Mit Hilfe von AppWizard die zugehörige Nachrichten-Funktion für die Nachricht WM_LBUTTONDOWN in der Ansicht. Machen Sie die Dialogklasse der Ansicht bekannt, indem Sie die Headerdatei der Dialogseite in der Ansicht mit der #include Anweisung eintragen. Um das Konzept vom Dokument und Ansicht zu realisieren, müssen Sie öffentliche Variablen vom gleichen Typ wie der zu lagernden Daten in der Headerdatei des Dokuments deklarieren. Für das Beispielprogramm ist der einzugebende Satz vom Typ CString. Deklarieren Sie in docview1Doc.h eine öffentliche Variable namens satz vom Typ CString. Die Ausgangsposition ist die Eingabe des Satzes. Schauen wir uns die Funktion OnLButtoDown() in der Ansicht an:

void CDocview1View::OnLButtonDown(UINT nFlags, CPoint point) 
{
 	CSatz dlg;
	int iresult=dlg.DoModal();
	if(iresult==IDOK)
	{
            CDocview1Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	pDoc->satz=dlg.m_satz;
	pDoc->SetModifiedFlag();
	Invalidate();
	}
	CView::OnLButtonDown(nFlags, point);
}

 

Wenn Sie in der Dialogseite einen Satz geschrieben und die OK Taste gedrückt haben, erhalten Sie mit pDoc einen Zeiger auf das Dokument, das mit der Ansicht assoziiert ist. Die Variable satz , die Sie bereits in der Dokumentklasse deklariert haben, ist vom gleichen Typ wie die Editbox-Variable m_salz. Mit der Anweisung pDoc->satz= dlg.m_satz; wird der Inhalt der Editbox bereits dem Dokument übergeben. Für die Speicherung von satz müssen Sie in der Funktion Serialize() gesorgt haben. Mit SetModifiedFlag() stellen Sie sicher, daß der Benutzer beim Verlassen der Anwendung zum Speichern der Änderungen aufgefordert wird. Der Satz, den Sie gespeichert haben, wird auf dem Bildschirm in der Funktion OnDraw() erscheinen. Mit Invalidate() leiten Sie den Zeichenvorgang ein. Schauen wir uns nun die Funktion Serialize im Dokument an:

void CDocview1Doc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// Hier speichern:
	ar<<satz;
	}
	else
	{
		// Hier laden:
	ar>>satz;
	}
}

 

Es ist erstaunlich, mit wie wenig Code Sie das Speichern und Laden bewerkstelligen können. Sie brauchen nur die Daten ins Archive zu schreiben, oder davon zu lesen, alles Andere macht das Anwendungsgerüst selbst für Sie. Kurz gesagt, mit ar<<satz speichern Sie den Satz und mit ar>>satz laden Sie ihn wieder. Alle weiteren Schritte, wie etwa das Aufrufen des Dialogs zum Öffnen oder Speichern der Dateien usw. erledigt das Anwendungsgerüst automatisch. Die Funktion OnDraw(), in der der Satz erscheint, sieht folgendermaßen aus:

void CDocview1View::OnDraw(CDC* pDC)
{
	CDocview1Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	pDC->TextOut(50,50,pDoc->satz);
}

 

Der dritte Parameter in der Funktion TextOut(), pDoc->satz bedeutet, daß TextOut() immer den Satz aus dem Dokument in den Bildschirm schreibt. Wenn Sie die Anwendung aufrufen, erscheint auf dem Bildschirm ein Satz " hier kann auch hier Ihr Satz stehen".


Abbildung
Dies bedeutet, daß das Dokument irgendwo mit diesem Satz initialisiert worden sein müßte. Der Platz für derartige Initialisierungen ist die Funktion OnNewDocument() im Dokument:

BOOL CDocview1Doc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;
	//Ab hier:
	satz="Hier kann auch Ihr Satz stehen." ;
	return TRUE;
}

Als letztes schauen wir uns die Funktion OnInitialUpdate() in der Ansicht an. Sie kennen diese Funktion bereits und wissen , daß Sie die Initialisierungen für die Ansicht dort durchführen sollten. Jedesmal Wenn Sie den Befehl Neu oder Öffnen vom Dateimenü aus betätigen, wird auch OnInitialUpdate() aufgerufen. Darin befindet sich, wie bei allen anderen Anwendungen eine Message Box mit Informationen über die Anwendung. Damit diese Message Box nicht bei jedem Öffnen einer neuen Datei wieder auf dem Bildschirm erscheint, wurde von einer BOOL Variable Gebrauch gemacht. Die Variable selbst ist im Konstruktor der Ansicht initialisiert worden.

void CDocview1View::OnInitialUpdate() 
{
	CView::OnInitialUpdate();
	
	if(Meldung)
                   {
MessageBox("Es handelt sich hier um ein einfaches Programm für"
	   " Serialisierung. Wenn Sie im Programm"
	   " auf die linke Maustaste drücken, erscheint das Dialog"
	   " zum Eingabe eines Satzes. Diesen Satz können Sie"
	   " speichern und nochmals laden.");
	
	Meldung=FALSE;
                  }
}

 

 

 

Erstellung eigener Klassen

 

Um einer bestimmten Aufgabe in der Programmierung gerecht werden zu können, muß man öfters auch eigene Klassen erstellen und diese dem Projekt zufügen. Falls Sie für eine Aufgabe eine eigene Klasse schreiben, müssen Sie auf ein paar Feinheiten achten. Grundsätzlich sollte Ihre neue Klasse in der Lage sein, Daten zu serialisieren. Daher sollten Sie Ihre eigene Klasse immer von der MFC Klasse CObject ableiten. Ein Großteil der MFC Klassen selbst, ist von CObject abgeleitet worden. Diese Klasse hat u.a. die Fähigkeit, Daten zu serialisieren. Die virtuelle Funktion Serialize(), die Sie bereits kennengelernt haben, ist ein Member dieser Klasse. Falls Sie Ihre eigene Klasse von CObject ableiten, vererbt auch Ihre Klasse die Eigenschaften von CObject. Sie sollten in Ihrer Klasse die virtuelle Funktion Serialize() für die Belange Ihrer eigenen Klasse überladen. Weil Ihre Eigene Klasse bestimmt nicht der MFC Bibliothek angehört, weiß natürlich die Anwendung nicht, wie sie die Daten Ihrer neuen Klassen ins Archive schreiben muß. Die neue Klasse muß also selbst wissen, wie sie die Daten ihres Typs ins Archive schreibt. In diesem Zusammenhang kann das Überladen von Operatoren eine wichtige Rolle spielen. Falls Sie mit Ihrer neuen Klasse Vergleichsoperationen durchführen, müssen Sie diese Operatoren überladen. Andernfalls hat der Compiler keine Ahnung, wie er mit dem neuen Typ, den Sie mit der Erstellung der neuen Klasse erzeugt haben, umzugehen. Bei der Erstellung einer eigenen neuen Klasse sollten Sie die folgenden Regeln beachten: Sie sollten in der Headerdatei die Klasse von CObject ableiten. Dies könnte folgendermaßen aussehen:

class IhreKlasse : public CObject {..........}

Innerhalb Ihrer eigenen Klasse müssen Sie als erstes das Makro DECLARE_SERIAL(IhreKlasse) schreiben. Achten Sie darauf, daß hinter dieser Anweisung kein Semikolon geschrieben wird. In der Implementationsdatei müssen Sie vor Ihrer eigenen Klasse ein zweites Makro namens IMPLEMENT_SERIAL(IhreKlasse ,CObject,1) schreiben. Hinter dieser Anweisung befindet sich auch kein Semikolon. Mit diesen beiden Makros bekommt Ihre neue Klasse die Fähigkeit, Daten zu serialisieren. Schauen wir uns mal das Beispielprogramm docview2 an. Dieses Beispielprogramm enthält eine eigene Klasse namens Student, die Name, Matrikelnummer und Alter eines Studenten speichert. Erstellen Sie ein neues SDI Projekt namens docview2. Ihre Aufgabe besteht zuerst darin, eine eigene Klasse namens Student dem Projekt hinzufügen. Wählen Sie den Menübefehl (Einfügen/Neue Klasse), Markieren Sie im Dialog für den Klassentyp Allgemeine Klasse ,tragen Sie für den Klassennamen Student ein. Die Basisklasse für die Klasse Student ist CObject und die Art der Vererbung ist public.


Abbildung
Auf diese Art erzeugt Visual C++ für Sie eine Header- und Implementationsdatei namens Student.h und Student.cpp. Schreiben Sie folgendes in die Headerdatei der Klasse Student:

#include"stdafx.h"
class Student : public CObject
{
	DECLARE_SERIAL(Student)
protected:
	
	int s_matrik;
	int s_alter;
	CString s_name;
	class Student* std;
public:
	class Student* getstudent(){
		return std;}
		
	int getMatrik() ;
	int getAlter();
	CString getName() ;
	void setName(CString);
	void setMatrik(int);
	void setAlter(int);
	virtual void Serialize(CArchive&);
};
 

Die Implementationsdatei der Klasse Student sieht folgendermaßen aus:

 
#include "stdafx.h"
#include "docview2.h"
#include "Student.h"
IMPLEMENT_SERIAL(Student,CObject,1)
CString Student::getName()
{
	return s_name;
}
int Student::getMatrik()
{
	return s_matrik;
}
int Student::getAlter()
{
	return s_alter;
}
void Student::setName(CString str)
{
	s_name=str;
}
void Student::setMatrik(int mat)
{
	s_matrik=mat;
}
void Student::setAlter(int alt)
{
	s_alter=alt;
}
void Student::Serialize(CArchive& ar)
{
	CObject::Serialize(ar);
	if(ar.IsStoring())
	{
		ar<<s_name;
		ar<<s_matrik;
		ar<<s_alter;
	}
	else
	{
		ar>>s_name;
		ar>>s_matrik;
		ar>>s_alter;
	}
}

 

Achten Sie auf die Position von DECLARE_SERIAL(Student) und IMPLEMENT_SERIAL(Student ,CObject ,1). Sie wissen zwar bereits, daß die Daten im Dokument gespeichert werden, aber die Klasse Student speichert die Daten in sich selbst. Sie müssen jedoch die Funktion Serialize() in der Dokumentklasse darauf hinweisen, daß die Daten in der Klasse Student gespeichert werden. Nun zurück zu dem Rest des Programms: Sie müssen zuerst mit Hilfe des Resource-Editors eine Dialogseite für die Eingabe der Daten für die Klasse Student entwerfen. Die Dialogseite muß drei Editboxen für Name, Matrikelnummer und Alter enthalten. Die Member-Variable für Name ist natürlich vom Typ CString und die beiden anderen sind vom Typ int. Wenn Sie Das Konzept von Dokument und Ansicht im vorigen Beispielprogramm verstanden haben, ist außer der Klasse Student nichts Neues dabei. Sie müssen in der Headerdatei des Dokuments eine öffentliche Variable st vom Typ Student deklarieren. Sie müssen vorher natürlich die Klasse Student mit der #include Direktive dem Dokument und Ansicht bekannt gemacht haben. Die Funktion OnLButtonDown() in der Ansicht, die das Dialog auf dem Bildschirm aufruft, sieht folgendermaßen aus:

void CDocview2View::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CDocview2Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	CStudDlg dlg;
	int iresult=dlg.DoModal();
	if(iresult==IDOK)
	{
	pDoc->st.setName(dlg.m_name);
	pDoc->st.setMatrik(dlg.m_matrik);
	pDoc->st.setAlter(dlg.m_alter);
	pDoc->SetModifiedFlag();
        Invalidate();
	}	
	CView::OnLButtonDown(nFlags, point);
}

 

Die Funktionen setName(), setMatrik() und setAlter gehören der Klasse Student an. Als Parameter werden ihnen die Member-Variablen der drei Editboxen m_name, m_matrik und m_alter übergeben. Die Variable st ist ja im Dokument deklariert worden. Mit Hilfe von pDoc landen die Daten sozusagen ins Dokument. Jetzt müssen wir die Serialisierung der Daten sicher stellen. Die Funktion, die für die Serialisierung zuständig ist, heißt bekanntlich Serialize() und befindet sich im Dokument. Da die Daten in der Klasse Student selbst serialisiert werden, müssen wir mit der Anweisung st.Serialize(ar) die eigentlich zuständige Funktion darauf aufmerksam machen. Die Funktion Serialize() im Dokument sieht dann wie folgt aus:

void CDocview2Doc::Serialize(CArchive& ar)
{
	st.Serialize(ar);
	if (ar.IsStoring())
	{
		
	}
	else
	{
		
	}
}

 

Die Daten aus dem Dokument werden in die Funktion OnDraw() auf den Bildschirm geschrieben. Dies wurde bereits im vorigen Beispiel erklärt. Wegen der Vollständigkeit sieht der Quellcode für die Funktion OnDraw() wie folgt aus:

void CDocview2View::OnDraw(CDC* pDC)
{
CDocview2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
char buff1[10], buff2[4];
sprintf(buff1,"%d",pDoc->st.getMatrik());
sprintf(buff2,"%d",pDoc->st.getAlter());
pDC->TextOut(50,50, "Name: ");
pDC->TextOut(50,100,"Matriekel Nr: ");
pDC->TextOut(50,150,"Alter: ");
pDC->TextOut(200,50, pDoc->st.getName());
pDC->TextOut(200,100, buff1);
pDC->TextOut(200,150, buff2);
 
}

Die Funktion TextOut() kann nur eine Zeichenkette auf dem Bildschirm zeigen. Wir müssen daher die beiden int Variablen für Matrikelnummer und Alter mit Hilfe von sprintf() in einem Puffer schreiben und dieses Puffer dann als Parameter an TextOut() übergeben.

 

 

Die Sammlungsklassen

 

Die Art, wie Sie die Daten Ihres Dokuments implementieren, hängt in erster Linie von der Natur Ihrer Anwendung ab. Als Hilfestellung, bietet Ihnen die MFC Bibliothek eine Gruppe von Sammlungsklassen(eng. Collection Classes) an. Die Sammlungsklassen werden in Deutsch auch als Auflistung bezeichnet. Falls Sie eine lange Liste von Daten speichern müssen, eignen sich die Sammlungsklassen am besten dafür. Die Klassen verfügen über viele Member-Funktionen, mit deren Hilfe Sie auf die Objekte Ihrer Liste zugreifen können. Um die Sammlungsklassen schneller zu verstehen, sollten Sie sich erstmals am besten mit den Member-Funktionen einer einzigen Klasse gut vertraut machen. Wenn Sie einmal den Einsatz dieser Funktionen verstanden haben, können Sie sie auch für die anderen Sammlungsklassen bequem einsetzen. Wenn Sie ein Objekt einer bestimmten Sammlungsklasse deklariert haben, stehen Ihnen die Member-Funktionen dieser Klasse über diese Variable zur Verfügung. Es gibt vorlagenbasierte(eng. Template Based) und nicht-vorlagenbasierte(eng. Nontemplate Based) Sammlungsklassen. Die nicht-vorlagenbasierten Sammlungsklassen stammen aus früheren Versionen der MFC Bibliothek. Diese werden jedoch weiterhin von den neueren Versionen unterstützt. Die Sammlungsklassen bestehen aus Listenklassen(Lists), Datenfeldklassen(Arrays) und Zuordnungsklassen(Maps, Dictionaries).

 

Eine Listenklasse besteht aus einer geordneten, nicht-indizierten Liste von Elementen, die als eine doppelt verkettete Liste implementiert worden ist. Die Listenklasse benutzt einen Indikator vom Typ POSITION, um eine bestimmte Position in der Listenklasse zu beschreiben. Jede Listenklasse hat einen Kopf und ein Ende. Die Listenklassen, wie auch die restlichen Sammlungsklassen, verfügen über Member-Funktionen, mit deren Hilfe Sie Elemente am Anfang, Ende oder Mitte der Liste einfügen oder entfernen können. Die notwendigen Einzelheiten über diese Member-Funktionen können Sie u.a. über die Online-Hilfe von Visual C++ erfahren.

 

Datenfelder sind Ihnen bestimmt aus der C-Programmierung bekannt. Die Datenfelder der MFC Bibliothek wachsen jedoch dynamisch und sind geordnete und integer-indizierte Felder von Objekten. Datenfelder benutzen Indizes um auf ihre Elemente zuzugreifen.

 

 

Nicht-vorlagenbasierte Sammlungsklassen

 

Die Sammlungsklassen wurden bereits mit der Version 1.0 der MFC Bibliothek eingeführt. Mittlerweile sind sie als vorlagenbasierte Klassen implementiert worden. Man sollte jedoch auch über die nicht-vorlagenbasierten Klassen Bescheid wissen, weil sie als Basisklasse für manche der neueren Sammlungsklassen dienen. Außerdem werden die nicht-vorlagenbasierten Klassen weiterhin von den neueren MFC Bibliotheken unterstützt. Es gibt eine Vielzahl von diesen Klassen. Es werden hier jeweils zwei Datenfeld- und zwei Listenklassen vorgestellt:

Diese Klasse unterstützt Felder(Arrays) von CObject Zeigern. CObArray benutzt das IMPLEMEN_SERIAL Makro, um die Serialisierung ihrer Elemente zu unterstützen. Wenn ein Feld von Zeigern auf CObject Objekten entweder mit den überladenen Eingabeoperatoren oder mit der Serialize() Funktion ins Archive geschrieben wird, werden auch die wirklichen CObject Objekte, die von diesen Zeigern referenziert werden, automatisch gespeichert. Wenn Sie ein Element von CObArray löschen, wird nur der Zeiger auf das Objekt gelöscht und nicht das wirkliche Objekt selbst.

Diese Klasse unterstützt Felder von 16 Bit Wörtern. Im Bezug auf die Serialisierung gilt das Gleiche wie bei CObArray, mit dem Unterschied, daß sich hier im Datenfeld keine Zeiger, sondern 16 Bit Werte befinden. Außerdem sind die Member-Funktionen von CWordArray den Member-Funktionen von CObArray sehr ähnlich.

Diese Klasse unterstützt eine geordnete Liste von CObject Zeigern, die entweder sequentiell oder durch den Zeigerwert angesprochen werden. CObList Listen verhalten sich wie doppelt verkettete Listen, wobei eine Variable vom Typ POSITION als Schlüssel für die Liste gilt. Im Bezug auf die Serialisierung gilt das Gleiche wie bei der CObArray Sammlungsklasse. Sie können natürlich Ihre eigenen Klassen von CObList ableiten. Die so erstellte Sammlungsklasse, die Zeiger auf von CObject abgeleiteten Objekten in sich lagert, kann mit eigenen Member-Funktionen erweitert werden. Beachten Sie, daß diese Klassen nicht unbedingt typsicher sind, da sie jede Art von CObject Zeigern akzeptieren. Stellvertretend für die nicht-vorlagenbasierte Sammlungsklassen, könnte ein praktisches Beispiel so aussehen:

Student* studi = new Student;
CObList StudList;
StudList.AddHead( studi ); 
Student* st = ( Student* )StudList.GetHead();

Die Klasse Student kennen Sie bereits. AddHead() ist eine Member-Funktion von CObList, die ein Objekt des Typs Student am Anfang der Liste einfügt. Mit GetHead(), die auch eine Member-Funktion von CObList ist, können Sie dieses Element des Typs Student wieder lesen.

 

Diese Sammlungsklasse unterstützt eine Liste von CString Objekten. Bei den Vergleichsoperationen werden die Werte, bzw. die Buchstaben selbst und nicht ihre Adressen berücksichtigt. In Sachen Serialisierung gilt das gleiche, wie bei der Sammlungsklasse CWordArray. Die Member-Funktionen dieser Klasse sind sehr der Member-Funktionen von CObList ähnlich.

 

 

Vorlagenbasierte Sammlungsklassen

 

Diese Art von Sammlungskassen sind mit der Version 4.0 der MFC Bibliothek eingeführt worden. Für die Benutzung dieser Art von Sammlungsklassen müssen Sie die Anweisung #include<afxtempl.h> in der Datei stdAfx.h Ihres Projektes einfügen. Es sind insgesamt sechs Sammlungsklassen mit dieser Technik implementiert worden. Es wird empfohlen, für Ihre neuen Programme von dieser Art von Sammlungsklassen Gebrauch zu machen. Auch hier haben Sie die Möglichkeit, entweder Zeiger auf die Objekte, oder aber die Objekte selbst zu speichern. Falls Sie sich in C++ mit Templates noch nicht beschäftigt haben, dann sollten Sie sich etwas darin einarbeiten. Es werden hier vier dieser Klassen, nämlich CArray, CList, CTypedPtrArray und CTypedPtrList vorgestellt. Wenn Sie sich die Deklaration dieser vier Sammlungsklassen genauer anschauen, fallen dann ein paar Ähnlichkeiten, aber auch Unterschiede auf, die man sich bewußt machen sollte. Zuerst einmal haben alle dieser vier Klassen zwei Parameter. Bei den Sammlungsklassen CArray und CList, die wirkliche CObject Elemente lagern können, kennzeichnet der erste Parameter den Typ von Objekten, die im Datenfeld gespeichert werden. Der zweite Parameter zeigt den Argumenttyp, mit dem auf die gespeicherten Objekten zugegriffen wird. Die Klassen CTypedPtrArray und CTypedPtrList , die Zeiger auf die CObject Objekte speichern, haben als ersten Parameter ihre Basisklasse für die Speicherung von Zeigern und als zweiten Parameter den Typ der Objekte, die in der Basiskasse gespeichert werden. Im folgenden werden noch die vier Klassen einzeln vorgestellt:

Damit Sie die vorlagenbasierten Sammlungsklassen besser verstehen, wird zuerst die allgemeine Deklaration der Klasse CArray erläutert:

template< class TYPE, class ARG_TYPE > class CArray : public CObject

TYPE ist der von CObject abgeleitete Typ des Objektes, der in CArray gespeichert wird. ARG_TYPE ist der Typ, mit dem auf die Objekte zugegriffen wird. Ein praktisches Beispiel kann wie folgt aussehen:

CArray<Student, Student&> StudArray;
Student studi;
StudArray->Add( studi );

Im obigen Beispiel werden Objekte des Typs Student in CArray gespeichert. Der Zugriffstyp ist eine Referenz auf die Klasse Student selbst. Add() ist eine Member-Funktion der Klasse CArray, mit deren Hilfe ein neues Objekt in die Sammlungsklasse gespeichert wird.

 

Ein praktisches Beispiel für CList könnte wie folgt aussehen:

CList<int, int> intlist;
intlist.AddTail( 100 );
intlist.RemoveAll( );

Mit AddTeil() wird die Zahl 100 am Ende der Liste eingefügt. Mit RemoveAll() können Sie alle Elemente der Liste löschen.

 

Für ein besseres Verständnis wird erst einmal die allgemeine Form dieser Sammlungsklasse erläutert:

template< class BASE_CLASS, class TYPE > class CTypedPtrArray : public BASE_CLASS

Der Erste Parameter BASE_CLASS ist die Basiskasse für das Feld von Zeigern. Dies muß eine Datenfeldklasse sein. Da Sie diese Klasse bestimmt zum Serialisieren benutzen, kommt als Basisklasse für CTypedPtrArray nur die Datenfeldklasse CObArray in Frage. TYPE kennzeichnet den Typ der Elemente, die in CObArray gespeichert werden. Ein praktisches Beispiel könnte wie folgt aussehen:

CTypedPtrArray<CObArray, Student*> studi;
for(int i=0; i<studi.GetSize(); i++)
{ Student* st= studi.GetAt(i);}

 

Mit der Member-Funktion GetSize() wird die Anzahl der Elemente ermittelt und mit der Member-Funktion GetAt() werden die Elemente gelesen. Ein gewichtiges Argument für die Benutzung dieser Klasse, oder der Klasse CTypedPtrList ist darin begründet, daß diese beiden Klassen typsicher sind.

 

Da Sie diese Klasse bestimmt zum Speichern der Elemente benutzen werden, kommt als Basisklasse für Zeiger auf die zu speichernden Objekte nur die Klasse CObList in Frage. Der zweite Parameter zeigt wie die vorherige CTypedPtrArray Klasse den Typ der Objekte, die in CObList gelagert werden. Ein praktisches Beispiel könnte wie folgt aussehen:

CTypedPtrList<CObList, Student*> studi;
POSITION pos = StudList.GetHeadPosition();
while( pos != NULL )
{
Student* st = studi.GetNext( pos );
}

Wie Sie aus dem Quellcode lesen können, ermittelt die Member-Funktion GetHeadPosition() die Position des ersten Elements und GetNext() gibt das nächste Element bis zum Ende der Liste aus.

 

 

Das Beispielprogramm Studlist

 

Nachdem Sie sich Mit Serialisierung, dem Konzept der Trennung vom Dokument und Ansicht, Erstellung eigener Klassen und MFC Sammlungsklassen beschäftigt haben, ist es an der Zeit, ein etwas komplexeres Programm zu entwickeln. In dem Beispielprogramm Studlist speichern Sie eine Liste von Student Elementen mit Hilfe der vorlagenbasierten MFC Sammlungsklasse CArray. Außerdem werden Sie Symbolschaltflächen entwerfen, mit deren Hilfe Sie zwischen den einzelnen Datensätzen navigieren können.


Abbildung
Erstellen Sie ein SDI Projekt namens Studlist, wobei Sie die Ansicht von der MFC Klasse CFormView ableiten sollten. Öffnen Sie den Resource-Editor und positionieren Sie drei Editboxen für Name, Matrikelnummer und Alter des Studenten. Die Member-Variablen für die drei Editboxen heißen m_name, m_matrik und m_alter. Man sollte ja das Programm sowohl vom Menü aus, als auch mit den Symbolschaltflächen steuern können. Entwerfen Sie ein Menüpunkt Student mit den Unterpunkten Eintragen, Löschen, Erster, Nächster, Vorheriger und Letzter. Geben Sie den Unterpunkten entsprechende IDs. Als nächstes müssen Sie sechs Symbolschaltflächen für die sechs Menübefehle des Menüs Student entwerfen. Sie wissen bereits, daß Sie diesen Symbolschaltflächen die gleiche ID geben müssen, wie die entsprechenden Menübefehle. Wenn Sie möchten, können Sie die Symbolschaltflächen auch noch mit QuickInfo(Tooltips) und Statuszeile ausstatten. Nun ist der visuelle Entwurf Ihrer Anwendung fertig. Als nächstes fügen Sie die eigene Klasse Student, die Sie im letzten Beispielprogramm geschrieben haben, in diesem Projekt ein. Die Objekte dieser Klasse werden wir in die vorlagenbasierte MFC Sammlungsklasse CArray lagern. Es ist praktisch, gleich bei der Deklaration einen Eigenen Typ daraus zu erzeugen. Schreiben am Ende der Headerdatei der eigenen Klasse Student, nämlich Student.h die folgende Anweisung:

typedef CArray<Student*,Student*> StudentList;

StudenList ist somit der neue Typ Ihrer Daten, den Sie direkt für die Trennung vom Dokument und Ansicht benutzen können. Damit die Anwendung die vorlagenbasierte MFC Sammlungsklasse erkennen kann, müssen Sie in die Datei stdAfx.h die Anweisung #include<afxtempl.h> schreiben. Nun muß eine Menge Routinearbeit erledigt werden. Sie müssen für die sechs Menübefehle des Menüs Student sechs Nachrichten-Funktionen mit Hilfe von ClassWizard schreiben. Außerdem brauchen Sie für diese Nachrichten-Funktionen mit Ausnahme des Befehls Eintragen fünf Befehlsaktualisierungen. Diese Funktionen müssen Sie ebenfalls mit Hilfe von ClassWizard erstellen.

Bitte Schreiben Sie folgende Deklarationen in die Headerdatei der Ansicht, nämlich StudlistView.h:

public:
Student* st; 
int c;
BOOL info;
BOOL loesch;
BOOL naechst;
void leseStudent();
void zeigeStudent(Student*);
 

die Variable st brauchen Sie für den Zugriff auf die Member-Funktionen vom Student. Die integer Variable c ist als eine Art Laufvariable gedacht, mit deren Hilfe Sie zwischen den Datensätzen navigieren können. Die BOOL Variablen loesch und naechst sind für das Funktionieren der Befehlsaktualisierungen nötig. Die Funktion leseStudent() Liest die Eingaben aus der Tastatur und transportiert sie ins Dokument. Die Funktion zeigeStudent(Student*) gibt die Daten eines Student-Objektes wieder auf den Bildschirm aus. Diese Funktion holt die Daten allerdings nicht selbst aus dem Dokument. Bevor wir uns die Implementierung der beiden Funktionen leseStudent() und zeigeStudent(Student*) anschauen, wenden wir uns zuerst an das Dokument:

Bitte schreiben Sie folgendes in die Headerdatei des Dokuments, nämlich StudlistDoc.h:

public:
int anzahl;
StudentList* starray;
void insertStudent(Student*);
StudentList* getList()
{
	return starray;
}

In die Variable anzahl werden wir den Index des Datenfeldes speichern. Der Zeiger starray zeigt auf den Typ der Datensätze, nämlich StudenList. Die Funktion insertStudent(Student*) fügt einen Datensatz in die Auflistung StudenList ein. Mit getList() werden Sie die Datensätze in der Auflistung ansprechen. Die Implementation der Funktion insertStudent(Studend*) sieht im Dokument folgendermaßen aus:

void CStudlistDoc::insertStudent(Student* s)
{
starray->Add(s);
SetModifiedFlag();
}

Die Serialisierung der Datensätze findet in der Funktion Serialize() des Dokumentes statt. Wir wissen von den Sammlungsklassen, daß es genügt, nur die Auflistung selbst zu serialisieren. Die Objekte, die in der Auflistung gelagert sind, werden dann automatisch serialisiert, vorausgesetzt diese Objekte wissen, wie sie sich ins Archive schreiben müssen. Die Funktion Serialize() sieht im Dokument wie folgt aus:

void CStudlistDoc::Serialize(CArchive& ar)
{
	anzahl=0;
	
	if (ar.IsStoring())
	{
	anzahl=starray->GetSize();
	
	//Anzahl der Datensätze.
	ar<<anzahl;
	
	
	for(int i=0;i<starray->GetSize();i++)
	{
		ar<<starray->GetAt(i);
	}
	
	}
	else
	{
		
	Student* s;
	ar>>anzahl;
	 
	for(int i=0;i<anzahl;i++)
	{
		ar>>s;
		starray->Add(s);
	}
	
	
	}
}

 

Das war bereits alles, was wir im Dokument implementieren mußten. Nun schauen wir uns die Implementierung der beiden Funktionen leseStudent() und zeigeStudent(Student*) in der Ansicht an:

void CStudlistView::leseStudent()
{
CStudlistDoc* pDoc=GetDocument();
if(st=!NULL){
	
	UpdateData(TRUE);
		
	st=new Student;
	st->setName(m_name);
        st->setMatrik(m_matrik);
        st->setAlter(m_alter);
	
	pDoc->insertStudent(st);
	
	m_name="";
	m_matrik=0;
	m_alter=0;
	UpdateData(FALSE);
	(((CEdit*)GetDlgItem(IDC_NAME)->SetFocus()));
}
}

 

Der Zeiger st wurde vorher im Konstruktor der Ansicht auf einen stabilen Wert gesetzt. Wie Sie aus dem Quellcode lesen können, liest die Funktion leseStudent() zuerst die Eingaben aus der Tastatur und die Funktion insertStudent(Student*) fügt den Datensatz in die Auflistung ein. Jetzt schauen wir uns die Funktion zeigeStudent(Student*) an:

 void CStudlistView::zeigeStudent(Student* aktStudent)
{
	
m_name=aktStudent->getName();
m_matrik=aktStudent->getMatrik();
m_alter=aktStudent->getAlter();
UpdateData(FALSE);	
	
}

Im folgenden sehen Sie die Nachrichten-Funktionen für die Menübefehle und Symbolschaltflächen und ihre Akualisierungsroutinen :

1. Der Befehl Eintragen:

void CStudlistView::OnEinfueg() 
{
leseStudent();
loesch=TRUE;
}

 

2. Der Befehl Erster:

void CStudlistView::OnErst() 
{
CStudlistDoc* pDoc=GetDocument();
Student* s=pDoc->getList()->GetAt(0);
zeigeStudent(s);
c=0;
naechst=TRUE;
}

 

void CStudlistView::OnUpdateErst(CCmdUI* pCmdUI) { CStudlistDoc* pDoc=GetDocument(); int i=pDoc->getList()->GetSize(); if(i<=0){ pCmdUI->Enable(FALSE); } }

 

3. Der Befehl Nächster:

void CStudlistView::OnNaechst() 
{
CStudlistDoc* pDoc=GetDocument();
int i=pDoc->getList()->GetSize();
if(c<i-1 && i>0){
Student* s=pDoc->getList()->GetAt(c+1);
zeigeStudent(s);
c=c+1;
	
}
}

 

void CStudlistView::OnUpdateNaechst(CCmdUI* pCmdUI) { CStudlistDoc* pDoc=GetDocument(); int i=pDoc->getList()->GetSize(); if(i-1<=c | !naechst ){ pCmdUI->Enable(FALSE); } }

4. Der Befehl letzter:

void CStudlistView::OnLetzt() 
{
CStudlistDoc* pDoc=GetDocument();
int i= pDoc->getList()->GetSize();
if(i>=0){
Student* s=pDoc->getList()->GetAt(i-1);
zeigeStudent(s);
c=i-1;
		
	
}
}

 

void CStudlistView::OnUpdateLetzt(CCmdUI* pCmdUI) { CStudlistDoc* pDoc=GetDocument(); int i=pDoc->getList()->GetSize(); if(i<=0){ pCmdUI->Enable(FALSE); } }

 

5. Der Befehl Vorheriger:

void CStudlistView::OnVorherig() 
{
	
CStudlistDoc* pDoc=GetDocument();
int i=pDoc->getList()->GetSize();
if(c<i & c>0){
Student* s=pDoc->getList()->GetAt(c-1);
zeigeStudent(s);
c=c-1;
}	
}

 

void CStudlistView::OnUpdateVorherig(CCmdUI* pCmdUI) { CStudlistDoc* pDoc=GetDocument(); int i=pDoc->getList()->GetSize(); if(i<=c |c<=0) { pCmdUI->Enable(FALSE); } }

 

6. Der Befehl Löschen:

void CStudlistView::OnLoesch() 
{
	
CStudlistDoc* pDoc=GetDocument();
pDoc->getList()->RemoveAt(c);
c=c-1;
m_name="";
m_matrik=0;
m_alter=0;
UpdateData(FALSE);	
	
}

 

void CStudlistView::OnUpdateLoesch(CCmdUI* pCmdUI) { if( c<0 | !loesch){ pCmdUI->Enable(FALSE); } }

 

 

 

MDI oder SDI

 

Alle Anwendungen dieses Lernprogramms sind als SDI Anwendungen geschrieben worden. Falls Sie sich für MDI Anwendungen interessieren und am besten ein wenig hinter den Kulissen schauen möchten, müssen Sie sich mit der einschlägigen Literatur in diesem Bereich beschäftigen. Es wird hier nur kurz auf dieses Thema eingegangen:

Eine SDI Anwendung hat nur eine Rahmenfensterklasse und ein Rahmenfensterobjekt. Diese Rahmenfensterklasse heißt CMainFrame und ist von der MFC Klasse CFrameWnd abgeleitet. Eine MDI Anwendung hat hingegen zwei Rahmenfensterklassen, nämlich CMainFrame und CChildFrame. Ein SDI Dokument kann nur einen Dokumententyp enthalten, Ein MDI Dokument kann jedoch verschiedene Dokumenttypen unterstützen und es kann mehr als ein Dokumententyp vorhanden sein.

Sie wissen bereits, daß die Beziehung zwischen der Dokumentklasse, Ansichtsklasse und der Hauptrahmenklasse in der Dokumentvorlage in InitInstance festgehalten wird. Der Aufruf der Funktion AddDocTemplate() hat zur Folge, daß die MDI Anwendung mehre untergeordnete MDI Fenster erhalten kann. Jedes dieser Fenster kann mit einem Dokumentobjekt und Ansichtsobjekt verbunden sein, muß aber nicht. Für den Praktiker ist es von Bedeutung zu wissen, daß eine MDI Anwendung zwei getrennte Menüresourcen mit den Bezeichnern IDR_MAINFRAME und IDR_.....TYPE hat. Für die Gestaltung von Menüs oder Symbolschaltflächen benutzen Sie die Resourcen mit dem Zusatz TYPE.

 

 

      Vorheriges Kapitel: Serialisierung  Nächstes Kapitel: Einsatz von Timern       Inhaltsverzeichnis Inhaltsverzeichnis       Die Downloadseite Download       Kontakt Kontakt