Netzwerkfähige Hierarchien in FileMaker Pro

Ein Artikel über die Einbindung von hierarchischen Elementen in eine FileMaker Datenbank. Erschienen in der Ausgabe 2010/06 im FileMaker-Magazin<<. Nachfolgend der vollständige Artikel. Sie können den Artikel auch als PDF downloaden. Die Beispieldatei beziehen bitte vom Verlag.
 
Abbildung von Hierarchien auf der Benutzeroberfläche
Mal wieder hierarchische Listen
Hierarchisch strukturierten Daten begegnet man so oft, dass sie als solches schon gar nicht mehr wahrgenommen werden: Auf einer Website mit einer hierarchisch aufgebauten Navigationsstruktur, dem Inhaltsverzeichnis eines Buches, dem Stammbaum Ihrer Familie oder in dem Dateisystem Ihres Computers mit dessen Ordern, Unterordnern und Dateien.
Schon einige Male wurde hier im FileMaker-Magazin über hierarchische Strukturen berichtet. Sehr ans Herz legen möchte ich Ihnen einen Artikel aus der Ausgabe 05/2002[1]<<. Der Beitrag befasst sich mit der Darstellung von Bäumen, einer fundamentalen Datenstruktur in der Informatik[2]<<. Dazu aber später mehr.
Warum aber einen weiteren Artikel? Ganz einfach! Die Redaktion hat ihn angenommen.
Ich erhoffe mir mit diesem Artikel, Ihnen eine Ergänzung zu den bisher hier im Magazin erschienen, sehr guten Fachbeiträgen, liefern zu können. Ziel soll die Vorstellung einer Methode sein, hierarchisch strukturierten Daten auf der Benutzeroberfläche mit Hilfe der Listenansicht zu implementieren. Dabei gilt es zu berücksichtigen, dass die darzustellenden Daten in einem Client-Server-Betrieb lauffähig sein müssen, also mehrbenutzerfähig sind.
 
Rüstzeug
Eine hierarchische Strukturierung von Daten folgt einer Rangordnung, genauer: Die Daten stehen in einem Verhältnis von einer Über- und Unterordnung[3]<<. Dies sollten wir uns für den weiteren Verlauf merken.
Die zuvor erwähnten Bäume folgen dieser Ordnung. Grafisch werden solche Bäume häufig zweidimensional in Form von Knoten und Kanten gezeichnet. Letztere verbinden die Knoten untereinander und drücken zugleich das Verhältnis der Knoten zueinander aus. 
Abbildung 1
 
Abbildung 1 zeigt eine solche zweidimensionale Darstellung am Beispiel von Projekten (P), den zu erfüllenden Aufgaben (A) und den Mitarbeitern, die eine Aufgabe erledigen sollen (M). Wir werden im weiteren Verlauf bei diesem Beispiel bleiben. Es entstammt einer von mir zu entwickelnden Lösung zur Planung, Steuerung und Kontrolle von Projekten.
Das Wurzelelement wird durch den obersten Knoten „Projekte“ dargestellt. Die Knoten darunter sind allesamt Kinder des Wurzelknotens. Ein konkretes Projekt hat selbst ebenfalls Kinder (Aufgaben), eine Aufgabe hat auch wieder Kinder (Mitarbeiter, die eine Aufgabe erledigen sollen). Aus sicht der Kinder wird ihr übergeordneter Knoten als Elternknoten bezeichnet. Bei diesem Beispiel wird das Verhältnis von Über- und Unterordnung sehr schön deutlich.
Hierarchische Listen sind ein sehr gutes Hilfsmittel, um solche Datenstrukturen auf der Benutzerobfläche zu präsentieren. Dabei werden die Kinder um einen gleich bleibenden Betrag in Zeilen unter ihrem jeweiligen Elternknoten eingerückt. In Abbildung 2 sehen Sie eine einfache hierarchische Liste in FileMaker. In ihr können Sie die im zuvor gezeigten Baum abgebildete Datenstruktur wieder erkennen, zumindest teilweise.

 

Abbildung 2
 
