Unkonventionelle Lösungen – programmiert mit dBASE
von Lutz Conrad

In meinem Vortrag möchte ich - aus der Sicht des Programmierers - einige Möglichkeiten zum Einsatz von Drag&Drop Verfahren am Beispiel einer Einsatzplanung demonstrieren. Im letzten Jahr erreichten mich viele Anfragen zur Programmierung von Treeviews. Darauf werde ich im zweiten Teil meines Vortrags eingehen. Die Gestaltung der Formulare kann anhand konkreter Quellcode-Beispiele nachvollzogen werden. Diese sowie ein paar nützliche Werkzeuge sind wie immer auf der Konferenz-CD enthalten.

Einsatzplanung

In diesem Screenshot ist der Dialog zur Planung des täglichen Einsatzes eines Pflegedienstes dargestellt.
Der Dialog enthält folgende Bereiche:

- Datenauswahl
- Beschriftung der Spaltenköpfe
- Spaltenköpfe
- Zeitschiene
- Planungsdaten

Bei der Änderung des Tagesdatums wird die vorhandene Einsatzplanung gelesen bzw. neu angelegt. Bei einer Neuanlage werden die Standardfestlegungen für die Mitarbeiter, die vorgesehenen Fahrzeuge, die Touren und Einsätze automatisch berücksichtigt. Noch nicht verplante Einsätze werden in einem frei schwebenden Fenster angezeigt.

Die Zuordnung von nicht geplanten Einsätzen zu den Touren erfolgt per Drag&Drop. Bei gehaltener STRG-Taste ist der gewünschte Eintrag vom Formular mit den nicht geplanten Einsätzen einfach auf die gewünschte Position des Einsatzplanes zu ziehen. Im Zielformular wird ein neues Objekt angelegt. Für die Positionierung werden die Koordinaten des onDrop-Ereignisses ausgewertet. Das Zielobjekt verwendet als Basisklasse die Klasse TEXT, so dass deren Eigenschaft ‘text’ zur Darstellung weiterer Informationen für den konkreten Einsatz genutzt werden kann. Die Höhe des Objektes wird dabei anhand der Einsatzdauer errechnet und somit eine maßstabgetreue Darstellung erreicht.

Das Ziel ist nun bekannt – wie ist der Weg dorthin?

Alle Beispielformulare sind in der MDI-Anwendung DBKON04LC enthalten, für die erforderlichen Tabellendaten wird der BDE-Alias `DBKON04LC` angelegt.

Zunächst gilt es, die Bereiche des Formulars zu definieren, in den die Daten dargestellt werden. Alle Bereiche sind immer sichtbar, unabhängig von der Größe des Formulars, der Anzahl der Touren, der Dauer der Einsätze. Die Breite der Spalten ist konstant, ebenso die Höhe einer Zeiteinheit.

Die Anzahl der dargestellten Spalten und der sichtbare Bereich der Zeitschiene ist dabei abhängig von der Größe des Formulars. Da aber auch das Formular selbst in seinen Abmessungen auf die Bildschirmgröße beschränkt ist, macht es sich für die Navigation erforderlich, die Objekte so zu verschieben, dass der gewünschte Ausschnitt dargestellt wird.

Nach dem Anlegen des Formulars und Setzen der Basisklasse `MyBaseForm` wird zunächst die Eigenschaft `metric` auf `6=Pixel` gesetzt. Dies ist wichtig für die spätere Berechnung von Koordinaten und die Auswertung der Maus-Events. Außerdem wird damit eine weitgehende Unabhängigkeit von den Anzeigeeinstellungen erreicht.

Die Containerobjekte werden entsprechend Ihrer Zweckbestimmung umbenannt. Mit dem Hinzufügen der vertikalen und horizontalen Scrollbars ist die Gestaltung des Layouts abgeschlossen.

Beispiel: Einsatz1.wfm

Als nächstes müssen wir uns um mögliche Größenveränderungen des Formulars kümmern. Hier werden die Größen der Containerobjekte anhand der Anzahl der vorgesehenen Touren (Spalten) und des darzustellenden Zeitraums berechnet. Das Formular arbeitet immer noch ohne Datenbindung, daher wird die Anzahl der Spalten mit 10, die Spaltenbreite mit 100 Pixeln, die Höhe einer Stundeneinheit mit 120 Pixeln (=2 Pixel/min), der darzustellende Zeitrahmen mit 6 Stunden (= 360min) und der früheste Einsatzbeginn mit 06:00 Uhr angenommen.

