Session D-NET

Netzwerk-Programmierung
in VFP

 

Jürgen Wondzinski
ProLib Software GmbH

 


EINLEITUNG

Zu behaupten, daß VFP´s Netzwerkfähigkeiten wesentlich erweitert wurden, wäre eine grobe Fehleinschätzung. Microsoft hat dermaßen viele Änderungen und Wünsche eingebaut, daß nichts mehr so funktioniert, wie man es gewohnt war. Dies aber im positiven Sinne, denn mit VFP haben wir nun sehr ausgefeilte Möglichkeiten, auf jedwede Multiuser-Problematik einzugehen.

Die eigentliche Grundlage für die wesentlich verbesserten Techniken sind das Data Buffering und die Private Datasessions.

Data Buffering führt dazu, daß wir uns nicht mehr mit SCATTER/GATHER rumärgern müssen und auch LOCK()/UNLOCK können wir im Normalfall vergessen. Vieles, was unter 2.x noch sehr schwierig oder garnicht zu realisieren war, ist nun in wenigen Minuten erledigt, ohne auch nur ein RLOCK zu verwenden. Vorbei sind die Zeiten, wo man mehrere Arrays angelegt hat, und diese ständig auf Änderungen überprüft hat, ganze fünf Funktionen wickeln alles ab.

Durch Private Datasessions können wir ohne Bedenken beliebige Masken parallel laufen lassen, ohne daß sich die Datenbestände überschreiben. Man kann sich die P.D.S (nein, nicht was Sie jetzt wieder denken...) als Abkapselung der Daten innerhalb einer Form vorstellen. Jede Form ist dadurch wie ein eigenständiger Netzwerkanwender zu sehen. Sie können ohne Probleme dieselbe Form mehrmals starten, und parallel verschiedene oder auch den selben Datensatz bearbeiten.

 


Anmerkung:

In diesem Artikel verwende ich hauptsächlich die englischen Ausdrücke; zum Einen, weil die ganze Thematik sehr eng mit den sowieso englischen Programmierbefehlen verbunden ist, zum Anderen, weil die Eindeutschungen nicht wesentlich verständlicher sind....


Data Buffering

Dies ist ein absolut neues Feature in VFP, das uns die meiste Arbeit mit Zwischenspeichern von Datensätzen abnimmt. Zwar kennen alte Hasen ähnliches schon als "AUTOMEM" von ARAGO , aber VFP´s Implementation geht noch wesentlich weiter. Zwar ist die grundlegende Methodik noch immer dieselbe, aber sie müssen sich um wesentlich weniger kümmern. Vieles was wir bisher per Hand codierten, wird nun von der Engine intern wesentlich sicherer erledigt.

Die alte SCATTER/GATHER Editierung bzw das Umkopieren auf Speichervariablen macht unter VFP keinen Sinn mehr, da VFP bei einer Variable keinen Bezug zu dem darunterliegenden Datenbankfeld mehr herstellen kann, und folglich alle unsere so schön definierten VALID und DEFAULT Funktionen nutzlos blieben. Folglich bleibt uns nur das direkte Feldeditieren, und hier kommt nun der Buffer zum Zuge.

Wenn wir hier von einem Buffer reden, dann ist damit ein spezieller Datenbereich in VFP gemeint. Wenn wir eine Tabelle öffnen, so liest VFP die ersten paar Datensätze in seinen internen Buffer ein. Wenn danach mit einem GET/READ bzw einer Textbox der Datensatz direkt bearbeitet wird, wurden die Änderungen früher sofort an die Platte weitergegeben. Nun haben wir auch noch die Möglichkeit, den Transfer zwischen Platte und Buffer zu kontrollieren, indem wir den Buffermodus aktivieren. Wir können uns den Buffermodus auch als internes SCATTER vorstellen, das trotzdem wie ein Feld angesprochen wird. Falls wir das Tablebuffering aktiviert haben, hält VFP für jeden geänderten Record ein SCATTER, und selbst Neuanlagen und Löschungen werden im Speicher gehalten, bis Sie den Befehl zum wegschreiben geben.