In hierarchischen Listen ist es üblich, dass Sie die Kinder eines Elternknotens ein- bzw. ausblenden können. Bei dem Einblenden der Kinder wandern Sie den Baum in der Hierarchie eine Ebene tiefer bis zur nächsten Ebene. Das Ausblenden geht den gleichen Weg wieder zurück. In diesem Zusammenhang spricht man auch von einem Pfad, dem Sie folgen. Und je mehr Ebenen Sie nach unten wandern, desto länger wird dieser Pfad. Da wir aber, entgegen der Natur, den Baum von oben nach untern wandern, sprechen wir hier von Pfadtiefe.
Nehmen wir an, wir wollen nicht nur die auszuführenden Aufgaben zu einem Projekt sehen, sondern auch, wer von den Mitarbeitern diese Aufgabe erledigen soll. In diesem Fall würden wir bis zur Ebene 3 alle Kinder eines Projekts einblenden. Sie erkennen dass in der Abbildung 1 an den gestrichelten Kanten. Hier lautet der Pfad Projekte → P2 → A1 → M1.
Mit jeder Ebene, die Sie nach unten wandern, werden die jeweiligen Kinder gegenüber ihrem Elternknoten stets um eine weitere Einheit eingerückt. Insofern korrespondiert die Nummer der Ebene mit dem Betrag, um wie viele Einheiten eine Einrückung gegenüber der Wurzel zu erfolgen hat. Anders ausgedrückt: Je größer die Pfadtiefe, desto öfters müssen Sie eine Einrückung auf der Benutzeroberfläche vornehmen.
Je nach dem auf welcher Ebene Sie Ihren Focus legen, wird diese wiederum zur Wurzel. Man spricht dann von einem Unterbaum. Auf Grund der Komplexität dieser Thematik beschränken wir uns in diesem Artikel lediglich auf die Betrachtung von so einem Unterbaum, nämlich Projekt → Aufgabe mit einer Pfadtiefe von 1. Die Erarbeitung von Pfadtiefen größer 1 behalten wir uns für einen weiteren Artikel vor.
 
Datengrundlage
Nachfolgender Ausschnitt aus einem einfachen ERD[4]<< soll uns als Ausgangsbasis für die Umsetzung in FileMaker dienen (Abbildung 3). In der  Beispieldatei HiraLi.fp7 habe ich die zwei Tabellen PROJEKT mit PRJ und AUFGABE mit AUFG abgekürzt. Für die Relation „1 Projekt besteht aus N Aufgaben“ benötigen wir auf Grund ihrer Kardinalitäten 1:N lediglich ein Fremdschlüsselfeld _aufg_fk_prj_t in Tabelle AUFG
Abbildung 3
 
Die weiteren Datentabellen MITARBEITER (abgekürzt: MA) und AUFG_j_MA sind zunächst nicht von Belang. Sie werden uns in dem angekündigten, vertiefenden Artikel beschäftigen.
 
Funktionalität – Das Prinzip
Ich möchte Ihnen zunächst die grundlegenden Ideen vermitteln, die diese Methode der hierarchischen Listendarstellung verfolgt. An den Stellen, bei denen ich denke, dass diese einer besonderen Erklärung bedürfen, werde ich ins Detail gehen.
Eine Vorgabe für die hierarchische Liste ist die Lauffähigkeit in einem Netzwerk mit mehreren Anwendern. Dies bedeutet, dass etwa das Ein- und Ausblenden von Aufgaben nicht die Ansicht eines anderen Anwenders beeinflussen darf. Basis hierfür ist eine weitere Tabelle HIL, die für hierarchische Liste steht. Sie wird ausschließlich der Funktion wegen benötigt.
In der Tabelle HIL werden (bei Bedarf) maximal so viele Datensätze angelegt, wie wir benötigen, um alle Datensätze aus den Datentabellen darstellen zu können. In unserem Fall sind das also die Anzahl der Datensätze aus PRJ und AUFG zusammen.
Die Primärschlüssel der Datensätze, die auf dem Layout angezeigt werden, schreibe ich stets in das Feld hil_ui_all_keys_gt. Die einzelnen Schlüssel werden durch einen Zeilenumbruch getrennt. Ebenso verfahre ich mit den Ebenen eines Datensatzes, die zur Steuerung der Einrückung dienen. Sie werden in das Feld hil_ui_all_levels_gt geschrieben (siehe Abbildung4). 
Abbildung 4
 
