[ 1 ] [ 2

Session D-EINS

Visual FoxPro für OOP-Einsteiger

Sebastian Flucke
ASCI Consulting GmbH


Braucht man objektorientierte Programmierung?

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 der Sektionen OOP-Objektorientierung, SOFT-Softwareentwicklung und CLASS-Klassenbibliotheken zu entnehmen.

Es bleibt alles ganz anders - die ersten Schritte von FPW 2.6 zu Visual FoxPro

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.

Klasse - jetzt kann man etwas erben!

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.

Wie sehen denn Ihre Referenzen aus?

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.

Wie wird aus einer Klasse ein "klasse" Objekt?

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).

Die besondere Form von "Forms"

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".

Jetzt wird die Sache unsichtbar

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)!

Als weitere Klassen für nicht-visuelle Objekte kommen häufig "Label" und "Container" zum Einsatz. Die Label-Klasse hat den Vorteil einer Caption, der man zur Design-Zeit ansehen kann, welche Komponente innerhalb einer Maske hat man denn gerade vor sich (im Gegensatz zu den nichtssagenden bunten Bauklötzchen der "Custom"-Klasse, allerdings sollten solche Labels auf Visible=.F. gesetzt werden, damit sie zur Laufzeit brav unsichtbar bleiben!). Die "Container"-Klasse hat wiederum den Vorteil, daß man ihm schon zur Designzeit per Drag&Drop andere Objekte hinzufügen kann!

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 custom
 
qnRecno = 0
qcFilter = ""
: usw.
qnBufferMode = 0
 
PROCEDURE Init
This.qnRecno = RECNO()
This.qcFilter = FILTER()
: usw.
This.qnBufferMode = CURSORGETPROP(...)
RETURN .T.
ENDPROCEDURE
 
PROCEDURE Destroy
GOTO This.qnRecno
: usw.
CURSORSETPROP(...This.qnBufferMode...)
RETURN
ENDPROCEDURE

ENDDEFINE

Die verblüffend einfache Handhabung ist leicht zu erkennen:

      xoSaveEnv = CREATEOBJECT( 'pCstSaveEnv' )

      RELEASE xoSaveEnv

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!

[ 1 ] [ 2