Eingestellt wird der Buffermodus über die Funktion CursorSetProp(). "Cursor" bedeutet in diesem Zusammenhang CURrent Set Of Records, also der aktuell gewählte Datenbestand.

Bei allen Buffermodi muß übrigens MULTILOCK auf ON gesetzt werden, ansonsten erhalten Sie eine Fehlermeldung.

Der Buffermodus ist für alle eingebundenen Tabellen als auch für freie und 2.x kompatible Tabellen anwendbar.

Es gibt verschiedene Möglichkeiten zu puffern: Auf Satzebene (Row) und auf Tabellenebene (Table). Bei beiden Metoden gibt es zusätzlich noch zwei verschiedene Methoden zu sperren: pessimistisch (bei Beginn der Eingabe) oder optimistisch (beim Speichern).

Sobald dieser Modus aktiviert ist, werden alle Änderungen im Speicher gehalten. Mittels TABLEUPDATE() oder TABLEREVERT() können die Änderungen gespeichert bzw. verworfen werden. Es kann immer nur ein Satz gepuffert werden, sobald Sie den Satzzeiger bewegen und Änderungen anstehen, wird automatisch ein TABLEUPDATE erzwungen. Falls dabei Fehler auftreten, bleibt der Satzzeiger auf dem aktuellen Satz stehen.

Bei dieser Sperrmethode wird automatisch eine Satzsperre ausgelöst, sobald ein Feld verändert wird. Falls der Sperrversuch fehlschlägt wird der Buffer zurückgesetzt, ansonsten werden die Änderungen im Speicher durchgeführt. Die Satzsperre wird dabei solange gehalten bis eine der folgenden Aktionen stattfindet:

Wenn beim Verändern des Satzzeigers das Updaten mißlingt, bleibt der Buffer erhalten und der Satz weiterhin gesperrt. Ähnlich ist es beim Schliessen der Datei: Wenn die implizit aufgerufene Funktion TABLEUPDATE einen Fehler meldet, kann die Datei nicht geschlossen werden.

Aufgrund der pessimistischen Sperre sollte ein TableUpdate eigentlich nie einen Fehler durch Änderungen anderer Anwender bringen. Es können aber Fehler entstehen durch:

Mit aktivierter optimistischer Sperrung wird der Datensatz erst dann gesperrt, wenn VFP versucht, den Buffer wegzuschreiben. Dabei wird von VFP automatisch überprüft, ob sich der Datensatz im Netz zwischenzeitlich geändert hat. Falls nicht, wird der Lock aufgehoben. Der Schreibvorgang wird durch die selben Ereignisse wie oben ausgelöst. Zusätzlich zu den obigen Fehlermöglichkeiten kommen hier aber noch hinzu:

Wenn der Tabellen-Buffermodus aktiviert ist, werden alle Änderungen an allen Datensätzen zwischengepuffert. Auch (APPEND BLANK oder INSERT). Da mehrere Datensätze gepuffert werden, führt ein Satzwechsel NICHT zu einem automatischen TableUpdate. Der Begriff Table-Buffering ist etwas irreführend. Natürlich wird nicht die gesamte Tabelle im Buffer gehalten, sondern nur die geänderten Datensätze. Falls Sie in diesem Modus unbedingt ein REPLACE ALL auf eine Millionen Satz Datei machen wollen, sollten Sie vorher für genügend freien Plattenplatz in ihrem Temp-Verzeichnis sorgen.

Wenn Sie im Table-Buffer Modus neue Datensätze erzeugen, erhalten diese NEGATIVE Satznummern. D.h. Erste Neuanlage hat RECNO() = -1, der nächste dann -2 usw. Auf diese Weise können Sie sehr schnell Neue von existierenden Datensätzen unterscheiden. Erst wenn die Datensätze erfolgreich weggeschrieben wurden, erhalten sie ihre entgültige Satznummer.