Für eine vollständige Darstellung aller Objekte müsste das Formular demnach folgende Größe (in Pixeln) aufweisen:

| Breite: | 90 | Container für Zeitschiene |  | Höhe: | 35 | Container für Datenauswahl |
|  | 1000 | 10*100 für Spalten |  |  | 170 | Container für Spaltenköpfe |
|  |  |  |  |  | 720 | 6*120 Container für Zeitschiene |
| Gesamt | 1090 |  |  |  | 925 |  |

Schon mit diesen Vorgaben wird deutlich, dass die erforderlichen Abmessungen der Container die maximale Größe des Formulars meist überschreiten. Für einen vollständigen Tag mit 30 Spalten würde dann eine Bildschirmauflösung von 3090*3085 Pixeln benötigt. Dies dürfte selbst bei optimistischer Beurteilung der technischen Entwicklung auf absehbare Zeit Zukunftsmusik sein.

Es bleibt also nur die Möglichkeit, den sichtbaren Bereich der Container so zu positionieren, dass der gewünschte Ausschnitt dargestellt wird. Hierzu dienen die Scrollbars, die rechts und unten angeordnet sind. Die Größe aller Containerobjekte wird anhand der vorgesehenen Zeitdauer und Spalten einmalig berechnet, die Position und Größe der Scrollbars muß dagegen bei jeder Größenveränderung des Formulars angepasst werden. Damit nicht genug, auch der Wertebereich der Scrollbars ist beim jedem size-Event des Formulars neu festzulegen.

Beispiel: Einsatz2.wfm

Nun werden die `onChange`-Events der Scrollbars eingehängt mit denen die Positionierung des dargestellten Bereichs gesteuert wird. Beim Navigieren fällt auf dass weder die Spaltenköpfe noch die Zeitschiene immer sichtbar bleiben. Dies liegt daran, dass bisher die Z-Order der Containerobjekte nicht beachtet wurde.

Beispiel: Einsatz3.wfm

Werden diese Objekte in eine zweckmäßige Reihenfolge gebracht, klappt es auch mit der Navigation.

Als erstes Objekt muß dasjenige definiert werden, welches sowohl horizontal als auch vertikal positioniert wird (`C_Inhalt`). Anschließend sind die Objekte zu definieren, deren Position hinsichtlich einer Richtung geändert werden muß (`C_Zeit` und `C_Kopf`). Zum Abschluß werden die statischen Objekte definiert (`C_Auswahl` und `C_Text`).

Nun fehlt noch die Beschriftung. Hierzu dienen die Methoden `BuildSpalten()` und `Buildzeit()`. Abschließend werden in der Methode `DrawLines()` noch die Hilfslinien für die vertikale und horizontale Positionierung eingefügt. Nach dem Setzen der Eigenschaft 'text' der Überblendungsobjekte auf einen leeren String, werden diese Bereiche korrekt dargestellt.

Beispiel: Einsatz4.wfm

Der Container zur Datenauswahl wird mit einem Objekt zur Eingabe des Planungsdatums ergänzt. Zum schnellen Blättern dienen die links und rechts angeordneten Schaltflächen. Die Event-Handler für die Datenauswahl sind bereits angelegt. Die Funktionen zum Aufbau der Objekte im Spaltenkopf wurden entsprechend ergänzt. Diese funktionieren bereits dahingehend, dass die korrekte Spaltenposition in einer `MsgBox()` angezeigt wird. Die Funktion `Init()` initialisiert das ausgewählte Datum mit dem Tagesdatum+1.`FillContainer()` ist noch leer, hier wird jetzt die Datenbindung eingefügt.

Beispiel: Einsatz5.wfm
Beispiel: Einsatz6.wfm

Im `Init()` werden zunächst einige Hilfsvariablen definiert.

| form.AnzahlSpalten | Anzahl der vorhandenen Spalten
| form.AnzObjekte | Anzahl der aktiven ‚Einsatzobjekte’ (gleichzeitig Anzahl geplanter Einsätze)
| form.MinBeginn | frühester Einsatzbeginn
| form.MaxEnde | spätestes Einsatzende
| form.AnzOffen | Anzahl ungeplanter Einsätze
| `` |