Die Zeilennummer eines Wertes korrespondiert hierbei mit der Datensatznummer in der Listenansicht. Mit anderen Worten: Wenn Sie in der Listenansicht auf dem 7. Datensatz stehen, wird die Aufgabe mit dem Schlüssel aufg_5 auf Ebene 1 angezeigt. Sie erkennen das an der rot hervorgehobenen Zeile in der Abbildung. Die beiden Felder sind global gespeicherte Textfelder, was Sie an dem Suffix _gt[5]<< erkennen können. Sie können hierfür aber auch globale Variablen ($$-Variablen) verwenden.
Für jede Ebene wird ein Schlüsselfeld der Form _hil_fk_level_X_uct benötigt, wobei X gegen die Nummer der betrachteten Ebene auszutauschen ist. Das Suffix _uct bedeutet, dass dies ein nicht gespeichertes Formelfeld mit dem Ergebnistyp Text ist (unstored calculated text).
Die Formel berechnet die ID aus Feld hil_ui_all_keys_gt mit Hilfe der Funktion Get( RecordNumber )[6]<<. Die Formel liefert nur dann ein Ergebnis, wenn die betrachtete Ebene mit der Ebenennummer in Feld hil_ui_all_levels_gt übereinstimmt.
Über die Schlüsselfelder werden die zur Anzeige der Daten erforderlichen Beziehungen realisiert. Diese sind in unserem Fall „hil__prj_level_0“ sowie „hil__aufg_level_1“ (Abbildung 5).
 
Abbildung 5
 
Hierin liegt auch schon das Geheimnis. Eine Formel, die ein global gespeichertes Feld oder eine globale Variable ($$) referenziert, liefert stets nur für den jeweiligen Anwender respektive Client ein Ergebnis. Damit wird die Mehrbenutzerfähigkeit realisiert.
Da immer nur eine Formel ein Ergebnis liefert, ist auch immer nur eine Beziehung gültig. Und da wir wissen, welche Beziehung für welche Ebene gilt, wissen wir auch, über welche Beziehung die Datenfelder auf dem Layout zu platzieren sind:
Für die Ebene 0 (Projekte) platzieren wir das Feld hil__prj_level_0::prj_text_t und für Ebene 1 (Aufgaben) das Feld hil__aufg_level_1::aufg_text_t auf dem Layout. Letzteres wird etwas eingerückt über dem ersten Feld gelegt.
 
Abbildung 6
 
Abbildung 6 zeigt die schematische Platzierung. Hier sind die Objekte (Felder und Hintergründe) untereinander dargestellt, um die Platzierung zu verdeutlichen. Alle Objekte müssen natürlich übereinander liegen. Entscheidend ist für die Felder, dass Sie diese über die richtige Beziehung holen.
Da die Felder übereinander liegen, müssen wir die Füllung des jeweiligen Feldes dynamisch ein- und ausblenden. Hierfür verwende ich die bedingte Formatierung. Die Formel füllt das Feld mit der von Ihnen gewünschten Farbe, wenn die Zeilennummer des Datensatzes mit der Ebene übereinstimmt.
Es gilt:
GetValue( HIL::hil_ui_all_levels_gt ; Get( RecordNumber ) ) = 0
für Ebene 0 und für Ebene 1 gilt:
GetValue( HIL::hil_ui_all_levels_gt ; Get( RecordNumber ) ) = 1.
In den Layouteinstellungen sind die Felder dagegen ohne Hintergrund und Rahmen gesetzt.
Leider kann in FileMaker der Feldrahmen nicht über die bedingte Formatierung beeinflusst werden. Also habe ich hierfür je Ebene ein Textobjekt mit einem Leerschritt als Inhalt erstellt. Die Größe dieses Objektes ist bei einer gewünschten Linienstärke von 1 px in der Breite und Höhe jeweils 2 px größer.
Es gelten wiederum zuvor genannte Formeln und auch hier wird lediglich die Füllung des Textobjektes erzeugt, wenn die Formal Wahr ergibt. Die Füllfarbe ist in der Farbe gewählt, die der Feldrahmen haben soll.
Das Feld hil_ui_arrow_level_0_ucc ermittelt den jeweiligen Zustand des Datensatzes und blendet im zugeklappten Zustand einen Pfeil nach rechts und im aufgeklappten Zustand einen Pfeil nach unten ein. Es handelt sich hierbei um ein nicht gespeichertes Formelfeld mit Ergebnistyp Container[7]<< (_ucc).
Wenn Sie mit mehren Ebenen arbeiten, benötigen Sie für jede Ebene, die Kinder haben kann, auch ein eigenes Formelfeld. Die verwendete Formel ist seinerseits gut kommentiert, so dass sie gut zu verstehen sein sollte (Abbildung 7). Erwähnen möchte ich aber noch, dass sich die Bilder für die Pfeile in einem global gespeicherten Medienfeld mit zwei Wiederholungen namens hil_ui_artwork_grc[8]<< befinden.
Wiederholungen adressieren sie mit [Nummer der Wiederholung]. Die Wiederholungen habe ich nur behelfsweise verwendet und kann von Ihnen nach Ihren Wünschen modifiziert werden. Gleiches gilt für die Formel überhaupt. Sie wird lediglich für die Darstellung der Pfeile benötigt, ist aber nicht für die Darstellung einer hierarchisch strukturierten Information erforderlich.
 
Abbildung 7
 
Für den Fall, dass Sie prüfen möchten, ob für ein Projekt überhaupt schon Aufgaben definiert wurden, wird die Beziehung „hil__prj_level_0__aufg“ benötigt. Sind noch keine Aufgaben für das Projekt vorhanden, wird kein Pfeil eingeblendet. Gleiches gilt, wenn der aktuelle Datensatz schon in der letzt möglichen Ebene dargestellt wird.
 
Scripte
Kommen wir nochmals kurz zurück auf die Tabelle HIL zu sprechen. Diese benötige ich, wie weiter oben bereits erwähnt, ausschließlich zur Realisierung der hierarchischen Liste. In ihr existieren maximal so viele Datensätze, wie wir zur Anzeige von Projekten und deren Aufgaben benötigen.
Neben dem Aufruf der Datensätze der hierarchischen Liste steuert das Script „Aktualisiere die Liste [Anzahl der Datensätze]“ eben auch die Neuanlage von Datensätzen in der Tabelle HIL. Mit Aufruf des Scriptes werden nur so viele Datensätze angelegt, wie die dem Script übergebene Anzahl die Anzahl der in HIL vorhandener Datensätze übersteigt.
 
#Werden neue Datensätze benötigt?
If [ Get ( TotalRecordCount ) < Get( ScriptParameter ) ]
Loop
Exit Loop If [ Get ( TotalRecordCount ) >= Get( ScriptParameter ) ]
New Record/Request
End Loop
End If
 
In einer zweiten Schleife werden nun die Primärschlüssel der Datensätze aus HIL in eine Liste ($param) geschrieben. Die Anzahl der Schlüssel entspricht ebenfalls dem Scriptparameter. Da es egal ist, welcher Datensatz von HIL zur Anzeige genommen wird, können wir bequem mit Hilfe der Funktion „GetNthRecord“ und über die Beziehung „hil__sj_x“ den nten Datensatz holen, bis wir genug Schlüssel zusammenhaben.
Die Beziehung ist ein Selbstbezug auf die Tabelle HIL in Form eines Kartesischen Produktes (Kreuzprodukt von Mengen, nm-Beziehung). Mit diesem Selbstbezug (self join) wird jeder Datensatz der Tabelle mit allen anderen Datensätzen derselben Tabelle verbunden.
 
#Schreibe eine Liste von Schlüssel aller aufzurufenden Datensätze.
Loop
Set Variable [ $c; Value:$c + 1 ]
Exit Loop If [ $c > Get( ScriptParameter ) ]
Set Variable [ $param; Value:$param & GetNthRecord( hil__sj_x::_hil_pk_at ; $c ) & ¶ ]
End Loop
 
Im Anschluss wird die Schlüsselliste $param in ein global gespeichertes Textfeld _hil_sk_tmp_rel_gt geschrieben. Ausgehend von diesem Feld wird die Beziehung „hil__sj_tmp“ aufgebaut – ebenfalls ein Selbstbezug – über die wiederum die in der hierarchischen Liste anzuzeigenden Datensätze aufgerufen werden.
Es folgen nun noch das Script „Zeile erweitern oder reduzieren“ und „Reduziere auf oberste Ebene“. Auf letzteres Script muss ich nicht wesentlich eingehen. Es dient zur Anzeige von allen Datensätzen der Ebene 0, also allen Projekten. Dabei werden die Primärschlüsse der Projekte über die Beziehung „hil__prj_x“ geholt. Auch diese Beziehung ist ein kartesisches Produkt.
Das noch verbleibende Script ist im Grunde genommen auch nicht kompliziert. Es dient dazu, wie dessen Name ja schon sagt, die Aufgaben eines Projektes ein- bzw. auszublenden. Oder etwas allgemeiner ausgedrückt: Es dient dazu, die Datensätze der nächst tieferen Ebene ein- bzw. auszublenden, ausgehend von der geklickten Zeile.
Hierbei helfen uns wieder die beiden Felder hil_ui_all_keys_gt und hil_ui_all_levels_gt. Bei einer Erweiterung benötigen wir die Schlüssel und Ebenen aller Aufgaben (allgemein: der Datensätze der nächst tieferen Ebene), die dann nach der geklickten Zeile in die beiden Felder eingefügt werden.
Bei einer Reduzierung müssen wir genau diese wieder entfernen. Dies geschieht dadurch, dass wir uns die Position des Endes der geklickten Zeile einerseits und die Position des Endes der letzten Aufgabe zu dem Projekt merken. Alle Zeichen, die zwischen den beiden Grenzen liegen, brauchen dann nur noch gelöscht werden.
In dem Script sind hierfür zwei Stellen von wesentlicher Bedeutung, die ich jetzt noch etwas näher beleuchten möchte. Alles weitere sollte Ihnen nach dem Studium diese Artikels und er Beispieldatei, die weitestgehend durchkommentiert ist, bekannt und verständlich vorkommen.
Der erste Teil steht gleich am Anfang des Scripts in der Variablen $init. In dieser Variablen wird das folgende Script initialisiert, einige erforderliche neue Variablen geschrieben sowie der eigentliche Ablauf des Scripts vorbestimmt ($process). In ihr werden aber auch die Position des Endes der geklickten Zeile in Bezug auf den Schlüssel ($pos.clicked.row.key) und in Bezug auf die Ebene ($pos.clicked.row.level) ermittelt.
Zweiter wesentlicher Teil ist die Variable $edit an der Stelle
#Modifiziere die Schlüssel- und Ebenenliste
Set Variable [ $edit; Value:Let( [ ...] ; "" ) ]
Set Field [ HIL::hil_ui_all_keys_gt; $list.keys.new ]
Set Field [ HIL::hil_ui_all_levels_gt; $list.levels.new ]
im Script.
In ihr werden grundsätzlich die Position des Endes der letzten Aufgabe in Bezug auf die Schlüssel ($pos.last.row.key) und in Bezug auf die Ebenen ($pos.last.row.level) ermittelt. Alsdann gibt sie die modifizierte Schlüssel- und Ebenenliste zurück ($list.keys.new, $list.levels.new) und führt letztlich das schon bekannte Script zur Aktualisierung der Listenansicht aus.
 
Fazit
Ich habe Ihnen die grundlegenden Prinzipien aufgezeigt, nach denen Sie diese Methode einer hierarchischen Liste in der Listenansicht realisieren können. Die vorgestellte Methode halte ich für adaptierbar, wenn das Grundverständnis dieser Datenstruktur verinnerlicht wurde.
Mit einer Hierarchie von zwei Ebenen können Sie bereits viele reale Aufgabenstellungen in Ihrer Software abbilden. Trotzdem bleiben noch einige spannende Fragen, wie etwa eine Sortierung der Datensätze oder das Suchen in diesen.
Ebenso spannend aber etwas komplexer ist die Betrachtung von mehr als zwei Hierarchieebenen. Solche könnten für die Auflösung von Stücklisten benötigt werden. Aber auch in unserem Anwendungsfall, wie am Anfang bereits angedeutet, sind Bäume mit mehr als zwei Ebenen interessant. Dies behalte ich mir für einen weiteren Artikel vor.
 

[1]<< Martin Lormes: Einen Baum pflanzen und Kinder kriegen, Seite 17 f., FileMaker-Magazin, Ausgabe 05/2002
[2]<< Vgl. auch zur Vertiefung Gumm, Heinz Peter; Sommer, Manfred: Einführung in die Informatik, S. 356 f., 6. Auflage, München, Oldenbourg Wissenschaftsverlag, 2004
[3]<< Vertiefend zum Begriff der Hierarchie vgl. http://de.wikipedia.org/wiki/Hierarchie
[4]<< Entity-Relationship-Diagramm, hier in der MC-Notation (Multiple Choice Notation), vgl. Bodendorf, 2006, Seite 17 f., Daten- und Wissensmanagement, 2. Auflage, 2006, Heidelberg
[5]<< Üblicherweise verwende ich bei der Namensgebung von Feldnamen sowohl einen Prä- als auch ein Suffix. Das Präfix besteht aus dem Tabellennamen und das Suffix aus einigen in Englisch notierten und abgekürzten Feldeigenschaften, wie etwa Feldtyp, Ergebnistyp von Formelfeldern oder Speichereigenschaften. Ich erkläre bei neuen Suffixen jeweils ihre Bedeutung.
[6]<< Hole( DatensatzPositionInErgebnismenge )
[7]<< Ergebnistyp Medien bei auf deutsch eingestelltem FileMaker Pro
[8]<< _grc steht für global repeated container

Kommentare


Ameisen
von Martin
XSie müssen angemeldet sein um diese Datei herunterladen zu können.
Neues Passwort anfordern
Wenn Sie kein Benutzerkonto bei uns besitzen, registrieren Sie sich bitte hier.
Laden