Session D-KOMP

Komponentenbau in
Visual FoxPro

Manfred Rätzmann
Rätzmann GmbH


Was sind Komponenten?

Schrauben Sie Ihren Computer auf und schauen Sie sich sein Innenleben an. Jeder heute gebaute Computer besteht aus vorgefertigten Komponenten. Kein Hersteller kann es sich mehr leisten, vom Gehäuse bis zum Prozessor alles selbst und vor allem, jedesmal neu zu fertigen. Die Einzelteile Ihres Computers, wie Netzteil, CD-ROM Laufwerk oder Motherboard sind selbst wieder aus Komponenten zusammengesetzt, aus elektronischen Bauteilen, die im Laden zu kaufen sind und aus spezialisierteren Teilen, in denen das Know-how des jeweiligen Herstellers steckt. Dieses Bild der Hardware ist in der Softwareentwicklung eine schwer zu realisierende Idealvorstellung. Warum sollte es aber nicht möglich sein, Software zumindest ähnlich aufzubauen? Einige fix und fertig zusammengeschraubte Komponenten kann man in Form von OCXen käuflich erwerben, andere Komponenten werden von Entwicklerkollegen als Public Domain zur Verfügung gestellt, anderes, besonders die Kleinteile, muß man einmal selbst entwickeln. Was bei dieser Entwicklung von eigenen Komponenten zu beachten ist, und wie Komponenten mit Microsoft Visual FoxPro (VFP) realisiert werden können, ist der Inhalt dieser Session.

Komponenten-Assembling oder Framework

Erich Gamma stellt in seinem Buch „Design Patterns“ Vererbung und Objekt-Komposition - also das Zusammensetzen neuer Objekte aus bestehenden anderen Objekten - gegenüber und gelangt zu dem Prinzip: „Favor object composition over class inheritance“ (Bevorzuge Objekt-Komposition gegenüber Vererbung). Dieses Prinzip gilt auch für die Erstellung von Applikationen und ebenso für die Struktur der dafür benutzten Werkzeuge. Klassische Frameworks sind dagegen oftmals überwiegend auf dem Vererbungsprinzip aufgebaut. Dabei entstehen häufig starke Kopplungen zwischen einzelnen Klassen. Es beziehen sich oft schon die Basisklassen oder erste Ableitungen aufeinander, weil in Methoden der Basisklassen auf Eigenschaften anderer Klassen zugegriffen wird. Die Eigenschaft XYZ einer Klasse wird von einer zweiten Klasse als vorhanden vorausgesetzt, weil beide Klassen zum selben Framework gehören. Das führt dann im Extremfall dazu, daß ein simpler „Schließen“-Knopf nur mit den frameworkeigenen Bildschirm-Formularen funktioniert. Da alle diese Beziehungen untereinander ständig berücksichtigt werden müssen, wird der Aufbau eines in sich stimmigen Frameworks schnell eine äußerst komplexe Angelegenheit. Das Hauptziel des objektorientierten Designs - Komplexität zu kapseln und dadurch handhabbar zu machen - wird durch die dem Framework eigene Komplexität konterkariert.

Komponenten-Assembling hingegen bedeutet, voneinander unabhängige Komponenten zu einem Ganzen zu verbinden. Das stellt uns vor zwei Aufgaben:

Komponenten werden als Klassen realisiert

Der Begriff Komponente hebt den inhaltlichen Aspekt eines Objekts, die Sicht auf die zu lösende Aufgabe, hervor. Natürlich sind die Komponenten eines Programms zur Laufzeit Objekte wie andere Objekte auch, deren Baupläne in Klassendefinitionen hinterlegt sind. Aber beim Komponentengedanken kommt es nicht so sehr auf die mehr technische Differenzierung zwischen Klasse und Objekt an. Komponenten müssen vor allem:

Die Vererbungshierarchie von Komponenten ist im allgemeinen flach. Sie enthält eventuell zwei, drei Varianten der Komponente, die auf einer gemeinsamen Elternklasse basieren.

Rahmenbedingungen