Hier werden alle Datensätze automatisch gesperrt, sobald an ihnen Änderungen vorgenommen werden. Die Sperren bleiben auch beim Satzwechsel bestehen.

Die Satzsperren werden unter den gleichen Bedingungen wie beim Satzweisen Sperren aufgehoben.

Implizite TABLEUPDATE Aufrufe beinhalten immer den Parameter "Alle Sätze schreiben". Wenn hierbei ein Satz nicht erfolgreich weggeschrieben werden kann, bleiben alle Satzsperren weiterhin aktiv.

In diesem Modus werden Satzsperren erst dann gesetzt, wenn VFP alle Änderungen wegschreiben will. Falls dabei ein Satz nicht aktualisiert werden kann, wird der gesamte Updatevorgang abgebrochen, wobei alle Recordlocks stehen bleiben. Die Datensätze sind dann meist schon teilweise geschrieben und teilweise ungeschrieben. Hier kann mit einer Transaktion das Verfahren verbessert werden (siehe auch Tips am Ende).

Feld- und Satz Regeln werden wie gewohnt abgearbeitet, auch wenn der Buffermodus aktiv ist. Einzige Ausnahme ist der APPEND BLANK Befehl: Hier werden die Abprüfungen erst beim tatsächlichen physikalischen Anhängen an die Tabelle durchgeführt. Dies ist übrigens die einzige Möglichkeit, einen Datensatz mit leeren Feldern zu erstellen, bei dem die Regeln nur "nichtleere" Felder erwarten.

Die Trigger verhalten sich ebenfalls wie gewohnt: Sie werden gestartet, wenn der eigentliche Update/Delete/Insert Fall auftritt. Dies ist beim Table-Buffering erst dann, wenn alle geänderten Sätze tatsächlich weggeschrieben werden, und nicht wenn der APPEND BLANK Befehl abgesetzt wird.

Genauso werden die Primary und Candidate Key Überprüfungen erst vorgenommen, wenn die Sätze weggeschrieben werden.

Die Reihenfolge der Überprüfungen läuft folgendermaßen ab:

Aufgrund der Tatsachen, daß man mit Table-Buffering das Row-Buffering hervorragend nachahmen kann, und des einfacheren Verhaltens beim APPEND BLANK ist es empfehlenswert, nur mit TableBuffering zu arbeiten. Auf diese Weise ist man für alle Eventualitäten gewappnet. Auch ist es auf Dauer einfacher, überall nach dem selben Modus zu arbeiten.

Der einzige Bereich, wo Table-Buffering nicht so empfehlenswert ist, sind direkte Eingaben in BROWSE-Felder. Nachdem man das in Programmen aber sowieso nicht macht, ist´s also kein Problem.

Nachdem die beiden Verfahren in VFP prinzipiell dasselbe machen, was alle xBase Programmierer seit Jahren machen, bleibt die alte Glaubensfrage weiterhin bestehen. Beide Methoden haben ihre Vor- und Nachteile, daher müssen Sie selbst entscheiden, welches Verfahren für Ihren Fall das geeignetere ist. Nachfolgend ein paar Überlegungen dazu:

Das Wegschreiben gepufferter Daten erfolgt mit der TABLEUPDATE Funktion, die im Prinzip dem GATHER MEMVAR entspricht. Das Verwerfen des Buffers wird mit TABLEREVERT erzielt, was einem erneuten SCATTER MEMVAR entsprechen würde.

Der erste Parameter ist nur im Tablebuffer Modus wichtig. Hier kann entweder nur der aktuelle Satz oder alle geändereten Sätze weggeschrieben werden.