Das Array `form.KopfArray` dient zur Aufnahme von Informationen bezüglich des Einsatzes und der Tour.

Die Methode `BuildSpalten()` wurde jetzt so geändert, daß die Tabelle `Touren.DBF` sequentiell durchlaufen und für jeden Datensatz eine Spalte generiert wird. Zur Bestimmung der Höhe des Containers `C_Inhalt` wird der früheste Beginn und das späteste Ende aller Touren ermittelt. Die erforderliche Breite des Containers ergibt sich aus Spaltenanzahl * Spaltenbreite.

Die Textobjekte des Spaltenkopfes für Tour, Mitarbeiter, Fahrzeug und kalkulatorische Größen werden anhand der Informationen aus der Datenbank aufgebaut. Zur Positionierung der Objekte wird die Methode `move()` verwendet. Diese arbeitet schneller als das aufeinanderfolgende Zuweisen von der Eigenschaften `top`,`left`,`width` und `height`.

Die Farbgebung der Spaltenköpfe erfolgt anhand der Zuordnung zu einem Einsatz (morgens, mittags ...)

Nach dem Aufbau des Gerüsts erfolgt das Überprüfen und Aktualisieren der vorhandenen Daten. Die Datei `Vorgabe.DBF` enthält die bereits vordefinierten Standards, die Tabelle `Tourendetails.DBF` die konkreten Festlegungen für ein bestimmtes Datum.

Für das Anlegen der Objekte wurde die Methode `CreateObject()` programmiert. Diese erstellt ein Textobjekt innerhalb des Containers `C_Inhalt` anhand der Informationen aus `Tourendetails.DBF`. Als Name für das Textobjekt wird hier die Bezeichnung OBJ und die laufende Objektnummer verwendet. Um den späteren Datenzugriff zu reduzieren, werden dem Textobjekt weitere benutzerdefinierte Eigenschaften zugewiesen. Die Werte dieser Eigenschaften stammen aus den Tabellen.

Die Eigenschaft `dragEffect=2` ermöglicht das spätere Verschieben der Objekte.

Doch bevor der Container befüllt werden kann, gilt es, eventuell vorhandene Objekte zu löschen. Hierzu sind die Namen aller relevanten Objekte zu bilden, hieraus die Objektreferenzen zu erzeugen und die Methode `release()` auszuführen.

Nun wird die Tabelle `Vorgaben.DBF` sequentiell durchlaufen und geprüft, ob für den betreffenden Datensatz eine Entsprechung für das aktuelle Datum in `Tourendetail.DBF` angelegt wurde. Ist dies nicht der Fall, z.B. bei Datumswechsel, wird der Datensatz angelegt. Datensätze in Tourendetails, die hinsichtlich ihrer zeitlichen Festlegungen oder Zuordnung zu einer Tour verändert wurden, bleiben demnach unberücksichtigt.

Für Datensätze aus Tourendetails, für die bereits eine automatische oder manuelle Festlegung einer Tour vorgenommen wurde, werden mittels `CreateObject()` Textobjekte angelegt.

Beispiel: Einsatz7.wfm

Nun wird das Verschieben der Textobjekte per Drag&Drop implementiert. Hierzu muß die Eigenschaft `allowDrop` des Containers `C_Inhalt` auf `true` stehen. Um Fehlbedienungen zu vermeiden, wurde das Verschieben dahingehend beschränkt, daß diese Funktionalität nur bei gedrückter `UMSCHALT-Taste` möglich ist. Der Aufruf der Funktion wird beim Betätigen der linken Maustaste eingeleitet. Weil das jeweils aktuelle Objekt verschoben wird, stehen dessen Eigenschaften über seine Referenz `this` unmittelbar zur Verfügung.

In diesem Beispiel werden lediglich Verschiebungen von einem Einsatz in einen anderen abgewiesen, für den praktischen Einsatz dürfte diese Prüfung nicht ausreichen. Hier gilt es, z.B. Überschneidungen in geeigneter Weise abzufangen.

Die neuen Werte für Tour und Beginn werden anhand der Mauskoordinaten des Zieles ermittelt, umgerechnet und in die Datenbank übertragen.

Beispiel: Einsatz8.wfm