Das Framework hier bildet Visual FoxPro selbst. Ein solcher kleinster gemeinsamer Nenner ist vor allem wichtig, wenn man daran denkt, Komponenten anderer Entwickler einzusetzen oder eigene Komponenten an andere weiterzugeben. Wenn Sie Komponenten erschaffen wollen, die in allen VFP-Applikationen einsetzbar sind, können Sie sich nur auf das verlassen, was Visual FoxPro selbst zur Verfügung stellt. Jede Funktionalität, die Ihre Komponente zum reibungslosen Einsatz braucht, muß in der Komponente selbst enthalten sein. Sie dürfen eine solche Funktionalität auch nicht in einer Ihrer Basisklassen oder in einer Procedure-Datei ablegen, da Ihre Komponente sonst nur mit genau dieser Basisklasse oder dieser Procedure-Datei zusammen funktionieren wird. Entwicklen und testen Sie Ihre Komponente am besten als Ableitung einer Visual FoxPro Basisklasse. Erst die fertige Komponenten definieren Sie mit Hilfe des Klassenbrowsers um, sodaß Sie nun von einer der Basisklassen abgeleitet wird, die Sie in Ihren Projekten benutzen. So können Sie bei der Entwicklung der Komponente sicher sein, daß Sie keine Funktionalitäten Ihrer speziellen Basisklasse unbewußt vorausgesetzt haben.

Dokumentation

Dokumentieren Sie Ihre Komponenten! So wie Hardware-Bauteile in technischen Merkblättern beschrieben sind, muß für Software-Komponenten eine Dokumentation des Interfaces existieren. Ein solches technisches Merkblatt sollten Sie auch anfertigen, wenn Sie nicht beabsichtigen, Ihre Komponente weiterzugeben. Sie erleichtern sich damit selbst das Leben. Wie jeder Entwickler weiß, steht man nach einem Jahr seinen eigenen Programmen fast wie ein Fremder gegenüber. Dies gilt natürlich auch für die Schnittstellendefinition einer selbst entworfenen Komponente.

Eine Software-Komponente ist vollständig durch ihre offen gelegten (public) Eigenschaften und Methoden beschrieben. Deshalb sollte sich auch die Dokumentation darauf beschränken. Natürlich müssen die internen Abläufe und Zusammenhänge im Sourcecode so genau wie nötig beschrieben werden. Im technischen Merkblatt Ihrer Komponente haben diese Interna jedoch nichts zu suchen. Hier sollten Sie nur das Verhalten der Komponente nach außen hin beschreiben.

Komponentendesign

Geheimhaltung ist oberstes Designgebot

Die Wiederverwendbarkeit einer Komponente steht und fällt mit ihrer Kapselung. Dabei kommt es nicht so sehr darauf an, daß nichts nach außen dringt, sondern vielmehr darauf, daß nichts aus dem Umfeld der Komponente zu ihrem internen Aufbau verwendet wird. Jede Verbindung nach außen muß über eine komponenteneigene Eigenschaft oder Methode laufen. Dazu gehört zum Beispiel die Angabe, mit welcher Tabelle bzw. auf welchem Arbeitsbereich die Komponente arbeiten soll, Namen von betroffenen Tabellenfeldern oder Referenzen auf die Eigenschaften und Methoden anderer Komponenten. Selbst eine direkte Referenz auf ein globales Applikationsobjekt, von dem Sie annehmen, das es „immer da ist“, verletzt das Kapselungsgebot. Vorschläge zur Vermeidung solcher direkten Referenzen finden Sie weiter unten im Abschnitt „Realisierung von Komponenten“.

Verbindungen planen

Eine wichtige Frage, die in der Designphase geklärt werden muß, ist: Wie soll Ihre neue Komponente mit anderen Komponenten verbunden werden? Unterschiedliche Einsatzziele brauchen unterschiedliche Verbindungen.

Die Frage, ob Komponenten aggregiert oder assoziiert werden sollen, bestimmt auch die Auswahl der geeigneten Basisklasse. So muß ein Container als Basisklasse für Komponenten gewählt werden, die sich selber aus anderen Komponenten zusammensetzen. Objekte, die anderen Objekten nur sporadisch Dienste zur Verfügung stellen oder für allgemeine Aufgaben zuständig sind, sollten assoziativ genutzt werden. Objekte, die auf das Vorhandensein oder Nichtvorhandensein anderer Objekte unterschiedlich reagieren müssen, sollten dynamisch miteinander verbunden werden.

Realisierung von Komponenten

Grundlegendes zur Unabhängigkeit

Das Wesen der Kapselung wird häufig mißverstanden. Natürlich ist es wichtig, daß keine Stelle in Ihrer Applikation versehentlich eine Eigenschaft umbiegt, die an anderer Stelle lebenswichtig ist. Diese Versehen sind jedoch Programmierfehler, die durch einen Schutz der betreffenden Eigenschaft lediglich früher auffallen als ohne. Leider ist es aber in VFP oft nicht möglich, alle schützenswerten Eigenschaften und Methoden tatsächlich als PROTECTED oder HIDDEN zu deklarieren. (HIDDEN steht als Schutzmerkmal in VFP 5 zur Verfügung. Eigenschaften und Methoden, die HIDDEN sind, werden auch von den Subklassen nicht mehr gesehen.)