Der zweite Parameter ist nur im optimistischen Modus wichtig: Wenn gesetzt, werden alle Änderungen brutal weggeschrieben, ohne Rücksicht auf eventuell schon erfolgte andere Änderungen.

Die Funktion gibt einen logischen Wert zurück, der über den Erfolg Auskunft gibt. Probleme können bei folgendem Scenario auftreten: Tablebuffering aktiviert, mehrere Sätze geändert und ein anderer als der erste Satz kann nicht erfolgreich weggeschrieben werden. Unter diesem Umstand wird der komplette Updatevorgang abgebrochen, mit einigen schon geschriebenen Datensätzen und einigen ungeschriebenen. Abhilfe schafft hier die Verwendung einer Transaktion (siehe Beispiel dort).

Falls man auf gepufferte lokale Updateable Views schreibt, die sich wiederum auf gepufferte lokale Tabellen beziehen, muß man in einem Zwei-Schritt Verfahren vorgehen:

Daraus folgt, daß man in so einem Fall nur die View puffern sollte....

Diese Funktion gibt einen numerischen Wert zurück, der aussagt, wieviele Datensätze erfolgreich zurückgenommen werden konnten. Im Normalfall kann man davon ausgehen, daß hier immer erfolgreich abgeschlossen werden kann. Bei allen meinen Tests konnte ich nie eine Diskrepanz feststellen. Einziger Verwendungszweck des Rückgabewertes wäre

Das Verwenden von TableRevert auf ungebufferten Tabellen führt zu einem Fehler. Den Buffermodus kann man über GETCURSORPROP("Buffering") abprüfen.

Trotz aller noch so ausgefeilten Buffermethoden bleibt es uns nicht erspart, sich über Zugriffskonflikte Gedanken zu machen denn diese Problematik wird immer bleiben. Aber im Gegensatz zu früher haben wir nun ausgezeichnete Möglichkeiten, hier einzugreifen.

Diese Funktion gibt uns alle Informationen über den Zustand eines kompletten Datensatzes oder eines Feldes: geändert, gelöscht, neu angelegt, wieder entlöscht usw. Durch Angabe der Feldnummer oder -1 für alle Felder erhalten wir eine String mit Statusflags zurück.

Die erste Stelle gibt Auskunft über den Löschzustand, ab Stelle 2 sind die Feldflags. Eine 1 bedeutet "Feld unverändert", eine 2 sagt aus, daß dieses Feld verändert wurde. Über die Konstruktion

kann man sehr einfach feststellen, ob man überhaupt Daten wegschreiben muß.

Es gibt einen Problemfall, der Ihnen graue Haare bringen kann: GetFldState (und auch das Eingabefeld selbst) werden nur dann geupdated, wenn man das Feld verlässt. Die Funktion ist nicht dafür gedacht, Ihnen Feldinformationen zu geben, während Sie noch in dem betreffenden Feld sind. Erst durch das Verlassen mittels TAB, ENTER, Pfeiltasten bzw Click auf ein anderes Objekt wird der geänderte Status bemerkt. Folgendes Scenario funkioniert aber leider nicht:

Daraus folgt, daß eine Änderung in einem Feld nicht bemerkt wird, wenn der Anwender dieses Feld nicht verlässt und die Speichern-Option über Menü/Toolbar angewählt wird. Es gibt verschiedene Methoden, mit diesem Problem umzugehen, aber die einfachste ist:

Ein eigenes Flag mitführen, das über den InteractiveChange Event gesetzt wird. Sobald ein Feld verändert wird, tritt der InteractiveChange Event auf, dieser setzt das Property und fertig. Nun können alle anderen Programmodule dieses Property abfragen und auf den Editmodus entsprechend reagieren.

Diese nette Funktion ist nur im TableBuffer Modus sinnvoll. Sie gibt die Satznummer des nächsten, geänderten Records zurück. Dabei werden geänderte Felder erkannt, Neuanlagen und Sätze an denen sich der Löschstatus geändert hat.