Abschließend wird die Möglichkeit zur Zuordnung nicht geplanter Einsätze eingebunden. Hierzu ist ein separates Formular vorgesehen, in dem die relevanten Daten dargestellt werden. Auf `Tourendetails.DBF` wirkt eine Schlüsselbeschränkung, die alle Datensätze ausblendet, in den das Feld Tour nicht leer ist. Das Planen eines Einsatzes erfolgt per Drag&Drop auf das Basisformular, wobei die Tourzuordnung anhand der Spaltenposition erfolgt. Das Objekt wird anhand der Mauspositionen des `onDrop`-Events und der vorgesehenen Dauer des Einsatzes angelegt und die entsprechenden Änderungen in die Datenbank übertragen. Durch das Festlegen einer konkreten Tour fällt der Datensatz aus der Schlüsselbeschränkung heraus und das Formular mit offenen Einsätzen wird automatisch aktualisiert.

Einsatz von Baumstrukturen

Neben der traditionellen Darstellung von Datenbeständen in Grid-Objekten bietet die Verwendung von Baumstrukturen den Vorteil einer sichtbaren Hierarchie. Zuordnungen können so über mehrere Ebenen dargestellt werden. Dem Anwender ist die Verfahrensweise zur Datenauswahl meist vom Windows-Explorer bekannt.

Die Realisierung statischer Baumstrukturen ist einfach zu lösen. Nach dem Anordnen eines Treeview-Objektes werden die einzelnen Treeitems definiert. Diese Trees werden gewöhnlich eine oder zwei Ebenen enthalten.

Beispiel 1 ist einer Software für ein Reservierungssystem entnommen. Hier erfolgt die Datenauswahl für den Ausdruck der Elemente eines Veranstaltungsvertrages. Der Benutzer wählt in der ersten Ebene aus, ob die ausgedruckten Belege für den Kunden oder die hausinterne Verwendung bestimmt sind. Die zweite Ebene enthält die zu druckenden Komponenten.

Der Aufbau des Baumes könnte so mittels der Methoden `streamChildren()` in eine Textdatei geschrieben bzw. mittels `loadChildren()` von dort gelesen werden. Wenn man sich die Zieldatei anschaut, wird man feststellen, dass diese lediglich den Quellcode zum Anlegen der Objekte enthält. Leider ist das nur die halbe Wahrheit, denn es werden nur die dBASE-Standardeigenschaften gestreamt, benutzerdefinierte Eigenschaften bleiben unbeachtet. Es wird immer die gesamte Struktur geschrieben, für einzelne Treeitems stehen die Methoden nicht zur Verfügung. Für die Programmierung von Baumstrukturen mit Datenbindung sind diese Methoden ungeeignet, denn der Treeaufbau muß hier dynamisch anhand der konkreten Daten zur Laufzeit erfolgen.

Zum Aufbau einer Baumstruktur mit Datenbindung kann der Formulardesigners lediglich zum Einfügen des Treeview-Objektes verwendet werden, die einzelnen Treeitems sind im Programm aufzubauen. Dies kann selbstverständlich nicht nach dem Öffnen des Formulars geschehen, sondern sollte bereits vor dem `onOpen`-Event erledigt sein. Aus Gründen der Übersichtlichkeit ist es zweckmäßig hierfür eine separate Methode z.B.`BuildTree()` vorzusehen.

Hier wird die Datenbasis sequentiell durchlaufen und die erforderlichen Treeitems angelegt. Dies kostet natürlich Zeit. Ist die voraussichtliche Anzahl von Objekten gering, kann man den Baum bereits vollständig aufbauen. In diesem Falle können auch gleich alle Treeitems sichtbar gemacht werden, indem man die Eigenschaft expanded der jeweils übergeordneten Objekte auf `true` setzt.

Sind jedoch sehr viele Objekte im Tree zu erwarten, ist eine andere Arbeitsweise zweckmäßiger. Es wird zunächst nur die erste Ebene aufgebaut und die Eigenschaft `expanded` bei allen Objekten dieser Ebene auf `false` belassen. Um zu erkennen, dass es auch in der zweiten Ebene noch Elemente gibt, wird auch diese ebenfalls vollständig aufgebaut. Das Vorhandensein von Elementen wird so automatisch mit einem + gekennzeichnet.

Die nachfolgende (zweite) Ebene wird beim Klick auf das + geöffnet. Im `canExpand`-Event werden aber vor dem Öffnen des Baumes noch die Items des dritten Ebene aufgebaut. Mit dieser Verfahrensweise (die übrigens ebenso vom Windows-Explorer angewandt wird) ergibt sich neben dem Tempogewinn noch der Vorteil, dass nur die tatsächlich benötigten Objekte aufgebaut werden. Wenn man bedenkt, dass jedes Treeitem-Objekt Ressourcen und Speicherplatz benötigt, ist es schon ein Unterschied, ob ein Baum mit drei Ebenen und jeweils 10 Einträgen einer Ebene sofort vollständig oder nur teilweise aufgebaut wird. Während beim vollständigen Aufbau 10+100+1000=1110 Einträge generiert werden müssen, sind es mit beim teilweisen Aufbau immerhin nur 10+100=110. Die Einträge der dritten Ebene werden ja erst bei Bedarf angelegt.

Vorsicht Falle! Meist wird es gewünscht, die Treeitems in alphabetischer Reihenfolge darzustellen. Die Methode `sortChildren()` wirkt aber erst, wenn das Formular bereits geöffnet ist. Beim Anlegen der Einträge der ersten Ebene (und weiterer, falls die übergeordnete Ebene expandiert ist) sollte daher die darzustellende Datenbasis alphabetisch sortiert (indexiert) vorliegen. Alternativ könnte `sortChildren()` im `onOpen`-Event des Formulars ausgeführt werden, würde dort aber zu unerwünschtem Flackern führen.

Man könnte meinen, zum Aufbau der Baumstruktur wäre nun bereits alles gesagt. Aber das Hauptproblem, bei dem viele ‘Neulinge’ scheitern kommt erst noch. Überläßt man dBASE den Aufbau des Trees, wird der Baum eine Vielzahl gleichnamiger Objekte enthalten. Diese unterscheiden sich zwar hinsichtlich ihrer Hierarchie innerhalb des Baumes, tragen in der Eigenschaft `name` den Wert `TREEITEM1` usw. Es ist somit nur sehr schwer nachvollziehbar, aus welcher Datenbindung der jeweilige Eintrag resultierte.

Wenn man sich aber schon um das Anlegen der Baustruktur kümmern muß, dann kann durch geschickte Namensgebung eine Referenz auf die Datenbasis abgebildet werden. Beim Anlegen eines Treeitems wird in den Namen des Objektes z.B. der Schlüssel des jeweiligen Datensatzes aufgenommen.

Im Formular `Tree2.WFM` wird der Aufbau einer Baumstruktur am Beispiel einer Adreßverwaltung. vollzogen. Die erste Ebene enthält die Firmen, die zweite die Abteilungen, die dritte schließlich die Ansprechpartner.

In diesem Beispiel heißen alle Einträge der ersten Ebene FIRxxxxx wobei xxxxx die Kennung der Firma referenziert, in der zweiten Ebene heißen alle Treeitems ABTyyyyy wobei mit yyyyy die Abteilung abgebildet wird, in der dritten ebene heißen die Einträge ASPzzzzz, wobei zzzzz die Kennung des Ansprechpartners darstellt.

Bei Auswahl eines Eintrags im Baum läßt sich neben der mit der Eigenschaft `level` referenzierten Hierarchieebene sofort ermitteln, um welche Art von Eintrag es sich handelt. Außerdem kann anhand des Namens sofort der Suchschlüssel ermittelt und auf den gewünschten Datensatz positioniert werden. Damit ist eine eineindeutige Datenbindung gewährleistet.

Zur Verdeutlichung der Namensgebung wird im Formular `Tree2a.WFM` für die Eigenschaft `text` der Treeitems der gleiche Wert wie für die Eigenschaft `name` verwendet.

In Abhängigkeit der mit der Eigenschaft `level` referenzierten aktuellen Ebene des ausgewählten Elements werden die drei Container auf der rechten Seite angezeigt oder verborgen. Die Funktionalität ist in der Methode `SelectItem()` enthalten.

Leider hat die Sache noch einen Haken. Bei der Auswahl eines Eintrags innerhalb eines Zweiges ist alles korrekt, wechselt man jedoch direkt von einem Ansprechpartner der Abteilung A auf einen Ansprechpartner der Abteilung B, werden im zweiten Container falsche Daten angezeigt. Es fehlt noch die Synchronisation mit den übergeordneten Datensatzbereichen.

Hierzu werden noch die Methoden `SelectFirma()` und `SelectAbteilung()` eingehängt und fertig ist das Formular zur Datenanzeige.

In `Tree3.WFM` wird das Formular nun um die Funktionalität zum Speichern und Verwerfen von Änderungen ergänzt. Zur Arbeitserleichterung wird ein kontextsensitives Popup-Menü zur Verfügung gestellt, welches sich – ‘windowstypisch’ - beim Klick mit der rechten Maustaste öffnet.

An die `onRightMouseDown`-Handler wird jeweils die Koordinate des Mausereignisses bezogen auf das jeweilig übergeordnete Objekt vermittelt. Damit sich das Popup an einer zweckmäßigen Position öffnet, ist es erforderlich, die Koordinaten auf eine Position bezüglich des Formulars umzurechnen. Daher wird zu den Parametern `col` und `row` der `left` bzw.`top` Wert des aktuellen Objektes addiert. Damit würde die Mitte des Popups exakt an der Stelle des Mausereignisses stehen.

In der Praxis dürfte es sich als zweckmäßig erweisen, diese Werte etwas zu korrigieren. Im Beispiel wird das Popup 1 Zeichen unterhalb und 10 Zeichen rechts vom Mausereignis geöffnet.

Es enthalt zunächst nur die minimalen Optionen zur Datenbearbeitung.

Bei der Programmierung der Event-Handler des Popups ist zu beachten, daß mögliche Aktionen gegebenenfalls auf alle beteiligten Datensatzbereiche anzuwenden sind. Daher wurden für die einzelnen Events keine Codeblöcke sondern explizite Methoden programmiert.

Am einfachsten läßt sich das Verwerfen von Datenänderungen programmieren. Hier ist lediglich die Methode `abandon()` der drei Datensatzbereiche aufzurufen. Änderungen an der Baumstruktur sind nicht zu berücksichtigen.

Beim Speichern von Änderungen müssen jedoch die Texte innerhalb der Baumstruktur überprüft und bei Bedarf neu gesetzt werden. Hierzu wird innerhalb der `onSave`-Handler der aus den Daten resultierende Text mit der Eigenschaft text des referenzierten Treeitems verglichen. Um ein Flackern des Formulars zu vermeiden, wird der Text nur bei Erfordernis neu zugewiesen. Die `onSave`-Handler werden explizit über das Popup oder aber implizit vor dem Wechsel des aktuellen Baumeintrags im `canChange`-Handler des Treeview ausgelöst.

Im Formular `Tree4.WFM` wird nun auch das Löschen von Datensätzen ermöglicht. Zunächst sollte stets eine Sicherheitsabfrage eingebaut werden, die unbeabsichtigtes Löschen verhindert. Die `canDelete`-Handler sind ein geeigneter Ort für diesen Dialog. Wird dieser nicht explizit mit ‘Ja’ beantwortet, geben die `canDelete`-Handler den Wert `false` zurück und das Löschen wird nicht ausgeführt.

Folgende Änderungen kommen beim Löschen von Datensätzen in Betracht:

- das Löschen des aktuellen Datensatzes
- Löschen aller nachgeordneten Datensätze
- Löschen aller übergeordneten Datensätze, sofern es sich u den letzten Eintrag des Zweiges handelt
- Auswählen eines neuen Eintrags innerhalb der Baumstuktur
- Löschen des bisherigen Eintrags der Baumstruktur

Das Löschen des aktuellen Datensatzes erfolgt automatisch, wenn `canDelete()` den Wert true zurückgibt. Alle anderen Aktionen erfolgen im `onDelete()`. Obwohl der Datensatz schon nicht mehr verfügbar ist, können über- und untergeordnete Referenzen anhand der Namensgebung in der Baumstruktur ermittelt werden.

Nachdem bereits die Bestätigung zum Löschen erteilt wurde, sollten alle zugeordneten Datensätze ohne weitere Rückfrage entfernt werden. Beim Löschen zugeordneter Daten werden jedoch auch deren `canDelete()` und `onDelete`-Handler ausgelöst. Abhilfe schafft hier das Flag `form.SuppressRowsetHandler`. Dieses wird im `Init()` auf den Standardwert `false` gesetzt. Der `onDelete`-Handler desjenigen Datensatzbereiches, welcher die Aktion zum Löschen auslöst, setzt das Flag auf den Wert `true`. Damit werden alle anderen `canDelete`- und `onDelete`-Handler ohne weitere Aktion übergangen.

Zum Löschen der Datensätze in untergeordneten Bereichen wird eine Schlüsselbeschränkung gesetzt und alle relevanten Datensätze entfernt. Zum Löschen übergeordneter Einträge kann der Schlüssel anhand der Baumhierarchie ermittelt werden. In diesem Beispiel sind dies immer die rechten 5 Zeichen des Objektnamens.

Abschließend muß noch der Baum aktualisiert werden. Achtung! Hierbei bitte handwerklich korrekt vorgehen, sonst ist der Absturz gewiß.

Zuerst gilt es das künftige aktive Objekt zu ermitteln. Danach ist dieses auszuwählen. Erst dann, also zum Schluß, kann das zu löschende Objekt wirklich entfernt werden.

Mit `Tree5.WFM` wird die Mini-Adressverwaltung noch um die Möglichkeit des Hinzufügens neuer Datensätze erweitert. Das Beispiel ist so konzipiert, daß beim Anlegen eines neuen Datensatzes auch in allen nachgeordneten Datensatzbereichen ein Datensatz angelegt wird. Ein neuer Datensatz erhält zunächst die Bezeichnung <Neue Firma>, <Neue Abteilung> bzw. <Neuer Ansprechpartner>. Gleiche Bezeichnungen sind möglich, für die Zuordnung wird ja ein Schlüsselfeld verwendet.

Auch hier gilt es, die Verschachtelung der Datenzugriffsobjekte zu beachten. Um Fehler bei der Abarbeitung der Rowset-Events auszuschließen wird `form.AppendMode` eingeführt. Diese Variable wird im `Init()` mit `0` initialisiert. Wird das Hinzufügen von `Firma.rowset` ausgelöst, erhält die Variable den Wert 1, beim Hinzufügen aus dem Abteilung.rowset den Wert 2 und beim Hinzufügen aus `Ansprech.rowset` den Wert 3. Im `onSave`-Handler des aufrufenden Datensatzbereichs wird die Variable nach Abschluß aller Aktionen wieder auf 0 zurückgesetzt.

Folgende Aktionen sind beim Hinzufügen von Daten durchzuführen:

- Anhängen und Initialisieren eines Datensatzes
- Vergeben einer eindeutigen Schlüsselnummer
- Aktionen in nachgeordneten Datensatzbereichen
- Einfügen neuer Elemente in die Baumstruktur
- Sortieren der Einträge in der Baumstruktur
- Auswählen des neuen Eintrags

Das Initialisieren des Datensatz erfolgt im `onAppend`-Handler. Hier wird die Standardbezeichnung und die Schlüsselnummer des übergeordneten Objekts gesetzt.

Zum Anlegen der eigenen eindeutigen Schlüsselnummer wird das `canSave`-Event der Datensatzbereiche (nicht das `onAppend`-Event) verwendet. Einzig und allein für diesen Zweck, wird die Tabelle in einer weiteren Query nochmals geöffnet. Als Index ist hier der Hauptschlüssel zu verwenden. Nachdem alle Prüfungen des Datensatzes erfolgreich waren, wird der Vergleichs-Datensatzbereich auf den letzten Datensatz positioniert und der nächstfolgende Schlüssel ermittelt und in das noch leere Feld geschrieben. Auf diese Weise wird erreicht, daß auch im Netzbetrieb keine doppelten Schlüssel vergeben werden. Man spart sich so allen Ärger mit Autoinc-Feldern.

In den `onSave`-Events wird zunächst der Name des referenzierten Elements der Baumstruktur ermittelt. Es ist noch nicht vorhanden, daher führt der Versuch, dieses einer Variablen zuzuweisen zu einem Fehler, der mittels eines `TRY/CATCH` Blocks abgefangen wird. Nun wird das neue `TREEITEM` angelegt und bei Bedarf weitere untergeordnete Daten hinzugefügt.

Nach dem Setzen der text-Eigenschaft und anschließender alphabetischer Sortierung ist der Anfügevorgang beendet. Als letzte Aktion ist `form.AppendMode` wieder auf den Standardwert `0` zu setzen.

Um die beigefügten Beispiele herunterzuladen, bitte hier klicken (360 Kb als Zip-Datei). |