Wenn sich Komponenten innerhalb eines Containers (zum Beispiel einer FORM)  untereinander verständigen sollen, so müssen Eigenschaften und Methoden der Komponentenschnittstellen zwangsläufig ungeschützt bleiben. Damit sind sie aber auch von außerhalb des Containers erreichbar. Visual FoxPro fehlt es hier an einem Konzept der Container-Kapselung oder der „Friends“ - Klassen also, die „befreundet“ sind und somit untereinander auch geschützte Eigenschaften und Methoden erreichen, während diese von anderen Klassen nicht erreichbar sind. Die Container-Kapselung, die durch die Basisklasse Control angeboten wird, ist hier nicht verwendbar. Sie können nämlich einem abgeleiteten Control keine weiteren Objekte hinzufügen. Damit können Sie keine Control-Klasse für alle Komponenten vorsehen und diese erst in der zweiten Ableitung mit spezifischen Objekten bestücken.

Der wesentliche Aspekt der Kapselung bei der Realisierung von Komponenten ist jedoch, nichts aus der Umgebung als bekannt vorauszusetzen. Die Antwort auf die Frage, was Komponenten voneinander wissen dürfen, hängt vom geplanten Einsatzbereich ab. Wir haben uns am Anfang darüber verständigt, daß Visual FoxPro selbst unser „Framework“ bildet. Eine Komponente, die zum Einsatz in jeder beliebigen VFP-FORM gebaut wurde, darf natürlich alle Eigenschaften und Methoden der VFP-Basisklasse FORM kennen. Aber auch nur solche! Eigenschaften und Methoden, die Sie in Ihrer ersten Ableitung der Basisklasse FORM hinzugefügt haben, sind für eine Komponente, die in allen Forms funktionieren soll, schon wieder tabu.

Ein simples Beispiel: Bei der Realisierung  einer allgemein verwendbaren Komponente sollten Sie den gewünschten Arbeitsbereich nicht durch SELECT CUSTOMER (und schon gar nicht durch SELECT 2) anwählen, sondern durch SELECT (This.cAlias). Der Tabellenalias, auf dem Ihre Komponente arbeitet, muß also in einer Eigenschaft hinterlegt sein. Diese Eigenschaft kann voreingestellt oder im INIT-Event aus dem aktuellen Arbeitsbereich ermittelt oder aus einer eventuell vorhandenen RowSource-Eigenschaft abgeleitet werden. Ein Zugriff auf die Eigenschaft InitialSelectedAlias des DataEnvironments ist als Alternative ebenfalls möglich - als ausschließliche Setzung aber problematisch. In einer Form, die kein DataEnvironment hat, wäre eine Komponente, die ausschließlich auf dem InitialSelectedAlias arbeitet, nicht einsetzbar. Solche Setzungen, für die der benötigte Alias nur ein Beispiel ist, sollten immer alternativ vorgenommen werden können. Prüfen Sie zunächst, ob die Eigenschaft vorbelegt ist, wenn ja, arbeiten Sie mit dieser Vorbelegung. Falls keine Vorbelegung existiert, versuchen Sie einen sinnvollen Wert einzusetzen, hier zum Beispiel den InitialSelectedAlias. Wenn auch dort nichts eingetragen ist, arbeiten Sie auf dem aktuell eingestellten Arbeitsbereich. Die Prüfung, ob dort eine Tabelle eröffnet ist, bzw. ob ein eingetragener Alias verfügbar ist, braucht nur durchgeführt zu werden, wenn eine solche Situation eintreten kann, ohne daß dies ein Programmierfehler ist. VFP5 bietet mit dem ASSERT-Befehl eine gute Möglichkeit, solche Prüfungen bei der Entwicklung vorzunehmen, die in der fertigen Programmversion dann entfallen können.

Eigenschaften, die einen komponenteneigenen Wert, und Methoden, die die komponenteneigene Funktionalität beinhalten, sind recht einfach allgemein zu halten. Einige zusätzliche Überlegungen müssen bei Eigenschaften und Methoden angewandt werden, die generell oder wahlweise zur Verbindung mit anderen Komponenten dienen sollen. Mehr dazu weiter unten im Abschnitt „Verbindungstechnik“.