Hier tritt das selbe Problem auf, wie bei GetFldState. Ist auch logisch, denn woher soll GetNextModified den Satz finden, wenn GetFldState ihn schon nicht erkennt...

Dieses Phänomen ist schon etwas diffiziler. Die Funktion gehört zu den wenigen Funktionen, die beim Beenden zwar wieder auf dem Datensatz stehen, mit dem sie angefangen hatten, zwischenzeitlich aber den Satzzeiger bewegen. (Eine andere ist z.B. KEYMATCH()). Dies kann nun zu Problemen führen, wenn dabei das Satz-Valid oder ein Feld-Valid einen Fehler meldet. Dadurch bekommen Sie Fehlermeldungen an Stellen, wo sie garnicht damit rechnen.

Mit CURVAL() erhalten Sie für das angegebene Feld den aktuellen Wert draussen im Netz. Passend dazu gibt OLDVAL() den Wert zurück, bevor Sie mit den Änderungen anfingen. Also analog zu den ARRAY-SCATTER Methoden aus den alten Tagen. Durch einen einfachen Vergleich der beiden Werte können Sie feststellen, ob sich ein Datensatz zwischenzeitlich geändert hat.

OlDVAL() ist auch sehr gut geeignet um eine feldbezogene UNDO Funktionalität zu implementieren.

Bei pessimistschem Locking haben CURVAL und OLDVAL immer den selben Wert, da es ja durch die Sperre nicht möglich ist, daß sich der Satz draußen ändert.

Diese drei einfachen Befehle:

sind alles, um eine enorme Datensicherheit mit minimalen Aufwand zu erreichen. Durch Einschliessen Ihres Updatecodes mit einer Transaktion können sie kiloweise Code einsparen, der sowieso nie sicher laufen würde... z.B können Sie:

Transaktionen können bis zu fünfmal ineinander gestaffelt sein; die Funktion TXNLEVEL() sagt Ihnen, wie tief sie sich schon verstrickt haben.

Ab dem Befehl BEGIN TRAN werden alle Änderungen an allen Tabellen mitprotokolliert, sofern diese ein Teil eines DBC´s sind. D.h. Freie Tabellen können nicht geschützt werden!


Data-Entry forms

In VFP werden Eingabeobjekte (Textbox, Editfeld usw) nun an die Daten "gebunden". Dies wird mit dem Property CONTROLSOURCE erreicht. D.h. Die Felder schreiben nicht mehr auf Speichervariablen, sondern direkt auf die Tabelle. Was früher ein No-No war, soll nun richtig sein? Nun, solange wir den Buffermodus einschalten, ist alles ok.

Dieses Property einer Form kann auf Pessimistisch oder Optimistisch gesetzt werden. Sie können es auch auf None stellen, aber dann müssen Sie selbst im Code die Einstellungen setzen. Wenn Sie das Property aber setzen, dann führt VFP ein implizites CURSORSETPROP() aus, wenn es die Tabellen im DataEnvironment öffnet. Normalerweise werden Tabellen und Views, die als RecordSource für Grids dienen, mit TabelBuffereing geöffnet; alle anderen Tabllen werden mit RowBuffering geöffnet und Views prinzipiell mit Optimistic RowBuffering.

Alle Forms haben ein DataEnvironment (Pseudo-) Objekt, egal ob Sie es verwenden oder nicht. (Datenumgebung). Alle Tabellen/Views die Sie hier angeben, werden automatisch geöffnet, wenn die Form anstartet. Dabei werden auch die Relationen gesetzt, die Sie aber auch löschen bzw selbst setzen können.

Diese CURSOR-"Objekte" im DataEnvironment haben ebenfalls Properties, Events und Methoden, von denen die meisten am besten auf ihren Defaultwerten gelassen sollten. Eigentlich sind diese Einstellungen nicht anderes als eine OOP-Version der alten SET... Befehle. So ist das ORDER Property nichts anderes als eine Anweisung zu einem SET ORDER TO ... Befehl.

Falls Sie also das DataEnvironment nicht verwenden wollen, passiert nicht viel. Sie können alles auch durch gewohnten xBase-Code ebenfalls erreichen. Da das DataEnvirnonment Pfadangaben zu den verwendeten DBCs und Tabellen hinterlegt, macht es unter Umständen Sinn, die Tabellen per Hand zu öffnen.

Dies ist das interessanteste Property des ganzen Cursor-Objektes. Damit kann für jede einzelne Tabelle ein SETCURSORPROP("Buffering") durchgeführt werden. Somit sind sie nicht abhängig von der BufferMode Einstellung der Form, sondern können gezielt einzelne Tabellen anders buffern.

Z.B. Könnten Sie die Rechnungskopf Tabelle pessimistisch sperren, die Rechnungszeilen aber optimistisch verwenden.

Wenn Sie das DataEnvirnoment nicht verwenden, dann müssen Sie die Befehle wiederum per Hand codieren, dies geschieht normalerweise im FORM-LOAD.

Damit können Sie einstellen, ob VFP einen abgekapselten Datenbereich für diese Form anlegt. Ich empfehle Ihnen, hier immer Option 2 (Private Datasession) einzustellen, wenn Sie Datengebundene Objekte in der Form haben.

Durch diese einfache Einstellung wird erreicht, was in FP2.x nur mit Unmengen von Code extrem aufwendig zu erreichen war. Das unabhängige Laufenlassen meherer Forms nebeneinander ist nun nur noch ein simples Aktivieren. VFP kümmert sich um Satzzeiger, Index Tags, Relationen, Arbeitsbereiche, usw für jede einzelne Form.

VFP erreicht dies durch ein internes USE AGAIN auf die verwendeten Tabellen/Views. Dies ist sogar ziemlich ähnlich wie man es früher in FPW gemacht hätte, mit dem Unterschied, daß VFP sogar die eindeutigen ALIASe usw für unsere Tabellen verwaltet. Sie erstellen einmal eine Maske und rufen Sie auf, sooft sie wollen. Sie haben sicher schon gelesen, daß VFP nun 32767 Selectebenen hat, aber durch das implizite USE AGAIN sind das 32767 Selectebenen pro Datasession!

DataSessions werden im Umgebungsfenster ebenfalls dargestellt, dazu bekam das Umgebungsfenster quasi eine dritte Dimension dazu. In der DropDown Listbox oben können Sie nun aus den verschiedenen Datasessions auswählen, und eine nach vorne bringen. Datasession 1 ist die sog. Default-Datasession, welche im Befehlsfenster aktiv ist (Dies ist identisch zu FPW, wo wir ja nur eine Datasession hatten). Jeder Aufruf einer Form mit Datasession=2 führt zu einer neuen Private Datasession, die dabei fortlaufend nummeriert werden. Dieser Integerwert ist im DataSessionID Property der Form wiederzufinden.

Obwohl es daher machbar wäre, die DataSessionId gleich zu setzen mit der einer anderen Form: tun sie´s nicht! Sobald sie den Wert ändern, verlieren Sie den Link der Eingabefelder zu den Daten, und das kann auch nicht mehr aktiviert werden.

Sobald Sie eine Form mit Private DataSession aktivieren, werden alle Tabellenaktionen (Öffnen/Schliessen Order usw) unabhängig vom Rest dr Applikation durchgeführt. Dieser Code wird normalerweise im LOAD bzw UNLOAD Eevent eingebaut.

Sogar SQL generierte Cursors und CREATE CURSOR basierende Dateien werden sauber den jeweiligen Sessions zugeordnet. Sogar das mehrfache Aufrufen derselben Form führt dazu, daß VFP die internen Namen dieser Cursordateien sauber verwaltet, sie müssen sich also keine großartigen Namensconventionen ausdenken. Wo Sie aber drandenken müssen, ist das manuelle Schliessen ihrer Dateien am Ende.

