Wenn man sich auf den Weg von FoxPro für Windows 2.6 zu Visual FoxPro 3.0 / 5.0 macht, so ist dies ein Weg von prozeduraler xBase-Programmierung zum Zauberwort OOP - objektorientierter Programmierung. Dieser Weg ist nicht leicht, er ist mit manchen Fallen und Tücken gepflastert, aber er lohnt sich! Der Einsatz von OOP birgt ein gewaltiges Effektivitätspotential in sich, welches zu vernachlässigen sich kein Software-Entwickler leisten kann.
TIP: Keine Angst vor OOP! Die Sache ist einfacher als man denkt! |
Im folgenden sollen wichtige Aspekte des Übergangs von der einen Welt in die andere besprochen werden, soweit sie die eigentliche Programmierung betreffen. Damit verbundene Themen wie z.B. die objektorientierte Analyse, der Überblick über alle wichtigen Design Patterns sowie weitere mit dieser Thematik verbundene Informationen sind den Sessions D-METH, D-MODL, E-PATT, E-STRU, E-IOOP, E-BOBJ und E-EOOP zu entnehmen.
Der Übergang von FPW 2.6 zu Visual FoxPro ist durch zwei Hauptaspekte gekennzeichnet:
In welcher Hinsicht muß man nun umlernen, wenn man sich Visual FoxPro zuwendet?
Theoretisch gar nicht - denn Visual FoxPro beinhaltet weiterhin den kompletten FPW-Sprach-Umfang! Allerdings existiert der Maskengenerator nicht mehr, der aus den SCX-Dateien den Quelltext mit den SAY-, GET und READ-Anweisungen generiert hat. Man müßte seine Masken also mit der Hand codieren oder sich den neuen VFP-Masken ("Forms" genannt) zuwenden.
Die Forms unter Visual FoxPro beinhalten allerdings schon eine gehörige Portion "Objektorientierung", mit der man sich dann wohl oder übel auseinandersetzen muß:
Hinter diesen Begriffen verbergen sich Dinge, die im Prinzip von FPW 2.6 her schon bekannt sind:
Unter Visual FoxPro werden Properties wie Variablen gehandhabt. Allerdings ist deren Existenz und Inhalt nicht abhängig von irgendwelchen PUBLIC-, PRIVATE- oder LOCAL-Deklarationen, sondern Properties leben solange und behalten ihren Inhalte solange wie das Objekt existiert, zu dem sie gehören.
In Visual FoxPro steht für jedes Control ein Vielzahl von Properties zur Verfügung, mit der sehr detailliert das Aussehen und die Funktionalität der Controls beinflußt werden kann.
Auch die Zahl der Events hat sich unter Visual FoxPro wesentlich erhöht, sodaß eine sehr differenzierte Reaktion auf die unterschiedlichsten Ereignisse möglich ist.
Je nach Art des Objekts existieren in der Summe bis zu über 100 Properties, Events und Methoden. Von dieser großen Zahl darf man sich nicht abschrecken lassen, da im Normalfall nur ein Bruchteil davon explizit benutzt und ausgestaltet werden muß (siehe in der Hilfe im Abschnitt "Language Reference \ Language Contents", dort sind alle existierenden Properties, Events und Methoden aufgeführt)!
Alles in allem also nichts Neues - bis auf die wesentlich größere Anzahl von Properties, Events und Methoden gegenüber FPW 2.6.
TIP: An dieser Stelle sei auf einen Vorschlag für Namenskonventionen verwiesen, der eine konfliktfreie und übersichtliche Arbeit auch bei größeren Projekten und bei der Team-Programmierung gewährleistet (siehe Begleitdiskette). |
Das war's dann schon für den ersten Schritt! Mehr Einarbeitung ist nicht notwendig, um mit Visual FoxPro in ähnlicher Form arbeiten zu können wie mit FPW 2.6. Und man hat sich schon "ganz nebenbei" einige Vorteile der neuen OOP-Welt erschlossen.
Das war's natürlich noch lange nicht! Ein zweiter wichtiger Aspekt der OOP neben der schon erwähnten Kapselung ist die "Vererbung". Mit der "Vererbung" wird ein OOP-Aspekt betrachtet, der in FPW 2.6 noch nicht zu finden war (lediglich im GENSCRNX gab es einen entsprechenden Ansatz).
Man stelle sich vor, eine Applikation soll vom WIN3.1-Look&Feel auf das WIN95-Look&Feel umgestellt werden. Das bedeutet unter anderem, die Button-Beschriftungen von "MS SANS SERIF", 8, "B" auf "ARIAL", 9, "N" umzustellen - in der gesamten Applikation, in allen Masken! - Eine Horror-Vision für jeden Programmier!
An dieser Stelle wünscht man sich als Entwickler die Möglichkeit, irgendwo eine Standardeinstellung zu verändern, die sich dann automatisch auf alle Buttons in allen Masken auswirkt.
Und genau diese Möglichkeit bietet uns die Vererbung! Man definiert sich einmalig einen CommandButton mit allen Eigenschaften, die benötigt werden. Und immer wenn man jetzt in einer Form einen CommandButton benötigt, greift man auf diese selbsterstellte Standard-Definition zurück (so wie man unter FPW quasi immer auf die fest vordefinierte FPW-Standard-Button-Definition zugegriffen hat). Und kommt dann die Umstellung vom WIN3.1-Look&Feel auf das WIN95-Look&Feel, ändert man lediglich seine Standard-Definition, und alles ist erledigt.
Eine solche Standard-Definition, ein solcher Bauplan wird in der OOP als "Klasse" bezeichnet. Eine Klasse kann zu zwei Verwendungszwecken benutzt werden:
Baupläne respektive Klassen können also neben ihrer direkten Verwendung zum Erstellen von Objekten auch als Vorlage für andere, detaillierter ausgeformte Klassen dienen. Zum Beispiel könnte man eine allgemeine Klasse für CommandButtons erstellen, von der eine Klasse "Buttons für normale Forms" (mit bestimmten Schrift-Eigenschaften) und eine weitere Klasse "Buttons für Toolbars" (mit der toolbarspezifischen Buttonform) abgeleitet werden.
Die allgemeinere CommandButton-Klasse dient dann ausschließlich als Vorlage für weitere Klassen und nie als Bauplan für konkrete Objekte. In diesem Fall spricht man von einer "abstrakten Klasse". Wird eine Klasse dagegen auch als Bauplan für konkrete Objekte verwendet, spricht man von einer "konkreten Klasse" (wobei eine konkrete Klasse zusätzlich auch als Vorlage für weitere davon abgeleitete Klassen dienen kann!).
TIP: Man sollte NIE direkt mit den Basisklassen arbeiten, die Visual FoxPro in der "Form Controls Toolbar" anbietet, sondern sich von allen Basisklassen eine Ableitung in einer VCX-Datei erstellen und ausschließlich diese Ableitungen oder davon weiter abgeleitete Klassen benutzen, da die Voreinstellungen der VFP-BaseClasses NICHT verändert werden können! |
Die in dem Beispiel beschriebene unterschiedliche Ausgestaltung von bestimmten Properties unserer CommandButton-Klassen kann analog auch auf den Programmquelltext in Events und Methoden übertragen werden. Ein allgemeiner Abbrechen-Button beinhaltet in seinem Click-Event den Quellcode zum Beenden einer Maske. Im einfachsten Fall sind das die Zeilen
PROCEDURE Click
ThisForm.Release
ENDPROCEDURE
Sollen nun auf Wunsch eines verzweifelten Anwenders alle Abbrechen-Button noch eine Sicherheitsabfrage durchführen, ist man gut beraten, wenn alle Abbrechen-Buttons von einer Klasse CommandButtons_fuer_Abbrechen abgeleitet sind. In diesem Fall braucht man nämlich nur den Quelltext im Click-Event dieser Klasse auf
PROCEDURE Click LOCAL xcAnswer WAIT WINDOW "Abbrechen (J/N)" TO xcAnswer IF xcAnswer $ "Jj" ThisForm.Release ENDIF ENDPROCEDURE
zu erweitern und schon ist die Sache in allen Masken erledigt.
TIP: Vererbung bezieht sich sowohl auf Properties als auch auf Events und Methoden! |
Wie werden nun Klassen erstellt? Dazu existieren zwei verschiedene Wege:
Beide Varianten haben diverse Vor- und Nachteile, wobei in der Summe wohl dem visuellen Weg der Vorzug zu geben ist.
TIP: Klassen können bei der Definition um selbstdefinierte Methoden und Properties erweitert werden! |
Das war's dann für den zweiten Schritt! Mit Hilfe von Vererbung kann man den Aufbau und insbesondere die Wartbarkeit von Applikationen wesentlich effektiver gestalten.
Das war's aber immer noch nicht! Als nächstes muß man sich mit der Bezugnahme von Objekten aufeinander noch etwas genauer auseinandersetzen. Zu diesem Zweck kann man die Objekte in zwei Kategorien einteilen:
elementare Objekte: | Container-Objekte: |
---|---|
_VFP | _SCREEN |
CheckBox | Column |
ComboBox | Command Group |
CommandButton | Container-Objekt |
Cursor | Control-Objekt |
EditBox | Custom-Objekt |
Image | DataEnvironment |
Label | Form/Toolbar |
Line | Formset |
ListBox | Grid |
OleBoundControl | Option Group |
OleControl | Page |
Relation | Pageframe |
Separator | |
Shape | |
Spinner | |
TextBox | |
Timer |
Container-Objekte sind Objekte, die andere Objekte (elementare Objekte oder weitere Container-Objekte) beinhalten können. Dabei ist zu beachten, daß bestimmte Container nur bestimmte andere Objekte beinhalten können (FormSets können z.B. nur Forms beinhalten).
Will man nun aus einem Objekt heraus auf ein Objekt referenzieren, muß man beachten, in welcher relativen Lage man sich zu dem gewünschten Objekt befindet.
Im einfachsten Fall will man auf ein Property, einen Event oder eine Methode oder auf ein Unterobjekt von sich selbst zugreifen (z.B. im Valid-Event auf das Value-Property). In diesem Fall muß man das spezielle Referenzierungs-Schlüsselwort "This" verwenden:
PROCEDURE Valid IF This.Value < 10 WAIT WINDOW "Zu klein!" RETURN .F. ENDIF ENDPROC
Will man auf ein Property, einen Event oder eine Methode oder auf ein Unterobjekt der Form zugreifen, in der man sich selbst befindet (egal innerhalb welchen Containers), so benutzt man das Schlüsselwort "ThisForm".
PROCEDURE Valid IF This.Value < 10 WAIT WINDOW ThisForm.Caption + ": Zu kleiner Wert!" RETURN .F. ENDIF ENDPROC
Benötigt man dagegen den Zugriff auf ein Property, einen Event oder eine Methode oder auf ein UnterObjekt des Containers zugreifen, in dem man sich selbst befindet. so benötigt man das Schlüsselwort "Parent":
PROCEDURE Valid IF This.Value < 10 AND This.Parent.TextBox2.Value > 10 WAIT WINDOW "Ungültige Werte-Kombination!" RETURN .F. ENDIF ENDPROC
Weitere Schlüsselworte in diesem Zusammenhang sind "ThisFormSet", "ActiveControl", "ActiveForm", "_screen" und "_vfp" (siehe VFP-Dokumentation bzw. Online-Hilfe).
Alle diese Schlüsselworte können übrigens beliebig miteinander kombiniert werden, solange das Ergebnis auf ein existierendes Objekt verweist.
Bisher wurden der Begriff der Klasse und die Erstellung von Klassen besprochen. Wie nun werden Klassen zum Leben erweckt - wie erstellt man aus dem Bauplan ein konkretes Objekt?
Zur Klärung dieser Frage sind zwei Situationen zu unterscheiden:
In der ersten Situation fügt man ein Objekt der gewünschten Klasse im entsprechenden Designer dem Eltern-Objekt einfach hinzu (über die modifizierte "Form Controls Toolbar" oder per Drag&Drop aus dem Projekt-Manager oder dem Class-Browser heraus, analog dem Hinzufügen eines normalen VFP-Objektes).
Sind die Klassen allerdings auf nichtvisuellem Weg per DEFINE CLASS definiert worden, muß man die ADD OBJECT - Klausel des DEFINE CLASS - Befehls benutzen.
Instanziiert werden solcherart hinzugefügte Objekte dann bei der Instanziierung des Eltern-Objekts. Zu beachten ist allerdings, daß innerhalb des Definierens einer Elternklasse den auf diesem Wege hinzugefügten Klassen keine zusätzlichen Properties und Methoden zugeordnet werden können (dies geht nur in der ursprünglichen Definition der jeweiligen Einzel-Klassen!).
In der zweiten Situation muß man die Methode AddObject des Containers verwenden, dem das gewünschte Objekt hinzugefügt werden soll. Soll ein Objekt allerdings außerhalb von bestehenden Container instanziiert werden, so ist die Funktion CREATEOBJECT() einzusetzen (Ausnahme siehe nächster Abschnitt).
Eine gewisse Sonder-Rolle in dieser ganzen Klassen- und Vererbungswelt spielen die Forms.
Forms können
gespeichert sein.
Für Forms sind folgende Besonderheiten zu beachten:
Soviel zum Thema "visuelle Objekte".
Aber selbst das war noch nicht alles. Die bisher betrachteten Aspekte der OOP waren die "Kapselung" und die "Vererbung".
Positive Effekte der Kapselung sind:
Die Vererbung bietet folgende Vorteile:
Außerdem haben die OOP-Objekte den Vorteil, daß man Quelltext hinterlegen kann, der zum Zeitpunkt der Instanziierung des Objekts bzw. zum Zeitpunkt der Zerstörung des Objektes abgearbeitet wird (der "Init"- bzw. Destroy-Event). Damit lassen sich sehr effektive "vollautomatische" Aufräum-Mechanismen programmieren.
Nun gibt es allerdings keinen Grund, warum die hier aufgeführten Vorteile nur bei den bisher betrachteten visuellen Objekten (Buttons, Forms usw.) zum Zuge kommen sollen. Ein noch viel größeres Effektivitätspotential liegt in den Möglichkeiten, interne Programm-Abläufe und Verarbeitungsstrukturen mit Hilfe von Objekten zu vereinfachen und effektiver zu gestalten.
Aus diesem Grund kommt den sogenannten nichtvisuellen Objekten eine enorme Bedeutung zu. Die eigentlich für solche Zwecke vorgesehene VFP-Basisklasse ist die Klasse "Custom". Sie hat von Hause aus wenige Properties, Events und Methoden und dient eigentlich nur als Rahmen für die Speicherung eigener Properties und Methoden.
Die Basisklasse "Custom" ist eine Klasse, die außerdem Container für beliebige andere Objekte sein kann (sogar für Forms und FormsSets!). Unverständlich ist in diesem Zusammenhang allerdings die Tatsache, daß der "Custom"-Klasse im Class-Designer auf visuellem Wege keine Objekte hinzugefügt werden können (was zur Laufzeit mit der AddObject-Methode sehr wohl möglich ist)!
Das folgende Code-Fragment zeigt ein Custom-Control, welches die Aufgabe erfüllt, vor einer Operation in einem SELECT-Bereich alle relevanten Zustände zu sichern und danach wiederherzustellen (in voller Ausgestaltung zu finden z.B. im CodeBook):
DEFINE CLASS pCstSaveEnv AS customqnRecno = 0 qcFilter = "" : usw. qnBufferMode = 0PROCEDURE Init This.qnRecno = RECNO() This.qcFilter = FILTER() : usw. This.qnBufferMode = CURSORGETPROP(...) RETURN .T. ENDPROCEDUREPROCEDURE Destroy GOTO This.qnRecno : usw. CURSORSETPROP(...This.qnBufferMode...) RETURN ENDPROCEDUREENDDEFINE
Die verblüffend einfache Handhabung ist leicht zu erkennen:
Eine ähnliche Strategie wird von einer unsichtbaren Klasse verwendet, die Namen für temporäre Dateien reserviert und in ihrem Destroy-Event diese Dateien wieder von der Platte löscht. Dadurch kann man das Platte-Aufräumen nie mehr vergessen, denn spätestens beim QUIT werden die letzten noch lebenden Objekte zerstört, sodaß man keine Möglichkeit mehr hat, das Aufräumen zu vergessen (diese Klasse befindet sich auf der Begleitdiskette).
Als ein wichtiger neuer Aspekt ist also die Vereinfachung von Programmstrukturen zutagegetreten. Allerdings hat diese Sache auch eine Kehrseite. Die eigentlichen Programmaktivitäten sind u. U. sehr weit von ihrer Anwendungsstelle entfernt gespeichert, und außerdem muß man in zunehmendem Maße mit solchen verdeckten Aktionen wie z.B. dem Destroy-Event rechnen, was das Verständnis bestimmter Programmstrukturen nicht gerade erleichtert.
TIP: Klassen-Definitionen müssen sauber dokumentiert werden, wenn man sich darin nach einigen Wochen noch zurechtfinden will! |
Die angeführten Beispiele sind sehr einfache Varianten von "unsichtbaren" Objekten. Sie zeigen aber deutlich eine Reihe von Möglichkeiten auf, die die Programm-Erstellung und Wartung wesentlich erleichtern können.
An dieser Stelle sei die Empfehlung gegeben, sich einfach mit solchen unsichtbaren Objekten zu beschäftigen, solche Objekte zu erstellen und damit zu experimentieren. Dadurch gewinnt man wesentlich besser als über theoretische Abhandlungen ein Gefühl für das enorme Potential, das in diesen Objekten steckt!
Als erster Aspekt bei der Vererbung wurde die effektive, an zentraler Stelle vorzunehmende Veränderung des "Aussehens von Objekten" behandelt. Nach der Diskussion über die unsichtbaren Objekte rückt allerdings die Einflußnahme auf das "Verhalten von Objekten" wesentlich mehr in den Vordergrund. Und nun wird die Geschichte erst richtig interessant.
Bei der Beeinflussung des Verhaltens von Objekten sind zwei Arten zu unterscheiden:
Die Effekte bei der Pflege und Wartung von objektorientierten Programmen sind weiter oben schon besprochen worden.
Im folgenden sollen Möglichkeiten der Verhaltensbeeinflussung von Objekten für bestimmte Einsatzfälle bzw. in bestimmten Programm-Situationen näher untersucht werden.
Das Verhalten von Objekten ist in seinen Methoden und Events hinterlegt und wird ggf. durch Properties beeinflußt.
Es gibt verschiedene Varianten, wie das Verhalten eines Objekts verändert werden muß:
TIP: Ein solcher Kommentar sollte genaue Auskunft über seinen Zweck geben, damit er nicht irgend einer Säuberungsaktion zum Opfer fällt und dadurch das Objektverhalten gestört wird. |
Für die Ergänzung des betrachteten Verhaltens durch vor- bzw. nachgelagerte Aktivitäten steht der "Scope Resolution Operator" und in Visual FoxPro 5.0 zusätzlich die DODEFAULT()-Funktion zur Verfügung. Das folgende Code-Beispiel illustriert die Einsatzmöglichkeiten von DODEFAULT():
PROCEDURE Click : irgendwelcher zusätzlicher Kram : DODEFAULT() && ruft den Click der übergeodneten Klasse auf : irgendwelcher weiterer zusätzlicher Kram : ENDPROCEDURE
TIP: DODEFAULT() darf man nicht verwechseln mit dem Schlüsselwort NODEFAULT, welches Eventauswertungs-Aktivitäten unterdrücken kann (siehe VFP-Dokumentation bzw. Online-Hilfe). |
Der Einsatz des "Scope Resolution Operators" ist nur ganz besonders kniffligen Fällen vorbehalten, denn bei seiner Verwendung können durch Unachtsamkeit schnell Rekursionen oder andere unerwartete Ergebnisse entstehen.
Was passiert nun aber, wenn für mehrere Methoden unterschiedliche Verhaltensweisen vorgesehen werden müssen?
In diesem Fall kann Vererbung nicht sonderlich helfen. Aus dieser Sackgasse gibt es zwei Auswege:
Der "prozedurale" Ausweg bedarf wohl keiner weiteren Erläuterung, dieses Verfahren ist wohl allen xBase-Programmierern geläufig.
Der wesentlich interessantere Ansatz ist der "objektorientierte" Ausweg, für dessen Diskussion allerdings einige Grundlagen gelegt werden müssen.
Der Zugriff auf Objekte kann über zwei verschiedene Wege erfolgen:
"Pointer" ist ein neuer Datentyp seit VFP 3.0, der einige Besonderheiten aufweist:
xoRef0 = CREATEOBJECT( "myClass" )
&& hiermit wird die Referenz der erstellten
&& Objekts der Variablen xoRef0 zugewiesen
xoRef1 = This && hiermit weist ein Objekt seine eigene
&& Objektreferenz der Variablen xoRef1 zu
xoRef2 = xoRef1 && hiermit wird der Variablen xoRef2 die
&& aktuelle Objektreferenz zugewiesen, die
&& gerade in xoRef1 gespeichert ist
Beim Zuweisen einer Objektreferenz wird KEINE(!) Kopie des Objektes angelegt, sondern lediglich eine weitere Referenz auf das entsprechende existierende Objekt! |
Neben den Pointern haben die meisten Container-Objekte Array-Properties, über die man die im Container enthaltenen Objekte referenzieren kann (siehe VFP-Hilfe zu den Properties "Buttons", "Columns", "Controls", "Forms", "Pages"). Außerdem kann man mit der AMEMBERS()-Funktion ein beliebiges Array mit den Objektreferenzen der Unterobjekte eines beliebigen Containers füllen. Für das Parsen von Containern sei außerdem auf das neue FOR EACH - Konstrukt verwiesen.
Zurück zum "objektorientierten" Ausweg aus der beschriebenen Sackgasse.
Das zu lösende Problem besteht also in der Ausstattung eines Objekts mit variierbaren Verhaltensweisen (je nach Einsatzfall des Objekts bzw. nach Programmsituation). Zu diesem Zweck macht man sich die hohe Flexibilität von Pointern zunutze.
Gegeben sei ein Objekt Y mit den Methoden M1, M2 und M3. Jede dieser Methoden wird nach Programmsituation in den vier verschiedenen Spielarten V1 bis V4 benötigt (in nicht vorhersehbarer Kombination).
Rein mathematisch existieren also 4**3 = 64 Varianten, die auf keinen Falls als Vererbungsvarianten in einer Klassenhierarchie vordefiniert sein können.
Anstelle des Aufbaus der klassisch-prozeduralen CASE-Statement-Variante kann man wie folgt vorgehen:
Auf diese Weise kann das Verhalten des Objekts Y durch einen bloßen Austausch der den Properties qoM1 bis qoM4 zugeordneten Objekte verändern.
Die hier beschriebene Verfahrensweise soll lediglich das Grundprinzip andeuten, nach dem eine solche Objekt-Komposition funktioniert. Es gibt diverse Spielarten von Objekt-Kompositionen - die sogenannten Design Patterns. Diese Patterns werden in den Sessions E-PATT und E-STRU systematisch erläutert werden. Außerdem beschäftigt sich die Session D-KOMP ausführlich mit Fragen der Kommunikation von Objekten untereinander.
Wenn man mit Objekt-Komposition arbeitet, werden viele Objekt-Referenzen in Variablen, Arrays, Properties usw. gespeichert. Dabei muß man um eine Besonderheit wissen, die im folgenden beschrieben wird. Dabei wird ein "Bild" verwendet, welches schon seit geraumer Zeit die Runde durch die FoxPro-Gemeinde macht, um den etwas verzwickten Tatbestand zu beschreiben
Soweit, so gut! Die Probleme beginnen dann, wenn irgendwelche Pointer auf das Objekt belegt werden.
Eine ärgerliche Geschichte. Noch schlimmer wird die Sache allerdings, wenn man das Bild auf die Objekte in der Form erweitert und diese als einzelne Arme des Kronleuchters betrachtet.
Dieses sehr problematische Verhalten passiert leider auch dann, wenn alle Pointer sich als Properties innerhalb der freizugebenden Form befinden und sowieso gelöscht werden würden.
Aus diesem Dilemma gibt es zwei Auswege:
Alle Pointer auf ein zu löschendes Objekt vorher zurücksetzen.
Dieses Verfahren scheitert an der Tatsache, daß bei konsequenter Nutzung
von Objekt-Kompositionen eine solche Menge an Pointern entsteht, daß an
ein Zurücksetzen schlichtweg nicht zu denken ist.
Jedes Objekt kann sich unabhängig von auf sich weisenden Pointern dadurch
"von der Decke" lösen, daß es aus einer Methode seiner
selbst heraus ein "RELEASE This" aufruft!
Ein darauf beruhender Parsing-Mechanismus, der jedweden Kronleuchter sicher
von der Decke schießt, befindet sich auf der Begleit-Diskette.
Letztendlich bleibt aber unklar, weshalb ein solches Verhalten in der Version 5.0 nicht abgestellt wurde.
Auf der Begleit-Diskette sind zu dem hier behandelten Thema diverse Materialien und Tools abgelegt. Beachten Sie die entsprechende Inhaltsangabe für weitere Informationen.
Bei weiterführendem Interesse kann zum Autor gern Kontakt aufgenommen werden (E-Mail-Adresse: 100121.405@compuserve.com)