Basis- und andere Klassen

Was packt der Komponentenbauer in seine Basisklassen? Zunächst einmal Ableitungen aller visuellen Klassen von Visual FoxPro. Sie wollen ja sicherstellen, das gleiche Elemente Ihrer Applikation überall gleich aussehen. Beschränken Sie sich bei der Änderung und Ergänzung der VFP-Basisklassen aber auf Dinge, die nur das jeweilige Element betreffen. So dürfen Sie Ihren FORMs und anderen Containern im DestroyEvent der Basisklassen bereits beibringen, zunächst alle aggregierten Objekte zu entfernen - zum Beispiel mit Hilfe der RemoveObject-Methode. Des weiteren können Sie in den Basisklassen, auf denen Sie Ihre Komponenten aufbauen wollen, bereits Methoden und Eigenschaften hinterlegen, die allen Komponenten verfügbar sein sollen. Ein Beispiel dafür wären die weiter unten beschriebenen Methoden GetProp() und SetProp(). Ein Tip dazu: Die Basisklasse LABEL eignet sich sehr gut zum Aufbau von unsichtbaren Komponenten, die keine Container sein müssen. Sie verbraucht wenig Resourcen und läßt sich sinnvoll beschriften, so daß Sie auch in FORMs mit vielen unsichtbaren Komponenten zur Entwurfzeit den Überblick behalten. Eine eigens zur Erstellung von Komponenten abgeleitete Unterklasse von LABEL paßt also sehr gut in Ihre Basisklassen.

Wenn Sie eine Komponente weitergeben wollen, sollten Sie diese in einer eigenen VCX-Datei ablegen. Packen Sie in diese VCX zunächst Kopien aller in der Komponente benutzten Basisklassen. Beim Aufbau der Komponente benutzen Sie dann nur diese Kopien. Dadurch wird die neue VCX völlig unabhängig von Ihrer Basisklassen-VCX. Der Entwicklerkollege, der Ihre Komponente einsetzen will, kann die dort hinterlegten visuellen Basisklassen mit Hilfe des Klassenbrowsers auf seine eigenen Basisklasen umdefinieren, um Ihre Komponente an das Erscheinungsbild seiner Applikation anzugleichen. Namensgleichheit von Eigenschaften oder Methoden kann dabei natürlich Probleme verursachen. Die Verwendung einer eigenen Namenskonvention für Eigenschaften und Methoden entschärft das Problem, behebt es aber nicht vollständig.

Verbindungstechnik

Objektmethoden als Verbindung nach außen

Stellen Sie sich ein elektronisches Bauteil vor, zum Beispiel ein Verstärkerchip, in das ein kleines Loch gebohrt ist, in dem ein Bein eines Kondensators steckt, der somit mit dem Verstärkerchip „fest verdrahtet“ ist. Schwer vorstellbar, werden Sie sagen. Kein Ingenieur der Welt würde auf so was kommen. Er würde den Kondensator entweder im Verstärkerchip integrieren oder über einen Pin eine Anschlußmöglichkeit für den Kondensator nach außen legen. Verwenden Sie die gleiche Verfahrensweise beim Design von Komponenten: Jede Funktionalität, die nicht in Ihrer Komponente selbst realisieren werden soll, wird über eine Methode nach „außen“ gelegt. Das hat den Vorteil, daß diese Methoden beim Einbau der Komponente in eine größere Komponente überschrieben werden können. Dort kann also beim Zusammenbau mehrerer Komponenten der Aufruf der Methode einer anderen Komponente eingetragen werden.

Methoden, die erst beim konkreten Einsatz einer Komponente ausgefüllt werden, werden im folgenden Objektmethoden genannt. Im Gegensatz dazu bestimmen Klassenmethoden das Verhalten der Klasse, enthalten also die klasseneigene Funktionalität. Objektmethoden dienen dazu, das konkrete Objekt mit seiner Umgebung zu verbinden. Ein anderer Einsatz von Objektmethoden ist, Haken zu bilden, an die applikationsspezifische Dinge angehängt werden können. Diese Haken sind so etwas wie komponenteneigene Events. So kann eine Komponente „Beleg“, die zur Erfassung von Belegen unterschiedlichster Art eingesetzt wird, die Objektmethoden „BeforePrint“ und „AfterPrint“ anbieten. Hier können anwendungsspezifische Aktionen hinterlegt, die vor bzw. nach dem Ausdruck des Belegs stattfinden sollen.

Eigenschaften als Verbinder

Auch Eigenschaften können für die Verbindung von Komponenten genutzt werden. Das Prinzip der Kapselung verbietet es uns jedoch, in den Klassenmethoden einer Komponente direkt auf Eigenschaften einer anderen Komponente zuzugreifen. Wünschenswert wäre also die Möglichkeit, eigene Eigenschaften der Komponente mit den Eigenschaften anderer Komponenten verbinden zu können. Das läßt sich machen.

Die Vorbesetzung einer Eigenschaft kann in Visual FoxPro auch ein Ausdruck sein. VFP erkennt einen Ausdruck am führenden Gleichheitszeichen. Wenn Sie eine Eigenschaft mit dem Ergebnis des Ausdrucks 

lDies OR lDas  vorbesetzen wollen, so tragen Sie im Eigenschaftenfenster

=lDdies OR lDas  ein. Der Ausdruckseditor, der im Eigenschaftenfenster abrufbar ist, setzt das Gleichheitszeichen selbst vor den Ausdruck. VFP wertet den Ausdruck bei der Instantiierung des Objektes aus und trägt das Ergebnis in die Eigenschaft ein. Häufig ist dieses Verhalten genau das, was gewünscht wird. Es eignet sich aber nicht zur dauerhaften Verbindung zweier Objekte über eine Eigenschaft. Nach der Instantiierung des Objektes steht der Ausdruck nämlich nicht mehr zur Verfügung, sondern nur noch sein Ergebnis. Das Programm kann den Ausdruck zur Laufzeit also nicht erneut auswerten. Genau das muß es aber, wenn die Eigenschaft der Komponente als Verbindung zu einer anderen Komponente tauglich sein soll.

Um das Auswerten eines Ausdrucks als Eigenschaft zur Laufzeit zu ermöglichen, legen Sie eine eigene Syntax für Ausdrücke als Vorbesetzung einer Eigenschaft fest. Gut eignet sich dazu das kleiner-Zeichen "<" für einen Ausdruck, der nur lesend ausgewertet werden darf, und das größer-Zeichen ">" für einen Ausdruck, der ein Ziel angibt, in das auch geschrieben werden kann. Zwei Methoden, GetProp() und SetProp() in allen Komponenten dienen zum Lesen oder Setzen von Eigenschaften, die eventuell mit den Eigenschaften anderer Komponenten verbunden sind:

Der Zugriff auf Eigenschaften, die mit einer Eigenschaft einer anderen Komponente verbunden sein können, erfolgt dann nur über diese Methoden. Zum Beispiel aus dem InteractiveChange heraus:

Der Lese-Zugriff auf die Eigenschaft erfolgt mit einer ähnlichen Syntax:

Da VFP den Value-Wert selbst setzt, kann Value in der Vorbesetzung nicht direkt umgelenkt werden (VFP versteht die Umlenkungssyntax leider nicht). Die Vorbesetzung der Eigenschaft „Aktuell“ kann aber den zu setzenden Wert direkt in die Value-Eigenschaft eines anderen Objektes umleiten:

Darüber hinaus kann das ProgramaticChange-Ereignis des Ziel-Steuerelementes dazu benutzt werden, sofort auf den neu übergebenen Wert zu reagieren. So lassen sich auch Prozesse, die in unterschiedlichen Komponenten ablaufen, synchronisieren.

Dynamische Verbindungen

INTERFACE ist eine vom Autor entwickelte Klasse, die es ermöglicht, dynamische Verbindungen zwischen zwei oder mehreren Komponenten einer Anwendung zu erstellen. Eine konkrete Verbindung, die auf der Klasse INTERFACE basiert, besteht immer aus mindestens zwei Partnern. Die beiden Partner bilden sozusagen die beiden Enden eines Kabels, über das Daten zwischen den Komponenten einer Anwendung ausgetauscht werden können.

Die Verbindung der beiden Partner erfolgt dynamisch zur Laufzeit. Auch der Abbau der Verbindung beim Destroy eines Partners oder der Wechsel zwischen verschiedenen Partnern erfolgt automatisch. INTERFACE stellt somit den Kommunikationsmechanismus bereit. Welche Daten über dieses „Kabel“ geschickt werden, bestimmt die Applikation, in der INTERFACE eingesetzt wird.

Wozu dynamische Verbindungen?