Dadurch, daß Datasessions eine abgekapselte Welt für sich darstellen, werden viele SET Einstellungen wieder auf den VFP-internen Defaultwert zurückgesetzt, egal wie sie diese im Hauptprogramm eingestellt hatten. Folgende Einstellungen müssen daher in jeder neuen Datasession erneut eingestellt werden:

Dies geht am besten im Form LOAD oder im DataEnvironment BeforeOpenTable Event. Wenn Sie eine Standard-Klasse für Ihre Forms verwenden, können sie das dort einmal ins LOAD reinsetzen und vergessen. (Übrigens ist in der Online-Hilfe auch noch SET PATH mit aufgeführt, dies stimmt aber nicht).


Tips und Tricks

Bei aktiviertem TableBuffering werden mit TABLEUPDATE die Änderungen mehrerer Datensätze weggeschrieben. Wenn einer der Updates nicht greift (Meist ein Triggerfehler), wird der TABLEUPDATE sofort abgebrochen, und lässt sie mit teilweise geschriebenen und ungeschriebenen Updates zurück. Hier können Sie mit einer Transaktion das ganze 100% wasserfest machen:

Wie schon weiter vorne festgestellt, ist GETFLDSTATE nur dann wirklich 100% sicher, wenn sie während der Eingabe sicherstellen können, daß die Form nicht verlassen werden kann via Toolbar, Menü , oder ON KEY LABEL. Da dies eher unwahrscheinlich ist, sollten Sie in ihrer Form eine Property definieren, die den momentanen Eingabezustand definiert.

Dieses Flag kann im InteractiveChange Event der Textbox gesetzt werden:

Später beim Blättern oder Verlassen der Maske brauchen Sie nur dieses Flag abfragen. Dies ist ausserdem sehr nützlich, wenn Sie diverse Toolbar Buttons, Menüoptionen usw enablen/disablen wollen.

In der FPW Welt hatten wir das READ TIMEOUT, um schlafende Anwender aus der Datensatzdauersperre zu werfen. Unetr VFP gibt es kein Property mit diesem Effekt. Dafür aber haben wir das Timerobjekt, das man ganz einfach mit auf die Form setzt.

Wichtig dabei: TimerEvents treten nicht auf, wenn das Menü aktiviert ist, eine MESSAGEBOX Funktion aufgerufen ist, oder wenn die Form im Verschiebemodus ist (Gedrückte Maustaste auf Titelleiste, bzw Systemmenü angewählt). Unter diesen Umständen werden alle zwischenzeitlichen TimerEvents gepuffert, sodaß sie nach Freigabe alle hintereinander loslegen.

Wenn Sie eine Form mit aktivierter PrivateDatasession beenden, schliest VFP automatisch die im Dataenvironment angegebenen Dateien. (Wenn Sie das DE nicht verwenden, steht ihr dafür zuständiger Code im UNLOAD Event). Falls dabei aber ein Fehler auftritt(zB TriggerFehler), wird die Form zwar geschlossen, aber die DataSession mitsamt den offenen Dateien bleibt bestehen. Dasselbe werden Sie sicherlich schon beim Enticklen erlebt haben: Ein CLOSE ALL CLEAR ALL bringt immer wieder ne Fehlermeldung, deren Ursache noch nicht weggeschriebene Änderungen sind. Sie können nicht mal VFP beenden, solange dieses Problem nicht behoben ist....

Um da rauszukommen, müssen sie diese DataSession aktivieren (SET DATA TO ..), und dann mit TABLEREVERT() aufräumen. Am einfachsten geht das über das SET (bzw Umgebungsfenster), wo sie die übriggebliebenen DataSessions raussuchen können. Komfortabler geht´s mit folgendem Code: