Assign- und Access Methoden gehören zu den wichtigsten und aufregendsten Spracherweiterungen von Visual FoxPro 6.0. Diese Session versucht sie angemessen darzustellen, vermutlich werden aber im Laufe der Zeit noch viele neue Tips und Tricks gefunden werden, die erst mit Assign- und Access-Methoden möglich sind. Es handelt sich um ein Feld, in dem in der Tat noch Forschungsbedarf besteht.
Die Namensgebung ist irreführend, denn aus der Sicht des Visual FoxPro Entwicklers handelt es sich bei den Assign- und Access-Methoden eigentlich um Ereignisse. Um Ereignisse, die von Visual FoxPro automatisch ausgelöst werden, wann immer auf eine Eigeschaft schreibend (Assign) oder lesend (Access) zugegriffen wird. Dabei muß es sich nicht immer um direkte Zugriffe im Code handeln, sondern erfaßt werden alle Schreib- und Lesezugriffe.
Wie bei vielen anderen Ereignissen auch können Sie direkt in diese Vorgänge eingreifen und so beispielsweise das Ändern einer Eigenschaft verhindern oder weitere Aufgaben durchführen. Besonders spannend daran ist aber, daß diese Möglichkeit nicht nur für selbstdefinierte Eigenschaften bereitsteht, sondern Sie auch für fast alle von Visual FoxPro angebotenen Eigenschaften Assign- und Access-Methoden definieren können. Das heißt im Klartext, Sie können darauf reagieren, wenn Viusal FoxPro eine Eigenschaft ändert! Bislang gab es diese Möglichkeiten nur für den schreibenden Zugriff auf die Value-Eigenschaft, die entweder das Programmatic-Change() oder das InteractiveChange() Ereignis auslöst.
Stark vereinfacht kann man Assign- und Access-Methoden mit den allseits bekannten Setxxx() und Getxxx() Methoden vergleichen. Allerdings erfolgt die Nutzung von Assign und Access Methoden transparent. Anstelle von
=oObject.SetProperty( "Wert" )
können Sie nun folgende Syntax verwenden
oObject.Property = "Wert"
und erreichen dennoch die gleichen Ergebnisse. Zudem bieten Assign- und Access-Methoden die oben angesprochene Erwieterung auf von Visual FoxPro definierte Eigenschaften und werden von Visual FoxPro automatisch berücksichtigt.
Visual FoxPro bietet drei Arten von Assign- und Access-Methoden: Methoden, die den Lesezugriff auf eine Eigenschaft abfangen (Property_Access), Methoden, die den Schreibzugriff auf eine Eigenschaft abfangen (Property_Assign) und Methoden, die den Zugriff auf jede Eigenschaft und Methode eines Objektes abfangen (THIS_Access).
Immer wenn Visual FoxPro auf eine Zuweisung an eine Eigenschaft eines Objektes stößt oder diese Eigenschaft gelesen werden soll, prüft es, ob es eine Methode mit den Namen der Eigenschaft mit der Ergänzung "_ACCESS" oder "_ASSIGN" gibt und ruft gegebenenfalls diese Methode auf. Wenn Sie also für die Eigenschaft "cName" eine Assign-Methode anlegen wollen, so definieren Sie eine Methode "cName_Assign". Gleichermaßen gilt für das Anlegen einer Access-Methode, daß diese den Namen "cName_Access" haben muß. Die Namensgebung ist von Visual FoxPro vorgegeben und muß von Ihnen befolgt werden. Wenn Sie also eine Klasse programmatisch definieren wollen, sieht das wie folgt aus:
DEFINE CLASS MyClass AS CUSTOM
cName = ""
PROCEDURE cName_Access
RETURN This.cName
PROCEDURE cName_Assign
LPARAMETER tcValue
This.cName = m.tcValue
ENDPROC
ENDDEFINE
An diesem Beispiel können Sie bereits die Grundzüge von Assign- und Access-Methoden sehen. Die Access-Methode muß einen Wert zurückliefern, der dann als der aktuelle Inhalt von cName betrachtet wird. In obigen Beispiel erfolgt diese Rückgabe direkt, die Access-Methode macht also nichts anderes, als es Visual FoxPro ohnehin getan hätte. Natürlich können Sie aber jeden beliebigen Code in der Access-Methode ausführen und auch jeden beliebigen Wert zurückgeben, es muß sich nicht zwangsläufig um die Eigenschaft handeln, die angefordert wurde. Wenn Sie innerhalb einer Access-Methode die Eigenschaft auslesen, wird die Access-Methode nicht noch einmal aufgerufen.
Die Assign-Methode dagegen erhält als Parameter den Wert, der der Eigenschaft zugewiesen werden soll. Sobald Sie eine Assign-Methode definiert haben, übernimmt Visual FoxPro diese Zuweisung nicht mehr für Sie, vielmehr müssen Sie explizit die Zuweisung durchführen. Auch hier gilt wie bei der Access-Methode, daß eine Zuweisung in einer Assign-Methode nicht noch einmal die gleiche Assign-Methode aufruft. Wenn Sie allerdings eine Zuweisung an eine andere Eigenschaft vornehmen, wird sehr wohl deren Assign-Methode ausgeführt. Der Wert, den Sie der Eigenschaft zuweisen muß nicht zwangsweise der Wert sein, den Sie als Parameter erhalten. So können Sie beispielsweise den Ausdruck UPPER(m.tcValue) zuweisen. Damit würde die Eigenschaft grundsätzlich in Großbuchstaben geschriebene Werte enthalten, egal was Sie ihr im übrigen Programm zuweisen.
Diese Flexibilität geht soweit, daß Sie nicht einmal den Wert zuweisen müssen. So wäre es beispielsweise denkbar, daß Sie eine Klasse definieren, die ihre Eigenschaften in einer Tabelle speichert und auch die Eigenschaften aus dieser Tabelle wieder ausliest. Die Eigenschaft selbst würde dann nur noch Platzhalter-Funktion ausüben. Auf diese Weise lassen sich auf einfachste Weise persistente Objekte erstellen.
Visual FoxPro unterstützt Sie bei der Erstellung dieser Methoden. So müssen Sie die entsprechenden Methoden nicht selbst anlegen. Wenn Sie eine neue Eigenschaft über Klasse|Neue Eigenschaft definieren, finden Sie in diesem Dialog zwei neue Checkboxen, die mit "Assign" und "Access" beschriftet sind. Wenn Sie die jeweiligen Checkbox aktivieren, wird von Visual FoxPro automatisch die entsprechende Methode erstellt. Und nicht nur daß, es wird auch der Code automatisch in die Methode geschrieben, der wie im obigen Beispiel das Standardverhalten erzeugt. Natürlich werden Sie diesen Code ändern wollen, sonst bräuchten Sie ja keine Assign- oder Access-Methode anlegen. Übrigens sind beide Methoden von einander unabhängig. Sie können für eine Eigenschaft nur eine Access-Methode, nur eine Assign-Methode oder beide anlegen.
Auch wenn Sie nachträglich eine solche Methode anlegen wollen, unterstützt Sie Visual FoxPro dabei. Mittels Klasse|Eigenschaften bearbeiten erhalten Sie einen vollkommen neu gestalteten Dialog, der ebenfalls die beiden Checkboxen "Assign" und "Access" beinhaltet. Wählen Sie die gewünschte Eigenschaft aus, machen Sie in den Checkboxen ein Häkchen und bestätigen Sie mit OK. Diesen Dialog verwenden Sie auch, wenn Sie eine Assign- oder Access-Methode für eine von Visual FoxPro definierte Eigenschaft anlegen wollen.
Bislang haben wir nur Assign- und Access-Methoden für einfache Eigenschaften betrachtet. Wie aber verhält es sich mit Arrays? Nun, in diesem Fall werden der Access-Methode die Indexwerte übergeben. Standardmäßig sieht die Access-Methode eines zweidimensionalen Arrays daher wie folgt aus:
PROCEDURE aArray_Access
LPARAMETERS m.nIndex1, m.nIndex2
RETURN THIS.test[m.nIndex1, m.nIndex2]
ENDPROC
Das besondere an den übergebenen Parametern ist allerdings, daß sie nicht numerisch sein müssen! Die Indexwerte werden an die Access-Methode übergeben, noch bevor sie von Visual FoxPro ausgewertet werden. Es ist also vollkommen legitim folgendes zu schreiben:
? oObject.aArray["Eins"]
Solange für aArray eine Access-Methode existiert, die die notwendige Auswertung vornimmt und den entsprechenden Wert zurückgibt. Auch die Assign.Methode erhält als Parameter die Index-Werte. Allerdings gibt es hier eine Besonderheit zu berücksichtigen In Visual FoxPro ist es möglich, allen Elementen den gleichen Wert zuzuweisen, indem kein Index angegeben wird:
DIMENSION laArray[10]
laArray = 0
Anschließend enthalten alle zehn Elemente den Wert 0. In einem solchen Fall wird der Assign-Methode als Index-Parameter der Wert .NULL. übergeben, weswegen der Standard-Code für die Assign-Methode geringfügig komplexer ist:
PROCEDURE aArray_Assign
LPARAMETERS vNewVal, m.nIndex1, m.nIndex2
IF ISNULL(m.nIndex1)
THIS.test = m.vNewVal
ELSE
THIS.test[m.nIndex1, m.nIndex2] = m.vNewVal
ENDIF
ENDPROC
Eine Access-Methode haben wir bislang aber außen vor gelassen: THIS_Access. Diese Methode fängt alle Zugriffe auf jede einzelne Eigenschaft und sogar jeden Methodenaufruf ab. Um eine solche Methode zu definieren, müssen Sie eine neue Methode unter Klasse|Neue Methode anlegen und ihr den Namen "This_Access" geben. Wie auch bei allen anderen Assign- und Access-Methoden fügt Visual FoxPro automatisch Standard-Code ein, der dafür sorgt, daß sich die Methode so verhält, als ob sie nicht existent wäre:
PROCEDURE THIS_Access
LPARAMETERS cMember
RETURN THIS
ENDPROC
Im Gegensatz zu anderen Access-Methoden erhalten Sie hier nur einen Parameter, den Namen des Elementes, auf das zugegriffen werden soll. Zurückgegeben wird die Referenz auf das Objekt, in dem sich die gewünschte Eigenschaft befindet. Auch hier sind sie wieder frei, andere Referenzen zurückzugeben, es muß nicht immer THIS sein. Das eröffnet viele ungeahnte Möglichkeiten, und würde noch viel mehr eröffnen, wären da nicht wieder einmal Bugs, die uns das Leben schwermachten. Dazu können Sie weiter unten Details nachlesen. Wichtig ist es hier zu betonen, daß THIS_ACCESS für jeden einzelnen Zugriff aufgerufen wird. Nehmen Sie an, wir haben folgenden Code:
oX = NEWOBJECT( "Klasse" )
oX.Move( oX.Left+1, oX.Top+1 )
In diesem Fall würde THIS_ACCESS insgesamt dreimal aufgerufen. Beim ersten Aufruf erhielten Sie "left" als Parameter, beim zweiten "top" und beim letzten schließlich "move". Hätten Sie auf Left und Top zusätzlich Access-Funktionen definiert, so würden diese zusätzlich aufgerufen werden. THIS_ACCESS ist also kein Ersatz für die einzelnen Access-Methoden, sondern vielmehr eine Erweiterung. So können Sie in THIS_ACCESS zwar bestimmen, die Eigenschaften welches Objektes verwendet werden sollen, sie können aber die Rückgabe der Eigenschaften selbst nicht beeinflußen.
Alle Assign- und Access-Methoden sind grundsätzlich PROTECTED (im Klassendesigner nennt sich das geschützt), auch wenn sie nicht explizit so definiert werden. Das heißt, Sie können von außerhalb nicht diese Methoden aufrufen. Dies ist nur innerhalb der Klasse selbst möglich. Wenn Sie eine Container-Klasse definieren, kann nur der Container selbst diese Methoden aufrufen. Für alle im Container enthaltenen Objekte sind die Assign- und Access-Methoden ebenso unsichtbar wie für alle Aufrufe aus anderen Programmen.
So schön Assign- und Access-Methoden auch sein mögen, sie haben ihre Grenzen. Einige davon sind durch Fehler in Visual FoxPro verursacht. Auf diese Probleme kommen wir später noch zurück. Die Grenzen, die hier behandelt werden sollen, sind zum einen die Grenzen durch das Design der Assign- und Access-Methoden, sowie die praktischen Grenzen.
Die erste Limitierung besteht in den Eigenschaften, die Visual FoxPro bereitstellt, und für die Sie Assign-Methoden definieren können. Dies ist zwar prinzipiell für alle Eigenschaften möglich, allerdings mit der Ausnahme von schreibgeschützten Eigenschaften. Dies sind alle Eigenschaften, die in Eigenschaftenfenster kursiv dargestellt sind. So wäre es doch sicherlich interessant, wenn das Formular feststellen könnte, wenn ein anderes Steuerelement aktiviert wird. Aber leider können Sie für ActiveControl keine Assign-Methode definieren. Genauer gesagt, können Sie diese Methode zwar erstellen, aber sie wird von Visual FoxPro nicht aufgerufen.
Gleiches gilt beispielsweise auch für ActiveColumn und ActiveRow. Um einen Zeilenwechsel in einem Grid festzustellen, müssen Sie bislang immer das kombinierte AfterRowColChange Ereignis verwenden und selber herausfinden, ob es sich um einen Zeilen- oder Spaltenwechsel handelt. Eine Assign-Methode auf bei Eigenschaften würde das Problem lösen, aber ist in der Praxis nicht durchführbar.
Sie können für Visual FoxPro Eigenschaften Access-Methoden definieren. Sollten Sie aber nun annehmen, diese würden aufgerufen werden, wenn Visual FoxPro auf diese Eigenschaften zugreift, muß ich Sie leider enttäuschen. Zwar wird die Assign-Methode aufgerufen, wenn Visual FoxPro eine Eigenschaft ändert, aber nicht, wenn es eine Eigenschaft abfragt. Solche Access-Methoden werden nur dann von Visual FoxPro verwendet, wenn Sie in Ihrem Code explizit auf die Eigenschaft zugreifen.
Und dann sind da natürlich auch die praktischen Grenzen. Assign- und Access-Methoden sind zwar nützlich, aber auch langsam. Gegenüber einem normalen Zugriff auf eine Eigenschaft ist eine Access-Methode bis zu zwanzig mal langsamer. Besonders schwerwiegend ist dies bei THIS_Access, denn diese Methode wird vor jeden Zugriff auf eine Eigenschaft und vor jedem Methodenaufruf auusgeführt. Assign- und Access-Methoden sollten der angemessen verwendet werden.
Visual FoxPro ist eine Sprache mit sogenanntem "weak typing". Variablen haben im Gegensatz zu den meisten anderen Sprachen keinen expliziten Typ, sondern können Werte jeden beliebigen Types annehmen, Sie können also einer Eigenschaft erst einen numerischen Wert, dann einen String und zum Schluß ein Datum zuweisen. Obwohl das in vielen Fällen praktisch ist, würde es bei der Entwicklung einer Klasse doch zur Stabilität beitragen, wenn die Klasse ungültige Typen abweist. Wenn Sie beispielsweise der Caption eines Formulares einen numerischen Wert zuweisen, erhalten Sie eine Fehlermeldung. Wenn aber Ihre Klasse eine Eigenschaft cCaption definiert, die ebenfalls nur Strings enthalten soll, hindert niemand die Nutzer Ihrer Klassen daran, dort jeden beliebigen Wert zu hinterlegen.
Wäre es nicht schön, wenn diese Nutzer schon bei der Zuweisung auf Ihren Fehler hingewiesen würden, und nicht später Ihr Code einen Laufzeit-Fehler verursacht, der Ihnen nachher als Programmierungsfehler ausgelegt wird? Um das zu erreichen, müssen Sie nur die Assign-Methode der Eigenschaft um eine Typprüfung erweitern:
Procedure cValue_Assign
LParameter tcValue
If VarType(m.tcValue) == "C"
This.cValue = tcValue
Else
Error "wrong type"
Endif
EndProc
Einige Eigenschaften von Visual FoxPro sind ausschließlich zu Informationszwecken gedacht. Jeder Versuch Ihnen einen Wert zuzuweisen, scheitert mit einer Fehlermeldung "Diese Eigenschaft ist schreibgeschützt". Wenn Sie dagegen solche Eigenschaften definieren wollen, müssen Sie sich darauf verlassen, daß die Benutzer Ihrer Klasse in diesen Eigenschaften keinen Wert hinterlegen, oder aber eine Methode verwenden, wo eine Eigenschaft eigentlich sinnvoller wäre.
Mit Assign-Methoden ist es kein Problem, auch bei Ihren Klassen schreibgeschützte Eigenenschaft zu erzeugen. Im einfachsten Fall entfernen Sie einfach die Zeile, die der Eigenschaft einen Wert zuweist. Jede Zuweisung wird fortan ignoriert, leider auch die Zuweisungen, die Sie in der Klasse selbst vornehmen wollen. Daher müssen Sie einen Weg finden, der jeglichen Zugriff von außen verbietet, aber den Zugriff innerhalb der Klasse zuläßt. Die einfachste Art, dies zu erreichen, ist eine geschützte Eigenschaft (PROTECTED), die angibt, ob der Schreibzugriff erlaubt oder verboten ist. Da diese Eigenschaft geschützt ist, kann Sie nur innerhalb der Klasse geändert werden, nicht von innerhalb. Im folgenden Beispiel ist dies die Eigenschaft lAssign:
Diese Lösung besteht aus zwei Teilen. Wenn Sie der Eigenschaft nReadOnly einen Wert zuweisen wollen, wird diese Zuwesiung grundsätzlich ignoriert. Wenn Sie wollen, können Sie natürlich mittels ERROR auch eine Fehlermeldung ausgeben. Um die Eigenschaft zu ändern, müssen Sie die Methode SetProperty("nReadOnly",Wert) verwenden. Da diese Methode ebenso wie die Eigenschaft lAssign geschützt ist, kann sie nur innerhalb der Klasse selbst verwendet werden. Die Eigenschaft nReadOnly bleibt also für Außenstehende unerreichbar, was den Schreibzugriff anbelangt.
Eine der hervorstechenden Eigenschaften von Assign- und Access Methoden ist die Möglichkeit, beliebige Strings als Parameter zu verwenden. Da eröffnet zahlreiche neue Möglichkeiten, denn plötzlich ist der Zugriff nicht an einen festen Index gebunden. Vielmehr können Sie dynamisch entscheiden, welches Arrayelement gemeint ist. Ja nicht nur sich auf ein Arrayelement beschränken, Sie können vielmehr Arrays definieren, die auf mehrere Elemente gleichzeitig zugreifen.
Ein in der Praxis sicherlich interessantes Anwendungsgebiet sind sogenannte Object Collections. Im Prinzip sind dies Arrays von Objekten, wobei Sie ein Objekt aber über einen Namen ansprechen können. Nehmen Sie beispielsweise an, sie hätten ein Array aller Kunden in Ihre Kundenverwaltung. Diese Arrays können sogar sehr effizient verwaltet werden, mit einer ähnlichen Geschwindigkeit wie der Tabellenzugriff von Visual FoxPro. Nun wollen Sie das Kreditlimit dieses Kunden abfragen. Anstatt direkt über Tabellen zuzugreifen, machen Sie das nun über ein Objekt, daß alle Kunden enthält:
oKunden.Item["Müller"].Kreditlimit
In der Access-Methode erhalten Sie als Parameter "Müller". Das Array selbst muß gerade einmal ein Element groß sein, welche Werte zurückgeliefert werden, entscheiden wir in der Access-Methode selbst. Niemand hindert uns daran für ein und das selbe Element immer andere Werte zurückzugeben. Nun können Sie in der Access-Methode in Ihrer Kunden-Tabelle nach dem Kunden Müller suchen und mittels SCATTER ein Objekt zurückgeben, das alle Kundendaten enthält. Auf dieses Objekt greift dann ihr Programm zurück. Natürlich bietet es sich an, dort Optimierungen vorzunehmen, eigene Kundenobjekte zu definieren, die die Werte aber auch Methoden enthalten, und vieles mehr.
Der Vorteil einer solchen Zugriffsweise auf Ihre Daten kommt spätestens dann zum tragen, wenn Sie plötzlich die Daten aus anderen Quellen lesen sollen, beispielsweise über ADO. Bei einem konsequent objektorientiert durchgeführten Datenzugriff müssen Sie das Programm selbst nicht anpassen, nur die Datenobjekte müssen geändert werden.
Für die Einführung in solche Manipulationen wollen wir uns aber an ein einfacheres Beispiel wagen. Ein Array, auf das Sie neben den ganz normalen Indexwerten auch über einige Schlüsselworte zugreifen können. Als weiteres Feature bietet dieses Array die Möglichkeit der Initialisierung des gesamten Arrays mit einer Zeile und ebenso die Rückgabe des gesamten Arrays als einzelner String.
Define Class XArray as Line
Dimension Item[10]
Protected nCurrent
nCurrent = 1
Procedure Item_Access
LParameter tuIndex
Do Case
* Wenn es sich um den Index 0 handelt, liefern wir das komplette
* Array zurück. Jedes Element wird dabei durch Kommata getrennt
Case Type("m.tuIndex") == "N" and m.tuIndex == 0
Local luElement, lcArray
lcArray = ""
For Each luElement in This.Item
If not Empty(m.lcArray)
lcArray = m.lcArray + ","
Endif
lcArray = m.lcArray + Transform(m.luElement)
Endfor
Return m.lcArray
* Wenn ein numerischer Wert übergeben wird, greifen wir direkt
* auf das Element zu und machen dieses zum aktuellen Element
Case Type("m.tuIndex") == "N"
This.nCurrent = m.tuIndex
Return This.Item[This.nCurrent]
* Wenn es sich um einen String handelt, werten wir diesen
* aus und liefern das gewünschte Element zurück.
Case Type("m.tuIndex") == "C"
Local lcCommand
lcCommand = Upper( Alltrim(m.tuIndex) )
Do Case
Case lcCommand == "FIRST"
This.nCurrent = 1
Return This.Item[This.nCurrent]
Case lcCommand == "LAST"
This.nCurrent = ALen(This.Item)
Return This.Item[This.nCurrent]
Case lcCommand == "NEXT"
This.nCurrent = Min( This.nCurrent+1, ALen(This.Item) )
Return This.Item[This.nCurrent]
Case lcCommand == "PREVIOUS"
This.nCurrent = Max( This.nCurrent-1, 1 )
Return This.Item[This.nCurrent]
Case lcCommand == "CURRENT"
Return This.Item[This.nCurrent]
Endcase
Endcase
EndProc
Procedure Item_Assign
LParameter tuValue, tuIndex
Do Case
* Wenn es sich um den Index 0 oder .NULL. handelt, soll dem
* kompletten Array ein Wert zugewiesen werden, entweder durch
* oArray.Item = "Werteliste"
* oder
* oArray.Item[0] = "Werteliste"
* In diesem Fall erwarten wir einen kommaspearierten String
* und weisen jedem Arrayelement ein Element aus der Liste zu.
Case IsNull(m.tuIndex) ;
or (Type("m.tuIndex")=="N" and m.tuIndex==0)
Local laSource[1]
ALines( laSource, ChrTran(m.tuValue,",",Chr(13)) )
ACopy( laSource, This.Item )
* Wenn ein numerischer Wert übergeben wird, greifen wir direkt
* auf das Element zu und machen dieses zum aktuellen Element
Case Type("m.tuIndex") == "N"
This.nCurrent = m.tuIndex
This.Item[This.nCurrent] = m.tuValue
* Wenn es sich um einen String handelt, werten wir diesen
* aus und schreiben den Wert in das gewünschte Element.
Case Type("m.tuIndex") == "C"
Local lcCommand
lcCommand = Upper( Alltrim(m.tuIndex) )
Do Case
Case lcCommand == "FIRST"
This.nCurrent = 1
This.Item[This.nCurrent] = m.tuValue
Case lcCommand == "LAST"
This.nCurrent = ALen(This.Item)
This.Item[This.nCurrent] = m.tuValue
Case lcCommand == "NEXT"
This.nCurrent = Min( This.nCurrent+1, ALen(This.Item) )
This.Item[This.nCurrent] = m.tuValue
Case lcCommand == "PREVIOUS"
This.nCurrent = Max( This.nCurrent-1, 1 )
This.Item[This.nCurrent] = m.tuValue
Case lcCommand == "CURRENT"
This.Item[This.nCurrent] = m.tuValue
Endcase
Endcase
EndProc
Enddefine
Wenn Sie nun von dieser Klasse mit NEWOBJECT() eine Instanz erzeugen, haben Sie mehrere Möglichkeiten, auf dieses Array zuzugreifen:
oArray = NewObject("XArray")
? oArray.Item[5] && gibt das 5. Element aus
? oArray.Item["First"] && das 1. Element
? oArray.Item["Next"] && das 2.Element
? oArray.Item["Next"] && das 3. Element
? oArray.Item["Current"] && das 3. Element
? oArray.Item["Previous"] && das 2. Element
? oArray.Item["Last"] && das 10. Element
Um das Array zu initialisieren, reicht nun eine Zeile:
oArray.Item = "1,2,3,4,5,6,7,8,9,10"
? oArray.Item[1] && Ergibt "1"
Ebenso können Sie das komplette Array auf einmal ausgeben:
oArray.Item[1] = "Eins"
? oArray.Item[0] && Ergibt "Eins,2,3,4,5,6,7,8,9,10"
Im komponentenorientierten Design kommt es immer wieder vor, daß Eigenschaften umlenkbar sein müssen. Stellen Sie sich vor, Sie haben eine Textbox entwickelt, die Sie in vielen verschiedenen Projekten einsetzen möchten. Diese Textbox soll je nach Benutzterrechten editierbar sein, oder nicht. Wenn Sie die Textbox entwicklen, wissen Sie aber nicht, wie die Benutzetrverwaltung aussehen wird, Sie müssen zwangsweise generisch entwickeln.
Also definieren Sie eine Eigenschaft lAllowEdit, die entweder .T. oder .F. sein kann. Wenn Sie .F. ist, wird die Textbox auf Readonly gesetzt, andernfalls bleibt Sie editierbar. Nun aber stellen Sie sich vor, Sie wollen die Klasse in einer Applikation einsetzen. Im Prinzip müßten Sie jetzt für alle Objekte diese Eigenschaft entsprechend setzen. Und wenn sich ein neuer Anwender anmeldet? Dann müßten Sie unter Umständen durch alle Formulare druchgehen und diese Eigenschaft aktualisieren. Das ist weder sehr gekapselt, noch sehr einfach in der Anwendung. Besser wäre es, wenn Sie der Textbox irgendwie sagen könnten, wie es selbst überprüfen kann, ob der Benutzter berechtigt ist.
Dazu erstellen Sie bei der Entwicklung dieser Komponente eine Access-Methode für die Eigenschaft lAllowEdit. In dieser Methode liefern Sie den aktuellen Wert zurück, wenn es sich um einen logischen Wert handelt. Wenn es sich aber um einen String handelt, so werten Sie diesen Ausdruck aus und liefern dessen Ergebnis zurück:
Procedure lAllowEdit_Access
If VarType(This.lAllowEdit) == "L"
Return This.lAllowEdit
Else
Return Evaluate( This.lAllowEdit )
Endif
EndProc
Wenn Sie nun die Klasse auf ihr Form packen, können Sie lAllowEdit auf einen Ausdruck wie "goApp.UserManager.HasEditRight()" setzen. Sollten Sie in einem anderen Projekt eine anderes Benutzerverwaltung verwenden, so müssen Sie nur die Eigenschaft lAllowEdit ändern, beispielsweise in "goApp.UserName == 'Admin'", wenn nur der Administrator dieses Feld ändern soll. Es ist weder eine Änderung an der Textbox notwendig, noch müssen Sie ihre Benutzterverwaltung anpassen, um alle Eigenschaften zu aktualisieren. Beide Komponenten arbeiten unabhängig voneinander und werden über solche umgeleiteten Eigenschaften miteinander verknüpft.
Selbst in der Klasse brauchen Sie sich nicht darum kümmern, daß die Eigenschaft eventuell umgelenkt werden kann. Sie greifen einfach mit This.lAllowEdit auf die Eigenschaft zu und erhalten einen logischen Wert zurück, selbst wenn in der Eigenschaft einer von obigen Strings stehen sollte. In den Sessions von Manfred Rätzmann und Sebastian Flucke zum Komponentenbau finden Sie dazu weitere Information, auch wenn dort die Umleitung noch über SetProp() und Getprop()-Methoden realsiert wird.
Das Formular hat Top, Left, Width and Height Eigenschaften, mit denen Sie die Position des Formualres auf dem Bildschirm bestimmen wollen. Was aber, wenn Sie aber nicht die Höhe eines Formulares angeben, sonden dem Formular sagen möchten, Du gehst von 100 bis 350. Sprich, wäre eine Bottom bzw. Right Eigenschaft nicht praktisch? Zugegebenermaßen, dieses Beispiel ist recht weit dahergeholt, da Bottom nichts anderes als Top+Height ist. Aber es gibt viele Fälle, wo Sie berechente Eigenschaften verwenden können. Bleiben wir aber der Einfachheit halber bei der Bottom Eigenschaft. Eine solche Eigenschaft können Sie leicht wie folgt definieren:
Procedure Bottom_Access
Return This.Top+This.Left
Procedure Bottom_Assign
LParameter tnBottom
This.Height = This.Bottom This.Top
EndProc
Schon können Sie die Bottom Eigenschaft wie Left und Height verwenden. Wenn Sie sie abfragen, erhalten Sie die untere Position des Formulares zurück. Wenn Sie sie setzen, wird das Formular entsprechend in der Größe angepaßt. Das läßt sich natürlich auch sehr viel komplexer machen. So können Sie etwa eine Eigenschaft Adresse definieren, in der Sie eine beliebige Adresse eingeben können. Wenn Sie diese Eigenschaft setzen, wird automatisch Straße, Ort, Postleitzahl, etc. herausgefiltert und in die entsprechenden Eigenschaften geschrieben. So läßt sich die Adresseingabe von Outlook etwa simulieren.
Wenn Sie einen Container mit zahlreichen Textboxen haben und bei diesem Container die Eigenschaft Enabled auf .F. setzen, sind alle im Container enthaltenen Textboxen ebenfalls deaktiviert. Der Anwender kann sie nicht aktivieren, nichts eingeben. Leider sieht es nicht so aus, denn die Enabled-Eigenschaft der Textboxen ist weiterhin .T. und der Hintergrund der Textbox bleibt unverändert. Mittels einer einfachen Assign-Methode für die Eigenschaft Enabled können Sie, sobald ein Container deaktiviert wird, ebenfalls alle darin enthaltenen Objekte deaktivieren.
Wenn Sie das in alle ihre Basisklassen einbauen, reicht ein einfache Objekt.Enabled = .F. Befehl aus, um alle Objekte zu deaktivieren, ganz gleich, wie tief sie in der Objektheirarchie stecken mögen. Denn ändert der Container die Enabled-Eigenschaft eines anderen Containers, sorgt der dafür, daß auch alle in diesem enthaltenen Objekte deaktiviert werden, und immer so weiter. Der dafür notwendige Code ist
Procedure Enabled_Assign
LParameter tlEnabled
Local loControl
This.Enabled = m.tlEnabled
For Each loControl in This.Controls
loControl.Enabled = m.tlEnabled
Endfor
EndProc
Mehrfachvererbung bedeutet, daß eine Klasse nicht von einer Elternklasse abgeleitet wird, sondern von zwei oder gar noch mehr Klassen. Wenn das Bedürfnis nach Mehrfachvererbung im normalen Klassendesign auftaucht, deutet dies meist auf ein Designproblem hin, insbesondere auf die exzessive Nutzung der Vererbung. Aber es gibt einen Fall in Visual FoxPro, bei dem eine Art Mehrfachvererbung sinnvoll und wünschenswert ist, die Ableitung von OLE Servern.
Wenn Sie in Ihren Applikationen OLE Server wie etwa WinWord verwenden möchten, so haben Sie dazu zwei Möglichkeiten. Entweder Sie verwenden Word direkt und müssen eine ganze Menge wiederkehrenden Code schreiben, oder sie entwickeln ein Objekt, daß den Zugriff auf Word kapselt. In der Praxis wird sich letztere Variante meist bewähren, da sie die höhere Wiederverwendbarkeit ermöglicht und zudem eine Anpassung an andere Word-Versionen ermöglicht.. Allerdings stellt sich bei ihrer praktischen Umsetzung oft ein Problem. Wieviel der Funktionalität wollen Sie kapseln? Wenn Sie alle Funktionen, die Word bietet, als Methoden duplizieren wollen, müssen Sie lange schreiben und bei jeder neuen Funktionalität in Word ihre Klassen anpassen.
Aus dieser Not wird oft eine Mischlösung verwendet. Die Word-Klasse enthält eine Referenz oWord auf Word und bietet zahlreiche Funktionen an, die komplexere Abläufe zusammenfassen und ihrerseits wieder auf oWord zurückgreifen. Diese Methoden rufen Sie direkt auf. Benötigen Sie aber andere Funktionen, so rufen Sie diese über die oWord-
Referenz auf:
loWordWrapper.Methode()
loWordWrapper.oWord.WordMethode()
Der Vorteil liegt auf der Hand, die WordWrapper-Klasse ist sehr viel kompakter und schneller entwickelt. Auch die Wartung vereinfacht sich, da sie nicht alle Eventualitäten berücksichtigen möchten. Was aber passiert, wenn Sie eine Funktion, die Sie bislang über die oWord-Referenz verwendet haben, nun plötzlich in der Klasse abbilden möchten? Es bleibt Ihnen nichts anderes übrig, als alle Programme durchzusehen und den Methodenaufruf anzupassen.
Da stellt sich natürlich die Frage, ob es nicht praktischer wäre, wenn sich der WordWrapper für Sie als ein Objekt darstellt, das alle Methoden von Word und weitere bietet, also eine Quasi-Mehrfachvererbung? Sie rufen die Methode auf und das Objekt entscheidet selbständig, ob sich die gewünschte Methode oder Eigenschaft in dem Visual FoxPro-Objekt oder in Word befindet. Genau das ist über THIS_Access möglich. Sie erhalten als Parameter die gewünschte Methode und prüfen über PEMSTATUS(), ob sich diese im Visual FoxPro-Objekt befindet. Wenn ja, dann liefern Sie THIS zurück und Visual FoxPro greift ganz normal auf die Objektmethoden zurück. Wenn aber nicht, so nehmen Sie an, es handelt sich um eine Word-Funktion, die ihr Objekt gar nicht zu kennen braucht, und liefern als Objektreferenz THIS.oWord zurück. Visual FoxPro greift dann vollkommen transparent auf WinWord zu.
Sollte Word um neue Funktionen erweitert werden, braucht Sie dan nicht zu kümmern, da THIS_Access alle unbekannten Anforderungen einfach an Word weiterreicht. Ohne daß es irgendeiner Änderung in Ihrer WordWrapper-Klasse benötigt, stehen Ihnen alle Funktionen von Word zur Verfügung. Und wenn Sie eine Methode von Word ableiten möchten, zum Beispiel weil die neue WinWord-Version andere Parameter erfordert? Dann legen Sie diese Methode in Ihrer WordWrapper-Klasse an. Ohne daß Sie ihre bisher geschriebenen Programme anpassen müssen, wird plötzlich auf die neue Methode zugegriffen werden, nicht mehr auf Word direkt. Das sieht dann wie folgt aus:
Define Class WordWrapper as Line
oWord = NULL
Procedure Init
This.oWord = CreateObject("Word.Application")
EndProc
Procedure New
Local loDocument
loDocument = This.oWord.Documents.Add
Return loDocument
Procedure This_Access
LParameter tcMember
If PemStatus( This, m.tcMember, 5 )
Return This
Else
Return This.oWord
Endif
EndProc
Enddefine
Wenn Sie nun ein neues Dokument anlegen wollen, rufen Sie einfach die New() Methode auf:
loNewDoc = loWordWrapper.New()
Aber auf die gleiche Weise können Sie nun auch auf das gerade aktive Document zugreifen, obwohl es keine Eigenschaft von WordWrapper ist:
loActiveDoc = loWordWrapper.ActiveDocument
Wenn Sie später diese Eigenschaft modifizieren möchten, können Sie problemlos in WordWrapper eine Eigenschaft ActiveDocument anlegen, und so sogar Assign- und Access-Methoden für Word-Eigenschaften definieren. So ist beispielsweise ActiveDocument schreibgeschützt. Wie wäre es, wenn Sie durch Zuweisung eines Document-Objektes an ActiveDocument das aktive Dokument wechseln können? Dazu braucht es nur eine Assign-Methode, die die entsprechenden Word-Funktionen aufruft. Oder Sie wollen anstelle eines OLE-Fehlers lieber .NULL. zurückbekommen, wenn kein Dokument aktiv ist. Dafür reicht eine Access-Methode für ActiveDocument, die prüft, ob es in Word ein aktives Dokument gibt und entweder .NULL. oder die Referenz auf das aktive Dokument zurückliefert.
Um zu erfahren, welche Quellcodezeilen Visual FoxPro ausführt, können Sie das Coverage Tool benutzten, das sich über SET COVERAGE einschalten läßt. Aber was machen Sie mit Variablen, genauer gesagt, mit Eigenschaften? Sicherlich haben Sie auch schon erlebt, daß während des Testens eine Variable plötzlich einen unsinningen Werte hatte und Sie sich fragten, wann wurde diese Variable denn geändert, welchen Wert hatte sie zuvor?
Assign-Methoden sind hier der Schlüssel. In einer Assign-Methode können sie jede Änderung protokollieren, zum Beispiel über DEBUGOUT, aber auch, indem Sie sie in eine Datei oder eine Tabelle schreiben. Sie könne nicht nur den alten und den neuen Wert festhalten, auch den Zeitpunkt und aus welcher Methode die Änderung erfolgt. Diese liefert Ihnen PROGRAM(PROGRAM(-1)-1) zurück. PROGRAM(-1) ist ebenfalls ein neues Feature von Visual FoxPro 6.0 und gibt Ihnen den aktuellen Prozedurlevel zurück.
Objektarrays sind Arrays, die Referenzen auf andere Objekte enthalten. _SCREEN.Forms ist beispielsweise ein solches Array, oft auch als Collection bezeichnet. Solche Arrays machen viele Aufgaben einfacher. So können Sie beispielsweise alle Rechnungen eines Kunden als Objekte in einem Array verwalten. Eine dertige konsequente Objektorientierung vergrößert die Wiederverwendbarkeit ihres Codes und macht im allgemeinen das Entwickeln leichter.
Allerdings ist das Erzeugen von Objekten in Visual FoxPro immer noch sehr zeitaufwendig. Große Objektarrays benötigen sehr viel zeit zum Laden, was die Applikation langsamer macht und was letztendlich zur Schlußfolgerung führt, das Objektorientierung doch nicht das Wahre sei. Mittels Access-Methoden ist es Ihnen aber möglich, diesen Vorgang drastisch zu beschleunigen, indem Sie einen einfachen Trick anwenden. Warum sollen Objekte erzeugt werden, noch bevor sie benötigt werden? Mit einer Access-Methode bekommen Sie mit, wann ihr Programm das Objekt benötigt. Dort können Sie prüfen, ob das gewünschte Objekt bereits existiert und wenn nicht, es erzeugen. Vereinfacht führt das zu folgendem Code:
Procedure aObjects_Access
LParameter tnIndex
If VarType( This.aObjects[m.tnIndex] ) # "O"
This.aObjects[m.tnIndex] = NewObject( "<Klasse>" )
Endif
Return This.aObjects[m.tnIndex]
Wenn Sie das nun mit dem weiter oben angesprochenen Array kombinieren, daß ihre Kundendaten in einem Objektarray verwaltet, können Sie sehr schnell ihre Kunden in einem Array verwalten, sofern Sie nicht mehr als 65000 Kunden haben. Als Index verwenden Sie einfach die Datensatznummer. Dadurch können Sie, wenn ein Name oder eine Kundenummer übergeben wurde, diesen mit SEEK suchen und das entsprechende Objekt zurückliefern. Existiert es noch nicht, so laden Sie den Datensatz als Objekt in den Speicher und geben das eben erstellte Objekt zurück.
Der Vorteil ist, daß der Zugriff mit fast der gleichen Geschwindigkeit, wie in FoxPro erfolgt, schließlich wird die FoxPro-Engine zur Suche des Datensatzes genutzt. Die Erzeugung des Arrays geschieht in Bruchteilen von Sekunden, da nur ein leeres Arary benötigt wird, nicht ein Array mit allen Objekten. Der erste Zugriff auf die Daten verzögert sich etwas, da das Objekt erzeugt werden muß, aber jeder weitere Zugriff erfolgt mit hoher Geschwindigkeit. Sofern Sie nicht alle Arrayelemente nacheinander ansprechen wollen, haben Sie die gewohnt hohe Geschwindigkeit mit voller Objektorientierung kombiniert. In der Session von Manfred Rätzmann können Sie mehr über objektorientierte Datenzugriffe erfahren.
Sie können dieses Konzept sogar noch verfeinern und sogenannte RecordSets implementieren. Das sind "Schnappschüsse" einer Tabelle, also die objektorientierte Version eines Cursors. Übergeben Sie einem solchen RecordSet-Objekt einen beliebigen SELECT-Befehl, der nur die Primärschlüssel der Datensätze ermittelt und in dem Array ablegt. Wenn immer die Anforderung nach einem Objekt kommt, prüfen Sie, ob das Arrayelement einen Primärschlüssel oder ein Objekt enthält. Wenn es ein Primärschlüssel ist, laden Sie nur diesen Datensatz in den Speicher.
Im obigen Abschnitt haben Sie erfahren, wie Sie RecordSets implementieren, also Arrays von Datenobjekten. Es wurde aber immer nur gesagt, laden Sie den Datensatz als Objekt in den Speicher. Aber wie? Die einfachste Form sind sicherlich SCATTER NAME Objekte, die allerdings den Nachteil haben, daß sie keine Methoden enthalten können. Auch keine Assign- und Access-Methoden. Nicht nur deswegen sind sie für die weitere Betrachtung unwesentlich.
Was aber sind Datenobjekte? Dies sind Objekte, die die Daten eines oder mehrerer verknüpfter Datensätze als Eigenschaften bereitstellen. Sie können diese Eigenschaften direkt als Controlsource einer Textbox und anderer Steuerelemente verwenden und so den eigentlichen Datenzugriff von der Oberfläche trennen. Dies macht es sehr viel einfacher, später die Oberfläche beispielsweise auf das Internet zu portieren. Selbst dann aber müssen Sie nicht auf die Leistungsfähigkeit von Visual FoxPro verzichten, denn natürlich kann ein solches Datenobjekt auch eine COM-Objekt sein.
Bereits bisher waren Datenobjekte möglich, aber Assign- und Access-Methoden machen Sie wesentlich attraktiver und einfacher zu benutzen. So können Sie bereits bei der Zuweisung überprüfen, ob die Daten stimmig sind. Und Sie können solche Eigenschaften bei jeder Änderung sofort in die Tabelle zurückschreiben. Nicht nur das, sie können auch Konvertierungen vornehmen. Das folgende Beispiel implementiert beispielsweise ein Datenobjekt, daß nur ein Datumsfeld enthält. An die aufrufenden Programme wird das Datum allerdings als String geliefert. Neben normalen Datumswerten kann diese Klasse auch die Text "Heute" und "Morgen" interpretieren. Wenn Sie ein solches Objekt an eine Textbox binden, kann der Anwender ganz normal ein Datum eingeben, das sofort in der Tabelle gespeichert wird. Er kann wie bei Outlook aber auch "Morgen" eingeben, gespeichert wird dann das morgige Datum.
Define Class xDataObject as Line
dDate = {}
Procedure dDate_Assign
LParameter tcDate
Local lcDate
lcDate = Upper(Alltrim(m.tcDate))
Do Case
Case m.lcDate == "HEUTE"
Replace Field with Date()
Case m.lcDate == "MORGEN"
Replace Field with Date()+1
Otherwise
Replace Field with CtoD(m.tcDate)
Endcase
This.dDate = Field
Endproc
Procedure dDate_Access
Return DtoC(This.dDate)
Enddefine
Was wäre wenn Visual FoxPro weniger Bugs hätte? Die im folgenden beschriebenen Anwendungsbeispiele für Assign- und Access-Methoden funktionieren im aktuellen Release von Visual FoxPro 6.0 (Build 8167) nicht. Sobald ein Service Pack verfügbar ist, lohnt es sich aber, diese auszuprobieren.
In Visual FoxPro sind Arrays auf 65000 Elemente beschränkt. Was aber, wenn Sie mehr haben wollen. Normalerweise sollte es kein Problem sein, für dieses Array eine Access-Methode zu definieren, die den gewünschten Index entgegennimmt. Intern werden die Daten auf mehrere Arrays verteilt. Wenn die Access-Methode auf einen Index größer 65000 trifft, würde sie einfach 65000 von diesem Wert abziehen und das als Index im zweiten versteckten Array nutzen und den Wert zurückliefern. Es sollte funktionieren, denn schließlich führt Visual FoxPro bei Arrays mit Access-Methoden keine Validierung der Indexparameter durch, nur so ist es ja möglich, daß Sie als Array-Index auch Zeichenketten verwenden können.
Weit gefehlt! Die Überprüfung findet zwar bei Stringindexwerten nicht statt, wohl aber bei numerischen Werten. Jeder Index über 65000 und unter 0 führt zu einer Fehlermeldung noch bevor die Access-Methode aufgerufen wird. Insbesondere der untere Wert ist bemerkenswert, er ist nicht etwa 1, wie bei normalen Arrays. Nein auch 0 ist ein gültiger Index, den ihre Access-Methode aber gesondert behandeln müßte. Alle anderen Werte sind unzulässig. De Fakto stehen Ihnen zwar nun größere Arrays zur Verfügung, aber der Unterscheid zwischen 65000 und 65001 Elementen ist wahrlich nicht berauschend.
Es hätte so schön sein können! Auch Visual FoxPro 6.0 bietet Ihnen nicht die Möglichkeit, Seiten abzuleiten. Weiterhin kann nur der Pageframe zur Ableitung verwendet werden. Leider bleibt damit auch die Problematik bestehen, daß Sie beim Aktivieren einer Seite unbedingt Refresh ausführen müssen, damit bei einem eventuellen Datensatzwechsel die korrekten Daten angezeigt werden.
Eine einfache Lösung hätte ActivePage uns geliefert. Diese Eigenschaft enthält immer die Nummer der gerade angezeigten Seite. Wenn also der Anwender eine andere Seite aktviert, so wird dieser Wert geändert. Und wenn sich Eigenschaften ändern, können wir sie mit einer Assign-Methode abfangen. Leider ist die Implementation nur unvollständig gelungen. Eine Methode ActivePage_Assign wird zwar aufgerufen, wenn Sie die Tastatur benutzen, um eine andere Seite zu aktivieren, zum Beispiel über die Cursortasten oder über einen Hotkey. Leider aber wird Sie nicht aufgerufen, wenn der Anwender auf eine andere Seite mit der Maus klickt. Der Wert verändert sich ohne unser Wissen.
Sobald Microsoft diesen Bug gefixt hat, können Sie in der ActivePage_Assign-Methode den Code unterbringen, den Sie normalerweise auf jeder Seite im Activate und im Deactivate-Event schreiben würden.
Erzeugen Sie ein neues Formular und legen Sie für dieses Formular eine Eigenschaft oTest an, für die Sie eine Assign-Methode definieren. Sie können den von Visual FoxPro standardmäßig erzeugten Code in der Assign-Methode so belassen. Fügen Sie dann eine Schaltfläche hinzu und in deren Click-Event schreiben Sie
Thisform.oTest = This
Nichts außergewöhnliches, sie weisen lediglich einer Formulareigenschaft eine Objektreferenz zu. Nun starten Sie das Formular und klicken Sie den Button drei mal. Plötzlich ist er verschwunden! Hier haben wir erstmals das Problem, daß ein Bug in Visual FoxPro nicht hängende Referenzen, sondern fehlende Referenzen erzeugt. Eine nähere Untersuchung dieses Bugs zeigte, daß Visual FoxPro den internen Referenzzähler zu stark dekrementiert. Dies ist der Zähler, der angibt, wieviele Referenzen es auf ein Objekt gibt. Wird er auf 0 gesetzt, so wird das Objekt freigegeben. Jede Zuweisung eine Objektreferenz an eine Variable erhöht diesen Zähler, jedes setzen einer solchen Variable auf einen anderen Wert, verringert den Referenzzähler.
Dieser Bug wurde erst Ende Oktober gefunden, die im folgenden vorgeschlagenen Workarounds sind also mit entsprechender Vorsicht zu genießen. Insbesondere muß ihre Verwendung bei neuen Versionen überprüft werden. Prinzipiell gibt es zwei Möglichkeiten, diesen Bug zu umgehen. Zum einen können Sie jede Zuweisung eines Objektes and eine Eigenschaft mit Assign-Methode wie folgt umschreiben:
Thisform.oTest = .NULL.
Thisform.oTest = This
Der Nachteil dieser Methode ist zum einen, daß sie die Kapselung bricht. Sie müssen bei der Zuweisung den Bug berücksichtigen, innerhalb der Assign-Methode führte der Code zu einem Absturz von Visual FoxPro führen. Ein zweiter Nachteil ist, daß die Assign-Methode NULL-Werte akzeptieren muß. Daher habe ich eine Bibliothek geschrieben (OBJREF.FLL), mit der Sie den internen Referenzzähler direkt manipulieren können. Sie finden diese Bibliothek auf der Konferenz-CD. Damit läßt sich folgender Code in der Assign-Methode verwenden, der ebenfalls den Bug zu beheben scheint:
LParameter toRef
If IsNull(m.toRef) and VarType(This.oTest) == "O"
DecrementRef( This.oTest )
Endif
This.oTest = m.toRef
If VarType(This.oTest) == "O"
IncrementRef(This.oTest)
Endif