Dynamische Verbindungen zwischen Objekten sind Verbindungen, die zur Laufzeit situationsbedingt aufgebaut werden müssen. Abhängig von den Aktionen des Anwenders entstehen Programmsituationen, die zur Entwicklungszeit nicht geplant werden können. Eine typisches Beispiel ist die Übergabe von Daten - z.B. einer Satznummer - aus einer Picklist an jedes Formular, das mit diesen Daten etwas anfangen kann. Es kann Programmsituationen geben, an denen keines der aktuell auf dem Bildschirm befindlichen Formulare die Daten der Picklist verarbeiten kann, in anderen Situationen stehen mehrere Formulare bereit, die mit den Picklist-Daten arbeiten könnten. Typischerweise sendet eine freie Picklist die Daten immer an das zuletzt aktive Formular. Auch dies erfordert dynamische Verbindungen, da das zuletzt aktive Formular jederzeit wechseln kann.

Eine andere Möglichkeit ist die Verbindung zwischen einer Toolbar  und den Formularen, die über die Toolbar gesteuert werden können. Eine dritte Anwendung wäre die Verbindung mit Datenrückgabe zwischen einem Formular und einem Unterformular, das aus bestimmten Gründen nicht modal aufgebaut werden kann. Aber nicht nur Formulare und Toolbars können so miteinander verbunden werden, sondern alle Komponenten einer Applikation, die zur Laufzeit dynamisch miteinander verknüpft werden müssen.

Die Arbeitsweise von INTERFACE:

Die Interfaceklasse wurde entwickelt, um auf möglichst flexible und einfache Art und Weise dynamische Verbindungen aufbauen zu können. Aus ihr lassen sich Interfacekomponenten ableiten, die in Formularen oder anderen Containern plaziert werden und die Verbindungen zu passenden Partnern selbständig aufbauen und verwalten.

Die dynamische Partnersuche benutzt im wesentlichen zwei Eigenschaften des konkreten Interface Objekts: „PartnerLocation“ gibt an, wo nach Partnern gesucht werden soll. Das sind im allgemeinen Namen von Containerobjekten, die einen Interfacepartner enthalten können. „PartnerLocation“ kann _SCREEN.ActiveForm sein oder auch THISFORM, wenn zwei Komponenten im selben Formular miteinander verbunden werden sollen. Darüber hinaus kennt INTERFACE eigene Schlüsselwörter für „PartnerLocation“, zum Beispiel LastActiveForm.

 „PartnerName“ gibt an, wie der Partner heißt.  In dem mit „PartnerLocation“ gekennzeichneten Container wird nach einem Objekt gesucht, das so heißt, wie in „PartnerName“ angegeben ist.

Wenn in der angegebenen „PartnerLocation“ ein Partner mit dem richtigen Namen gefunden wird, so sendet die suchende Interfaceseite an diesen Partner eine Verbindungsanfrage. Je nach Einstellung der „Mode“ Eigenschaft lehnt der gefundene Partner die Aufforderung zur Verbindung ab oder nimmt sie an.

Abhängig von der Einstellung der Eigenschaft „RequestMode“ wird die Partnersuche entweder nur auf Anforderung oder beim Init der Interfacekomponente oder timergesteuert durchgeführt. Alle Interfacekomponenten führen ständig Buch darüber, mit welchen Partnern sie gerade verbunden sind. Bei einer wiederholten Partnersuche (z.B. timergesteuert) werden alle bekannten Partner abgeprüft. Die Verbindung zu Partnern, die aus dem durch „PartnerLocation“ vorgegebenen Suchbereich verschwunden sind, wird automatisch aufgelöst, an neu im Suchbereich gefundene Partner  wird eine Verbindungsanfrage geschickt. Wenn eine Interfaceseite beim Schließen eines Formulars freigegeben wird, löst sie vorher alle eventuell noch bestehenden Verbindungen auf.

Zwei Beispiele für den Einsatz von INTERFACE:

1. Verbindungstyp Austausch

Der Austausch von Daten zwischen einer frei abrufbaren PickList und jeder FORM, die Daten der PickList verarbeiten kann, läßt sich mit je einem Austausch-Interface in der PickList und in der FORM bewerkstelligen. In der Klasse Austausch, die von INTERFACE abgeleitet ist, werden dazu die Methoden Receive() und Send() als Klassenmethoden, sowie AfterReceive() als Objektmethode hinterlegt. Die Methode AfterReceive() wird erst im Interface-Objekt einer jeden betroffenen FORM ausformuliert, da jede FORM mit den aus der PickList empfangenen Daten anders umgehen muß.

2. Verbindungstyp Steuerung

Dieser Verbindungstyp dient dazu, eine Toolbar zur Formularsteuerung mit dem jeweils gerade aktiven Formular zu verbinden. Steuerung kennt dazu z.B. die Methoden:

Add(), Browse(), Copy(), Delete(), Edit(), First(), Last(), Next() und Previous(). Das Steuerungs-Objekt, das sich in der Toolbar befindet, leitet die Mouseclicks auf die Tollbar-Buttons als Methodenaufrufe an das jeweils mit ihm verbundenen Steuerungs-Objekt in der FORM weiter.

INTERFACE ist Public Domain und im Compuserve Forum der deutschsprachigen FoxPro User Group (dFPUG) zu finden. (GO DFPUG, Bibliotheksbereich 9)

Fehlerbehandlung

Es gibt eine spezielle Problematik der Fehlerbehandlung in vorgefertigten Komponenten, auf die hier eingegangen werden soll.

Visual FoxPro ermöglicht es jedem Objekt, die in einer seiner Methoden auftretenden Fehler selbst zu behandeln. Damit gibt es prinzipiell keine Schwierigkeiten, auftretende Fehler abzufangen und zu untersuchen. Können die Fehler von der Komponente selbst behandelt werden, ist alles in Ordnung. Problematisch wird es erst, wenn der Fehler nicht selbst behandelt werden kann und somit an einen globalen ErrorHandler weitergereicht werden soll. Über Art und Weise des Aufrufs dieses ErrorHandlers kann und darf eine allgemein einsetzbare Komponente nicht wissen. Wie kann der Fehler also weitergereicht werden?

Einen Ausweg bieten auch hier die oben beschriebenen Objekt-Methoden. Dazu erhält die Komponente eine Objekt-Methode zur Behandlung all der Fehler, die von der Komponente nicht selber behandelt werden. Diese Methode soll zum Beispiel „OtherError“ heißen. Nun kann die Error-Methode der Komponente bestimmte Fehler selbst behandeln und die anderen an OtherError weitergeben. Die OtherError-Methode wird mit einem möglichst allgemein formulierten Aufruf eines globalen ErrorHandlers vorbesetzt und kann von jedem, der die Komponente einsetzt, überschrieben werden.

Der folgende Code in einer ErrorEvent-Methode einer Komponente behandelt den Fehler Nr. 1 = „Datei nicht vorhanden“ selbst und übergibt alle anderen Fehler an OtherError:

Üblicherweise erfolgt ein Aufruf des globalen ErrorHandlers über eine Anweisung wie:

Dem ErrorHandler werden also die wichtigsten Parameter beim Aufruf übergeben. Der ErrorEvent-Methode eines Objektes wird von Visual FoxPro ebenfalls die Fehlernummer, der Name der Methode, in der der Fehler auftrat und die Zeilennummer übergeben. Um sicherzustellen, daß die Weitergabe des Fehlers an den globalen ErrorHandler mit den richtigen Parametern erfolgt, wird die Aufrufzeile, die durch ON(“ERROR“) zurückgegeben wird, mit den aktuellen Parametern aufbereitet.

Weiterführende Konzepte

Callback-Methoden

Bei größeren, zentral genutzten Komponenten, funktioniert das Konzept der Objektmethoden, das oben beschrieben wurde, nicht mehr. Dadurch, daß unterschiedliche Clients die Dienste einer Komponente nutzen sollen, können spezifische Abläufe nicht in der Komponente selbst hinterlegt werden, sondern müssen in Methoden des jeweiligen Client untergebracht werden. Beispiel dafür ist eine Komponente, die Daten an Winword-Formulare weitergibt. Wenn in einem solchen Formular nun eine Tabelle auftaucht, kann die Komponente selbst nicht entscheiden, in welcher Datenbanktabelle nun geSKIPt werden muß, um die Zeilen der Winword-Tabelle zu füllen. Also muß ein Client, der die Dienste dieser Komponente nutzen will, selbst eine Methode dazu anbieten. Beim Aufruf der Komponente wird dieser mitgeteilt, wie die Methode heißt, die aufgerufen werden soll, es wird also ein Rückruf (Callback) für eine ganz bestimmte Situation - hier die Notwendigkeit, einen SKIP auszuführen und damit einen neuen Datensatz bereitzustellen - vereinbart.

In dem Aufruf der Komponente übergibt der Client eine Objektreferenz auf sich selbst und den Namen der Callback-Methode, wie im folgenden Beispiel:

