In meinem letzten Blogeintrag habe ich bereits den Maskensucher und dessen Funktionalität vorgestellt. Dieser ist seit Version 4.0.3 Bestandteil des quasiAdmins. Bisher mussten Nutzer des quasiAdmins über den Navigationsbaum zu den einzelnen Masken navigieren. Da der quasiAdmin jedoch eine Vielzahl von Masken besitzt kann es teilweise vorkommen, dass man zwar den Namen einer Maske kennt, jedoch nicht sicher weiß unter welchem Unterpunkt sich diese im Navigationsbaum befindet. Über den Maskensucher kann zuerst eine Instanz ausgewählt werden, für die dann alle verfügbaren Masken angezeigt werden. Über ein Filterfeld können die Masken dann weiter eingegrenzt werden.

Wenn im Rahmen dieses Eintrages über Instanzen gesprochen wird sind Datenbanken oder Middleware-Farmen gemeint. Der quasiAdmin verwaltet diese beiden Komponenten in identisch aufgebauten Subapplikationen.

In diesem Beitrag wollen wir nun einen Blick auf die Technik hinter dem Maskensucher werfen und dabei einige nützliche Funktionen von Java 8 und insbesondere JavaFX kennenlernen:

  1. Beschaffung der Instanzen mit Hilfe von Streams, Methodenreferenzen und Lambda-Funktionen
  2. Vorstellung des Datenmodells hinter JavaFX und funktionaler Interfaces zur Filterung der Instanzen sowie die Erklärung warum es sinnvoll sein kann JavaFX-Komponenten in einem Framework zu kapseln
  3. Die dynamischer Verknüpfung zweier JavaFX-Elemente mittels eines Listeners zur Beschaffung der zugehörigen Masken
  4. Vorstellung einiger grafischer JavaFX-Komponenten zum Aufbau des grafischen Interfaces

Beschaffung der Instanzen

neu0Zuerst wollen wir uns ansehen, wie wir aus unserem Datenmodell die verfügbaren Instanzen erhalten können. Clientseitig wird Java 8 verwendet, daher können wir an dieser Stelle auf den vollen Sprachumfang dieser Version zugreifen. Instanzen werden durch die Klasse InstanceModel im quasiAdmin dargestellt. Nehmen wir an, wir haben bereits eine Liste aller verfügbaren Instanzen vorliegen, so müssen wir diese für den Maskensucher weiter einschränken, denn Instanzen können sowohl nicht verbunden (etwa wenn eine Datenbank heruntergefahren ist) als auch nicht verfügbar (etwa wenn die Zugangsdaten falsch hinterlegt wurden) sein und daher gar keine Masken anzeigen. Daher sind diese für den Maskensucher nicht von Interesse. Weiterhin interessiert uns von den einzelnen Instanzen vorerst jeweils nur der Name. Vor Java 8 musste nun über die ursprüngliche Liste iteriert werden und eine neue Liste aufgebaut, die die für den Maskensucher relevanten Instanzen beinhaltet. In Java 8 können stattdessen Streams eingesetzt werden, um diese Operationen deutlich lesbarer und kürzer durchzuführen.

code1

Zuerst konvertieren wir unsere Liste mittels der stream()-Methode in einen Stream. Auf diesen können wir dann unterschiedliche Methoden anwenden. Die filter()-Methode erwartet ein Prädikat . Dabei handelt es sich um ein funktionales Interface, das ein Argument entgegennimmt und einen boolschen Wert zurückliefert. Nur Instanzen, die dieses Prädikat erfüllen, verbleiben nach dem Durchlauf der filter-Methode im Stream. Als Prädikat verwenden wir Methodenreferenzen, in diesem Fall auf die isConnected()- und isAvailable()-Methoden der InstanceModel-Klasse. Die filter()-Methode iteriert nun über alle Instanzen im Stream und wendet auf jede die Methodenreferenz an. Objekte bei denen die Funktion false zurückgibt werden dabei aus dem Stream entfernt. Nach Abarbeitung der beiden filter()-Aufrufe besteht der Stream also nur noch aus Instanzen, die sowohl verbunden als auch verfügbar sind.

Für die Darstellung benötigen wir nur den Namen der Instanzen. Um einen Stream von Objekten umzuwandeln kann die map()-Funktion verwendet werden, die als Eingabe eine Methode erwartet, die das Function-Interface erfüllt. Dieses Interface beschreibt Methoden, die ein Argument als Eingabe erwarten sowie ein Ergebnis eines zu spezifizierenden Typs zurückliefern. Auch in diesem Fall verwenden wir eine Methodenreferenz auf die getName()-Methode des InstanceModel-Objektes. Die map()-Methode iteriert nun erneut über unseren Stream und transformiert die InstanceModel-Objekte in Strings, indem von den Objekten der Name extrahiert wird.

Für die weitere Verarbeitung muss der Stream, der jetzt nur noch aus Strings besteht, wieder in eine Liste umgewandelt werden. Praktischerweise stellen Streams mit der collect()-Methode auch dafür eine einfache Möglichkeit bereit. Als Argument muss ein Collector übergeben werden. Um einen passenden Collector zu erhalten, kann die Klasse Collectors verwendet werden. Über die Methode toList() erhalten wir aus dieser einen Collector, der unseren Stream in eine Liste umwandelt. Das Ergebnis können wir in einer neuen Liste festhalten.

 

Durch dieses einfache Beispiel zeigt sich bereits, wie mächtig die Kombination von Streams, Lambdas und Methodenreferenzen sein kann. Unser Code ist deutlich lesbarer und zugleich kürzer geworden. Auch der Ablauf wird relativ schnell deutlich. Schwieriger wird es jedoch, solchen Code die ersten Male selbst zu schreiben, hier benötigt es einfach einiges an Übung. Eine Nuance, die mir einiges an Kopfzerbrechen bereitet hat ist folgende: Sowohl die an die filter()- als auch die an die map()-Methode übergebenen Interfaces beschreiben Methoden, die jeweils ein Argument entgegen nehmen. Wir verwenden hier Referenzen auf Methoden, die allesamt kein Argument entgegennehmen. Wie können diese also das Interface erfüllen? Um dies zu verstehen müssen wir uns verdeutlichen, dass die Methodenreferenz hier stellvertretend für eine Lambdafunktion verwendet wird, wie unten beispielhaft für die Filterung von verbundenen Instanzen gezeigt. Das verwendete Argument ist also die jeweilige Instanz des Iterationsschrittes.

code2

Filtern der Instanzen

Beim quasiAdmin handelt es sich clientseitig um eine JavaFX Applikation, deren Elemente mit Propertys und ObservableLists befüllt werden müssen. Wir müssen unsere zuvor generierte Liste also in eine ObservableList transformieren. Dazu können wir uns mittels der Klasse FXCollections eine ObservableList beschaffen und diese dann mit den zuvor beschafften Instanznamen befüllen.

code3

Da der Nutzer die Möglichkeit erhalten soll, mittels eines Suchfeldes die Instanzen weiter zu filtern, wird ein Textfeld benötigt, an das der Nutzer seine Eingabe übergeben kann. Textfelder lassen sich in JavaFX durch die Klasse TextField realisieren. Die meisten JavaFX Komponenten bieten einen Funktionsumfang, der für den quasiAdmin zu komplex wäre. Um dem Entwickler die Arbeit mit den Komponenten zu erleichtern, aber auch um ein gleichmäßiges Design zu gewährleisten, wurde daher ein eigenes Framework entwickelt, dass die JavaFX Komponenten kapselt und eine Factory bereitstellt, die dem Entwickler die benötigten Komponenten ohne großen Aufwand generiert. So können wir uns ein Textfeld generieren. Dieses arbeitetet intern mit StringPropertys, die den Inhalt des Textfeldes halten.

code4

Um die ObservableList zu filtern bietet sich die Methode filtered() an, die ein Prädikat erwartet und eine FilteredList zurückgibt, die nur noch die Elemente der ursprünglichen Liste enthält, die dem Prädikat entsprechen. Das Prädikat erhält als Argument den Instanznamen aus unserer Liste und beschafft sich zusätzlich den Wert unseres Textfeldes, indem es diesen aus der Property abfragt. Dann muss nur noch überprüft werden, ob der Instanzname den Filtertext enthält, ohne dabei auf die Groß-/Kleinschreibung zu achten.

code5

Für die Darstellung der gefilterten Liste wird die JavaFX Komponente ListView verwendet. Auch für diese steht in unserem Framework eine ummantelnde Klasse bereit, die die Komplexität kapselt. Über die Factory erzeugen wir uns eine solche ListView, wobei wir als ersten Parameter Text für ein Label übergeben könnten. Da wir an dieser Stelle keinen Wert übergeben, wird kein zusätzliches Label erzeugt.

Beschaffung der zugehörigen Masken

Jede Instanz verfügt über eine Vielzahl von Masken, die der Nutzer auswählen kann. Abhängig von der zuvor ausgewählten Instanz sollen diese nun in einer weiteren Liste dargestellt werden.

code6

Dazu wird die Instanzliste mit einem Listener versehen, der bei jeder Veränderung der Selektion innerhalb der Liste reagiert. Der Listener muss das aus JavaFX stammende Interface ListChangeListener erfüllen. Dieses definiert nur eine Methode und ist damit gut geeignet für die Verwendung einer Lambda-Funktion. Als Argument erhält die Funktion ein Argument vom Typ ListChangeListener.Change aus dem verschiedene Informationen über die Veränderung entnommen werden können, uns in diesem Fall jedoch nicht interessieren. Wir nutzen stattdessen die bereits erzeugte Instanzliste und überprüfen, ob ein Element ausgewählt wurde. Ist dies der Fall, beschaffen wir uns dieses Element und nutzen die Funktion getAllViewsForInstance() um alle Views in einer Liste zu erhalten, die zu diesem Instanznamen gehören. Die Implementierung der Funktion ist wenig spektakulär und wird daher nicht weiter betrachtet.

code7

Da auch die Maskenliste filterbar sein soll verfahren wir wie zuvor bei der Instanzliste. Zuerst wird ein Textfeld samt zugehöriger Property erstellt und ein Prädikat definiert. Das Prädikat fragt die Property ab und überprüft, ob dieses im übergebenen Maskennamen vorkommt. Anschließend wird die Maskenliste mittels der filtered()-Methode und dem Prädikat in eine FilteredList umgewandelt. Aus dieser wird dann schlussendlich die ListView erzeugt, die alle nicht zuvor herausgesiebten Masken anzeigt.

code8

ListViews verwenden eine sogenannte CellFactory, um ihre Elemente in Zellen umzuwandeln, die dann angezeigt werden können. Für unsere Instanzliste können diese ganz einfach aus den übergebenen Strings einzelne Zellen erzeugt werden. Bei der Maskenliste wird dies jedoch schwieriger. Diese beinhaltet AMenuItems, eine Klasse des quasiAdmins, die Menüpunkte im Navigationsdialog symbolisiert. Wir müssen der ListView daher mitteilen, wie Sie aus den AMenuItems Zellen konstruieren soll. Das quasiAdmin Framework stellt dafür die Methode setCellFactory() an der ListView bereit, die zwei Methoden erwartet, die das Function-Interface erfüllen, und einen boolschen Wert. Die erste Methode soll Text erzeugen und die zweite eine Grafik, die die Zelle dann anzeigt. Die normale Reihenfolge ist dabei zuerst Grafik gefolgt vom Text. Der boolsche Wert gibt an, ob diese Reihenfolge invertiert werden soll. Die erste Function liest einfach den Namen des AMenuItems aus und gibt diesen zurück. Etwas komplizierter wird es für die zweite Function. Der quasiAdmin verwendet Objekte vom Typ ImageKey als Referenzen auf Icons. Die Factory des Frameworks kann daraus die Grafik erstellen. Dabei muss auf eine Eigenheit Rücksicht genommen werden: JavaFX verwendet für die Platzierung der einzelnen Elemente einen sogenannten Szenegraphen. Jedes Element kann nur einmal im Szenegraphen platziert werden. Bei mehrfacher Verwendung würde nur die letzte Platzierung des Elementes berücksichtigt. Da ein AMenuItem mehrfach angezeigt werden kann (beispielsweise im Navigationsbaum und im Maskensucher), kann es nicht direkt das Icon halten und für die Darstellung bereitstellen.

Aufbau des grafischen Interfaces

Nachdem wir nun alle Elemente des grafischen Interfaces kennengelernt haben, müssen wir diese nur noch in unserer JavaFX Applikation anordnen. Der Maskensucher (ViewSwitchDialog) erbt von der JavaFX-Klasse VBox, die als Container dient um Elemente vertikal anzuordnen. In diesen Container werden zwei Elemente platziert. Im unteren Bereich befindet sich die QButtonBar, die von der JavaFX-Klasse HBox erbt und als Container wiederum sowohl einen Bestätigen- als auch eine Abbrechen-Button aufnimmt. Oberhalb dieses Containers befindet sich ein QGrid. Dieses erbt von der JavaFX-Klasse GridPane, einem sehr mächtigen Containerelement, welches sowohl horizontal als auch vertikal Elemente anordnen kann.

Untitled Page

Für die Auswahl einer Maske ordnen wir die in den vorherigen Abschnitten kennengelernten Elemente im QGrid an. Im linken Bereich soll der Nutzer später die verfügbaren Instanzen filtern können sowie eine auswählen. Die davon abhängigen Masken werden rechts davon angezeigt mit dem entsprechenden Filterfeld.

code9

Fazit

In diesem Eintrag haben wir anhand kleiner Beispiele aus dem Maskensucher viele neue Elemente aus Java 8 kennengelernt. Mit Hilfe von Streams haben wir eine Liste von Instanzen gefiltert und transformiert und dabei auch auf Methodenreferenzen und Lambda-Funktionen zurückgegriffen. Anschließend haben wir den Inhalt der Liste in JavaFX Elementen untergebracht und schließlich gezeigt wie diese Elemente angeordnet werden können. Dabei wurden einige JavaFX Komponenten vorgestellt und gezeigt, warum es für große Projekte sinnvoll sein kann diese Elemente in einem eigenen Framework zu kapseln, um den Funktionsumfang von JavaFX zu verringern und dadurch Entwicklern den Einsatz der Komponenten zu erleichtern und ein einheitliches Layout zu gewährkeisten.

Java 8 erschien im März 2014 und ist damit bereits seit anderthalb Jahren verfügbar. Dennoch kommt es immer wieder vor, dass neuartige Funktionalitäten noch nicht von Entwicklern übernommen werden, obwohl diese nach einer gewissen Einarbeitungszeit unserer Erfahrung nach zu einer deutlichen Produktivitätssteigerung und lesbarerem Code führen. Zu erwähnen wären hier vor allem Streams, Methodenreferenzen und Lambda-Funktionen. JavaFX erweist sich ebenfalls als flexibles Framework, das selbst in bestehende Applikationen eingebunden werden kann.

Teile diesen Artikel:
Tagged with →  

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *