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: 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 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:

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