Die aufgerufene Methode der Komponente nimmt die Parameter entgegen:

und führt zum richtigen Zeitpunkt den Rückruf durch:

Der Aufruf der Callback-Methode erfolgt über eine Makrosubstitution. Übergabeparameter und Rückgaben der Callback-Methoden werden von der Serverkomponente festgelegt. Der Client muß sich an diese Festlegungen halten.

Messaging

Wenn eine Komponente stets über Aktionen oder Zustände in anderen Komponenten Bescheid wissen muß, gibt es zwei Möglichkeiten, die dazu notwendigen Abläufe aufzubauen.

Zum einen könnte die Komponente selbst in regelmäßigen Zeitabständen die für sie wichtigen Informationen zusammentragen. Das läßt sich mit Hilfe eines Timers realisieren. Außerdem muß die Komponente wissen, wo die Informationen abgeholt werden müssen, d.h. sie muß Referenzen auf die betreffenden anderen Komponenten halten und die Namen der interessierenden Eigenschaften oder Methoden kennen. Die Referenzen könnten dabei über die oben beschriebene INTERFACE-Klasse dynamisch verwaltet werden. Die Namen der abzufragenden Methoden und Eigenschaften können in Eigenschaften der abfragenden Komponente hinterlegt werden, um eine möglichst lose Kopplung zwischen den Komponenten zu erzielen. Was bei diesem Verfahren am meisten stört, ist der Timer. Mehrere laufende Timer haben durchaus negative Auswirkungen auf die Antwortzeiten der Applikation, zum Beispiel beim Refresh einer Form. Außerdem ist das Debuggen schon bei nur einem laufenden Timer eine Qual.

Um die Verwendung von Timern zu umgehen, kann man einen anderen Ansatz wählen und ein applikationsweites Messaging installieren. Hierbei wird die Beobachtung von außen ersetzt durch eine Mitteilung von innen. Die Objekte, deren Status beim ersten Verfahren von außen überwacht wird, schicken jetzt selber eine Message ab, wenn sich an ihrem Status etwas ändert, das andere Objekte interessieren könnte.

Ein Messaging-System besteht immer aus zwei Teilen, Terminal-Komponenten, die für das Versenden und Empfangen von Messages zuständig sind, und einem Messagemediator, der die Rolle eines Postverteilers spielt. Die Terminal-Komponenten melden sich beim Mediator an und schicken alle eigenen Messages an den Mediator. Dieser verteilt die eingehenden Messages an alle momentan bei ihm angemeldeten Komponenten. Der Inhalt der einzelnen Messages wird dabei von der sendenen Komponente festgelegt und gehört zur dokumentierten Schnittstelle der Komponente. So könnte eine FORM in ihrem ActivateEvent die Message:

senden, während jede PAGE der FORM im ActivateEvent die Message

aussendet. Eine Steuerungskomponente, die sich zum Beispiel in einer Toolbar oder einem Menüobjekt befindet, kann auf die Nachrichten reagieren, indem sie sich mit der jetzt aktiven PAGE oder FORM verbindet und sich auf die dort vorliegenden Bedingungen einstellt.

Schlußbemerkung

Der Zusammenbau von Applikationen aus vorgefertigten Komponenten ist inzwischen ein in der Fachpresse vieldiskutiertes Thema. Die Komponenten, von denen dort die Rede ist, sind meistens in C++ programmiert und können als OCXe oder DLLs in andere Applikationen eingebunden werden. Die Zielapplikation muß dazu nicht in der selben Sprache geschrieben sein wie die Komponente selbst.

Da VFP 5 als OLE-Server arbeiten kann, können Sie auch mit VFP 5 Komponenten bauen, die von Applikationen in anderen Sprachen genutzt werden. Beim Design solcher Komponenten wird das erste Gebot für alle Komponenten, nämlich die vollständige Kapselung von den Bedingungen der Zielumgebung, offensichtlich. Jeder Kontakt mit der „Außenwelt“ muß über komponenteneigene Eigenschaften und Methoden erfolgen, da Sie beim Design Ihrer Komponente nicht wissen können, ob der Client ein VisualBasic Programm, C++ oder vielleicht ein Word-Makro ist.

Dieses Prinzip der vollständigen Kapselung ist aber auch ein nützliches Designprinzip für Ihre eigenen, rein in VFP geschriebenen Applikationen. Ich hoffe, Ihnen mit meiner Session einige wichtige Konstruktionstechniken dazu vermitteln zu können.