ui-javafx.html 30.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355


<p></p><a href="https://openjfx.io/">JavaFX</a> ist ein auf Java basierendes UI-Framework insbesondere für Desktop-Anwendungen. 
Es kann auch zur Entwicklung mobiler Anwendungen für spezifische eingettete Systeme und für die sehr verbreiteten Zielplattformen Android und iOS eingesetzt werden. 
Als Laufzeitumgebung bietet sich für diesen Fall die <a href="https://gluonhq.com/products/mobile/vm/">Gluon VM</a> an.</p>

<p>JavaFX war seit Version 7 (2012) bis inkl. Version 10 (2018) Teil der <a href="https://www.oracle.com/technetwork/java/javase/downloads/">Java SE</a> und damit das standardmäßig empfohlene UI-Framework. 
Im Zuge der Modularisierung und Verschlankung der Java SE ist auch JavaFX seit Java 11 nicht mehr Teil des JDK und muss als separate Bibliothek eingebunden werden.
Oracle erläutert diese Entscheidung in einem <a href="https://blogs.oracle.com/java-platform-group/the-future-of-javafx-and-other-java-client-roadmap-updates">Blog-Artikel</a>.
Die Weiterentwicklung von JavaFX erfolgt heute über die Open Source-Community <a href="https://openjfx.io/">OpenJFX</a>.
Da OpenJFX unter der <a href="https://de.wikipedia.org/wiki/GNU_General_Public_License">GPL</a> veröffentlicht ist, lassen sich mittels OpenJFX auf Basis des <a href="https://openjdk.java.net/">OpenJDK</a> nun freie, nicht durch Lizenzen eingeschränkte Desktop-Anwendungen mit Java konstruieren, was nicht nur für freie Linux-Distributionen erfreulich ist.
In der Dokumentation zu OpenJFX finden sich ausführliche und bebilderte <a href="https://openjfx.io/openjfx-docs/">Installationsanleitungen</a> für verschiedene Entwicklungsumgebungen und Build-Management-Tools.</p>

<p>JavaFX stellt eine Vielzahl von grundlegenden Steuerungselementen (sogenannte <a href="https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/ui_controls.htm"><i>UI Controls</i></a>) zur Interaktion mit dem Anwender bereit.
Dazu zählen <i>Button</i>, <i>Text Field</i>, <i>Radio Button</i>, <i>Checkbox</i>, <i>List View</i>, <i>Table View</i>, <i>Slider</i>, <i>Progress Bar</i> usw.
Die Darstellung der Steuerungselemente kann individuell über ein <i>Look and Feel-Theme</i> angepasst werden – z.B. mittels <a href="https://github.com/jfoenixadmin/JFoenix">JFoenix</a> an das <a href="https://material.io/design/">Material Design</a> von Google.
Zum Einstieg bietet sich der grafische Editor <a href="https://gluonhq.com/products/scene-builder/">SceneBuilder</a> an, der es erlaubt das UI per Drag & Drop zusammenzustellen.
SceneBuilder lässt sich in geläufige Java-Entwicklungsumgebungen wie IntelliJ oder Eclipse integrieren, um schnell zwischen der grafischen Editoransicht und dem generierten deklarativen Code wechseln zu können.
Für JavaFX-Anfänger bietet der SceneBuilder-Editor die Möglichkeit die vorhandenen Steuerungselemente und ihre anpassbaren Eigenschaften explorativ zu erkunden und zu beobachten, welcher Code durch die Zusammenstellung per Drag & Drop entsteht. Weiter unten folgt ein Video, das eine Einführung in den SceneBuilder-Editor bietet.
</p>

<h4>Trennung von Präsentation und Steuerung einer View</h4>

<p>Bevor wir die Motivation hinter der Trennung von Präsentation und Steuerungslogik in einem UI erläutern, klären wir kurz den allgemeinen Begriff "View":
Als eine View bezeichnen wir hier den Ausschnitt des gesamten UI, der dem Anwender aktuell angezeigt wird.
Das gesamte UI besteht also aus mehreren Views, zwischen deren Anzeige der Anwender i.d.R. durch eigene Interaktion wie das Anklicken eines Buttons navigiert.
Die möglichen Transitionen zwischen den Views einer Anwendung können als Zustandsautomat modelliert werden.
Je nach UI-Framework werden die Views unterschiedlich bezeichnet: In der Web-Entwicklung wird klassischerweise von Seiten (engl. <i>Pages</i>) oder in neueren Web-Frameworks wie <a href="https://angular.io/">Angular</a>, <a href="https://vuejs.org/">Vue.js</a> oder <a href="https://reactjs.org/">React</a> generisch von Komponenten (engl. <i>Components</i>) gesprochen. Im Android SDK navigiert der Anwender zwischen unterschiedlichen <i><a href="https://developer.android.com/guide/components/activities/activity-lifecycle">Activities</a></i> (oder ihren Fragmenten). 
In JavaFX wurde zur Bezeichnung der Views eine Analogie zur Theaterbühne (engl. <i>Stage</i>) gewählt, auf der mehrere Szenen (engl. <i>Scenes</i>) mit unterschiedlichem Bühnenbild ablaufen.
Das Fenster, in dem eine Anwendung dargestellt wird, ist dabei die <code><a href="https://openjfx.io/javadoc/12/javafx.graphics/javafx/stage/Stage.html">Stage</a></code>. Der Anwender kann durch Interaktion zwischen unterschiedlichen <code><a href="https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/Scene.html">Scenes</a></code> wechseln.</p>

<pre><code class="language-java line-numbers">import javafx.application.Application;
import javafx.stage.Stage;
    
public class HelloWorldApp extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    
    public void start(Stage stage) {
        stage.setTitle("Hello World!");
        stage.show();
    }
}</code></pre>

<img src="media/javafx_helloworld.png" style="width:460px">
<label>Minimale JavaFX-Anwendung mit leerer Stage</label>

<p>Der Inhalt einer <code>Scene</code> besteht in JavaFX aus mehreren Knoten (die entsprechende Klasse heißt <code><a href="https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/Node.html">Node</a></code>), die in einer Baumstruktur ineinander verschachtelt sind. Diese Baumstruktur wird in JavaFX als <i>Scene Graph</i> bezeichnet und entspricht konzeptionell dem <i>Document Object Model (DOM)</i> in der Web-Entwicklung. Container-Knoten, die mehrere andere Knoten als Kindelemente aufnehmen können, werden als <code><a href="https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/Parent.html">Parent</a></code> bezeichnet. Jede <code>Scene</code> hat einen Wurzelknoten, den <i>Root Parent Node</i>. Eine beliebig tief geschachtelte Baumstruktur entsteht dadurch, dass ein Container-Knoten auch andere Container-Knoten als Kindelemente aufnehmen kann. Grafische Animationen wie z.B. Verschiebungen, Rotationen und Skalierungen oder andere Effekte, die auf einen <code>Parent</code> angewendet werden, gelten auch für ihre untergeordneten Kindelemente. Konkrete Knoten, d.h. die Unterklassen der allgemeinen Klasse <code>Node</code> sind sämtliche Klassen für Steuerungselemente, Bilder oder Texte, die im UI angezeigt werden. Die folgende Abbildung und das folgende Code-Beispiel verdeutlichen den Aufbau eines <i>Scene Graph</i> in JavaFX.</p>

<img src="media/javafx_nodes.png" style="width:400px">
<label>Scene Graph in JavaFX</label>

<pre><code class="language-java line-numbers">HBox rootNode = new HBox(); // HBox is a concrete Parent subclass
Scene scene = new Scene(rootNode);

Node node = new Button("Button 1"); // Button is a concrete Node subclass
rootNode.getChildren().add(node);

stage.setScene(scene);
stage.show();</code></pre>

<p>Wie bei den anderen oben genannten UI-Frameworks aus dem Web- und Mobile-Bereich hat es sich auch in JavaFX bewährt, die Präsentation einer View von ihrer Steuerungslogik zu trennen. In nahezu alle modernen UI-Frameworks wird aktuell der Ansatz verfolgt, die grundlegende Präsentation einer View als Baumstruktur deklarativ in XML oder HTML zu formulieren und die zugehörige Steuerungslogik in einer imperativen Programmiersprache wie Java. Vor- und Nachteile dieses Ansatzes sollen kurz zusammengefasst werden:</p>
<ul>
    <li><b>Deklarative Programmierung der Präsentation:</b> In einer separaten Ressourcen-Datei wird die grundlegende Struktur einer View in einer deklarativen Auszeichnungssprache wie XML oder HTML gespeichert. Bei JavaFX ist dies <a href="https://docs.oracle.com/javase/8/javafx/fxml-tutorial/why_use_fxml.htm">FXML</a>. Derartige Auszeichnungssprachen sind sehr gut geeignet, um Baumstrukturen übersichtlich abzubilden. Das Framework ist in der Lage eine valide Ressourcen-Datei zu verarbeiten und für alle Knoten der Baumstruktur ein entsprechendes <code>Node</code>-Objekt zu erzeugen. Die einzelnen Einträge in Listen und Tabellen können zur Entwurfszeit natürlich nicht deklarativ angelegt werden, da sie erst dynamisch zur Laufzeit der Anwendung über einen Webservice o.ä. geladen werden.</li>    
    <li><b>Imperative Programmierung der Steuerungslogik:</b> Die Steuerung der View wird in einer imperativen Programmiersprache wie Java implementiert und beinhaltet insbesondere die Verarbeitung von sämtlichen Ereignissen, die zur Laufzeit auftreten können – wie z.B. die Navigation zur nächsten View nach einem Klickereignis und das damit verbundene dynamische Nachladen von Daten. Würde zusätzlich die grundlegende Struktur beim initialen Laden der Views imperativ zusammengestellt werden, entsteht i.d.R. sehr viel unleserlicher Code zum Aufbau der UI-Elemente, zum Setzen ihrer Eigenschaften und zum Positionieren im jeweiligen Container-Element (= Vaterknoten in XML). Änderungen an diesem Code sind aufwändig, da auch für kleine Verschiebungen von UI-Elementen viel Code bewegt wird. In der Versionsverwaltung sind die Effekte der Änderungen schwer nachzuvollziehen. Wer einen Blick auf diesen Code wirft, wird schnell erkennen, dass er sich sehr gut zur automatisierten Generierung durch ein Framework anbietet.</li>
</ul>

<p>Ein weiterer Vorteil der klaren Trennung von Präsentation und Steuerung einer View in der Anwendungsentwicklung ist, dass die Rollenaufteilung zwischen spezialisierten Teammitgliedern vereinfacht wird: UI-Designer sind i.d.R. den Umgang mit deklarativen Auszeichnungssprachen wie HTML gewohnt und müssen sich nicht mehr mit der imperativen Programmiersprache auseinandersetzen. 
Im <a href="#unit-mvc" class="navigate">MVC-Kapitel</a> ist bereits vorgestellt worden, wie das MVC-Muster zum MVVM-Muster ausgebaut werden kann, um die Steuerungslogik der View (= <i>ViewModel</i> in MVVM) unabhängig von einer konkreten Präsentation (= <i>View</i> in MVVM) zu gestalten. Die Präsentation kann dann relativ einfach ausgetauscht werden, ohne die Steuerungslogik der View anpassen zu müssen – z.B. von FXML zu HTML oder umgekehrt. Im Kapitel <a href="#unit-mvc-mvvm-javafx" class="navigate">MVC und MVVM in JavaFX</a> ist bereits dargestellt worden, wie eine einfache View inkl. Controller bzw. ViewModel in JavaFX aufgebaut werden kann.</p>

<h4>Layout Panes</h4>
<p>Ein <a href="https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/layout/Pane.html"><code>Pane</code></a> ist in JavaFX ein bestimmter Container-Knoten, in dem mehrere andere Knoten vereint und nach einem bestimmten Layout-Prinzip im UI angeordnet werden. Die wichtigsten der vorgefertigten <i><a href="https://docs.oracle.com/javase/8/javafx/layout-tutorial/builtin_layouts.htm">Layout Panes</a></i> sollen im Folgenden kurz erläutert werden.</p>
<ul>
    <li><b>HBox: </b>Die Kindelemente werden horizontal nebeneinander dargestellt. Es kann ein Abstand zwischen den Kindelementen angegeben werden.</li>
    <li><b>VBox: </b>Die Kindelemente werden entsprechend vertikal untereinander dargestellt.</li>
    <li><b>TilePane: </b>Die Kindelemente werden auf gleich großen Kacheln dargestellt. Die Anzahl der gewünschten Zeilen und Spalten kann angegeben werden.</li>
    <li><b>GridPane: </b>Die Kindelemente werden auf einem Gitter dargestellt, das aus einer definierten Anzahl an Zeilen und Spalten besteht. Kindelemente können sich über mehrere Zeilen und/oder Spalten des Gitters erstrecken.</li>
    <li><b>FlowPane: </b>Die Kindelemente werden horizontal oder vertikal hintereinander dargestellt und können unterschiedlich breit und hoch sein. Wenn die Breite des Containers (bei horizontaler Flussrichtung) bzw. die Höhe des Containers (bei vertikaler Flussrichtung) ausgeschöpft ist, wird der Fluss der Kindelemente in der nächsten Zeile bzw. Spalte fortgesetzt.</li>
    <li><b>BorderPane: </b>Die Kindelemente können in 5 definierten Regionen (<i>Center</i>, <i>Top</i>, <i>Left</i>, <i>Bottom</i>, <i>Right</i>) des Containers positioniert werden. Die Regionen dürfen auch leer bleiben.</li>
    <li><b>AnchorPane: </b>Die Kindelemente können an den Kanten und den Ecken des Containers mit fixen Koordinaten ausgerichtet werden.</li>
    <li><b>StackPane: </b>Die Kindelemente können gezielt übereinander gestapelt werden, wenn sie sich überlappen sollen, um Tiefe als dritte Dimension anzudeuten.</li>
    <li><b>TitledPane: </b>Der Container besteht aus einem Titel- und einem Inhaltsbereich. Der Inhaltsbereich kann mit einem Klick auf den Titel zugeklappt werden.</li>
    <li><b>TabPane: </b>Der Container besteht aus mehreren Tabs, von denen jeweils nur eines sichtbar ist.</li>
    <li><b>ScrollPane: </b>Der Container ist horizontal oder vertikal scrollbar und kapselt i.d.R. einen weiteren Container.</li>
    <li><b>SplitPane: </b>Der Container ist horizontal oder vertikal in mehrere Bereiche unterteilt, deren Breite bzw. Höhe der Anwender ggf. interaktiv verändern kann.</li>
</ul>
<img src="media/fx_layout_panes.png" style="width:700px">
<label>Layout Panes in JavaFX</label>

<h4>Navigation zwischen Views</h4>
<p>Die verschiedenen Views einer Anwendung teilen sich häufig bestimmte inhaltliche Bereiche wie einen Header, einen Footer oder eine Sidebar. Das Navigationsmenu selbst ist i.d.R. in einem dieser Bereiche untergebracht. Es ergeben sich zwei alternative Ansätze, um die Navigation zwischen den Views einer Anwendung zu realisieren, ohne den Code für die geteilten Bereiche zu duplizieren:</p>
<ul>
    <li><b><i>Multiple scenes</i>:</b> Alle Views sind strukturell gleich aufgebaut und inkludieren die geteilten Bereiche. Im folgenden Code-Beispiel werden der Header und die Sidebar aus separaten Dateien eingebunden. Weitere Views der Anwendung sind gleich aufgebaut wie die folgend dargestellte FXML-Datei <code>myFirstView.fxml</code> und unterscheiden sich nur im inhaltlichen Bereich. Nachteil: Wenn die allgemeine Struktur angepasst werden muss (z.B. Verschieben der Sidebar von links nach rechts), müssen alle Views einzeln editiert werden. 
        <script type="text/plain" class="language-xml line-numbers"><BorderPane fx:id="myFirstView">
    <top>
        <fx:include source="header.fxml"/>
    </top>
    <left>
        <fx:include source="sidebar.fxml"/>
    </left>
    <center>
        <!-- specific content of this view  -->
    </center>
</BorderPane></script>    
    Vorteil in JavaFX: Jede View kann über einen eindeutigen Dateinamen in einer neuen Szene geladen werden. 
        <pre><code class="language-java line-numbers">Scene scene = new Scene(FXMLLoader.load(getClass().getResource("myFirstView.fxml")));
stage.setScene(scene);</code></pre>    
    </li>
    <li><b><i>Single scene, dynamic content</i>:</b> Es wird eine FXML-Datei (z.B. namens <code>main.fxml</code>) angelegt, die den strukturellen Rahmen inkl. Header und Sidebar für alle Views definiert.
        <script type="text/plain" class="language-xml line-numbers"><BorderPane fx:id="main" fx:controller="MainController">
    <top>
        <fx:include source="header.fxml"/>
    </top>
    <left>
        <fx:include source="sidebar.fxml"/>
    </left>
    <center>
        <StackPane fx:id="viewHolder">
            <!-- specific content of the active view is loaded here -->
        </StackPane>
    </center>
</BorderPane></script> 
    Diese Datei wird initial in einer Szene geladen. Anstatt zur Laufzeit zu neuen Szenen zu wechseln, wird dynamisch der spezifische Inhalt einer View in ein dafür vorgesehenes <i>Layout Pane</i> (hier <code>StackPane viewHolder</code>) geladen.    
    <pre><code class="language-java line-numbers">public class MainController {

    @FXML
    private StackPane viewHolder;

    public void changeView(String fxmlFilename) {
        Node view = FXMLLoader.load(getClass().getResource(fxmlFilename));
        viewHolder.getChildren().setAll(view); // clears the list of child elements and adds the view as a new child element
    }
}</code></pre>       
    </li>
</ul>

<h4>Beispielanwendung "Yacht Shop"</h4>
<p>In folgendem Video wird eine JavaFX-Beispielanwendung vorgestellt, die mit der im <a href="#unit-spring" class="navigate">Spring-Kapitel</a> erstellten REST-API kommuniziert. Spezifische JavaFX-UI-Elemente werden im Video hervorgehoben. Der vollständige Code der Beispielanwendung findet sich im Verzeichnis <a href="/ui/javafx-shop" class="repo-link">/ui/javafx-shop</a> des Modul-Repository.</p>

<video controls><source src="media/JavaFX_Yacht_Shop.mp4" type="video/mp4"></video>
<label>JavaFX-Beispielanwendung "Yacht Shop"</label>

Die Anwendung folgt dem Ansatz <i>"Single scene, dynamic content"</i> und lädt dynamisch 3 unterschiedliche Views in Form von FXML-Dateien in den Inhaltsbereich eines <i>ViewHolder-Pane</i>. Die 3 Views bilden folgende Anwendungsfälle ab:
<ul>
    <li><code>login.fxml</code>
        <ul>
            <li>Anmelden als registrierter User</li>
        </ul>
    </li>
    <li><code>catalog.fxml</code>
        <ul>
            <li>Einsehen des Produktkatalogs</li>
            <li>Buchen eines Produkts</li>
        </ul>
    </li>
    <li><code>bookings.fxml</code>
        <ul>
            <li>Einsehen aller Buchungen des angemeldeten Users in einer Tabelle</li>
            <li>Löschen einer Buchung des angemeldeten Users</li>
        </ul>
    </li>        
</ul>
Zusätzlich besteht die Anwendung noch aus 2 weiteren FXML-Dateien:
<ul>
    <li><code>main.fxml</code>: Enthält die gemeinsame Rahmenstruktur alle inhaltlichen Views, insbesondere Header und Navigationsmenu.</li>
    <li><code>menu.fxml</code>: Enthält das Navigationsmenu, das in einem <i>MenuDrawer</i> dargestellt wird. Über das Menu kann zwischen den Views gewechselt werden.</li>
</ul> 
Im Folgenden wird der Code auszugsweise und teilweise vereinfacht anhand ausgewählter Aspekte erläutert.

<h4>FXML, Controller und CSS in JavaFX</h4>
<p>Das folgende Video zeigt, wie das Anmeldeformular einer Anwendung mittels des SceneBuilder-Editors ohne Programmierkenntnisse entworfen werden kann. Als Container werden im Video exemplarisch nur eine <code>VBox</code> und eine <code>HBox</code> verwendet, die verschiedene UI-Steuerungselemente aufnehmen und ineinander geschachtelt werden.</p>

<video controls><source src="media/SceneBuilder.mp4" type="video/mp4"></video>
<label>FXML-Entwurf im SceneBuilder-Editor</label>

<p>Die Ausgabe des Entwurfsvorgangs im SceneBuilder ist eine FXML-Datei, hier namens <code>login.fxml</code>, die noch nicht an einen Controller gebunden ist. Der Login-Button hat dadurch noch keine Funktionalität. In dem folgenden Code-Beispiel wird dem Anmeldeformular durch die Referenz auf die Klasse <code>LoginController</code> in Zeile 1 ein Controller zugeordnet, der automatisch durch das JavaFX-Framework instantiiert wird. Die Methode <code>initialize</code> versehen mit der Annotation <code>@FXML</code> (Zeilen 12-15 in <code>LoginController</code>) wird beim Erzeugen des Controllers durch das Framework ausgeführt. In Zeile 1 der FXML-Datei wird zusätzlich die CSS-Datei <code>app.css</code> eingebunden, so dass alle UI-Elemente die dort definierten Stylesheet-Klassen über das Attributs <code>styleClass</code> adressieren können.</p>

<ul class="nav nav-tabs" id="javafx1-tabs" role="tablist">
    <li class="nav-item"><a href="#javafx1-tabs-fxml" class="nav-link active" data-toggle="tab" role="tab">FXML (login.fxml)</a></li>
    <li class="nav-item"><a href="#javafx1-tabs-java" class="nav-link" data-toggle="tab" role="tab">Controller (LoginController.java)</a></li>
    <li class="nav-item"><a href="#javafx1-tabs-css" class="nav-link" data-toggle="tab" role="tab">CSS (app.css)</a></li>
</ul>
<div class="tab-content" id="javafx1-tabs-content">
    <div class="tab-pane show active" id="javafx1-tabs-fxml" role="tabpanel">
        <pre><code class="language-xml line-numbers"><!-- <VBox xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="shop.controller.LoginController" stylesheets="@app.css">
    <HBox>
        <MaterialIconView glyphName="LOCK"></MaterialIconView>
        <Label>Anmelden</Label>
    </HBox>
    <JFXTextField fx:id="username" promptText="Username" styleClass="input"/>
    <JFXPasswordField fx:id="password" promptText="Passwort" styleClass="input"/>
    <JFXButton onAction="#loginAction" text="LOGIN" styleClass="button-raised"></JFXButton>
    <Label fx:id="loginFailure" visible="false">Login fehlgeschlagen!</Label>
</VBox> --></code></pre> 
    </div>
    <div class="tab-pane" id="javafx1-tabs-java" role="tabpanel">
        <pre><code class="language-java line-numbers">public class LoginController {

    @FXML
    private TextField username;

    @FXML
    private PasswordField password;

    @FXML
    private Label loginFailure;
    
    @FXML
    public void initialize() {
        // executed when the controller object is created by the framework
    }
    
    public void loginAction(ActionEvent e1) {
        // execute login request in async task
        PostLoginTask loginTask = new PostLoginTask(username.getText(), password.getText());
        new Thread(loginTask).start();

        // login task response handler
        loginTask.setOnSucceeded((WorkerStateEvent e2) -> {
            // login is successfull if the return value is not null
            if (loginTask.getValue() == null) {
                loginFailure.setVisible(true);
                return;
            }
            // navigate to catalog view
            MainController.getInstance().changeView("catalog");
        });
    }
}</code></pre> 
    </div>
    <div class="tab-pane" id="javafx1-tabs-css" role="tabpanel">
        <pre><code class="language-css line-numbers">.button-raised {
    -jfx-button-type: RAISED;
    -fx-background-color: deeppink;
    -fx-text-fill: white;
    -fx-font-size: 14; 
    -fx-padding: 8 20;       
}

.input {
    -fx-max-width: 200;
}

#loginFailure {
    -fx-text-fill: red;
    -fx-font-size: 16;
}</code></pre> 
    </div>
</div>

<span>Die UI-Elemente aus der FXML-Datei können an Attribute in der Controller-Klasse gebunden werden (<i>Two-Way-Binding</i>). Auch diese Attribute werden per Dependency Injection durch das Framework instantiiert. Dazu muss ...</span>
<ul>
    <li>... der Bezeichner des Attributs in der Java-Klasse gleich dem Wert des Attributs <code>fx:id</code> in der FXML-Datei sein,</li>
    <li>der Datentyp des Attributs in der Java-Klasse gleich dem Datentyp in der FXML-Datei sein</li>
    <li>und das Attribut in der Java-Klasse mit der Annotation <code>@FXML</code> versehen sein.</li>
</ul>

<p>Die Methode <code>loginAction</code> (Zeilen 17-32 in <code>LoginController</code>) ist durch das Attribut <code>onAction="#loginAction"</code> in der FXML-Deklaration des Login-Buttons als <i>EventHandler</i> an diesen Button gebunden. Die Implementierung dieser Methode, inbesondere die Klasse <code>PostLoginTask</code>, wird weiter unten genauer erläutert.</p>

<p>Das Styling der UI-Elemente erfolgt in JavaFX wie aus der Web-Entwicklung bekannt über <a href="https://de.wikipedia.org/wiki/Cascading_Style_Sheets"><i>Cascading Style Sheets (CSS)</i></a>. Es ist demzufolge zu empfehlen, dass eine FXML-Datei nur die strukturelle Anordnung der UI-Elemente definiert und deren Styling über wiederverwendbare Stylesheet-Klassen in eine separate CSS-Datei ausgelagert wird. Eine Styling-Klasse kann über das Attribut <code>styleClass</code> adressiert werden. Über das Attribut <code>style</code> können die CSS-Properties eines UI-Elements genau wie in HTML direkt verändert werden. Eine Referenz über alle verfügbaren <a href="https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html">CSS-Properties in JavaFX</a> findet sich in der Dokumentation von Oracle.</p>

<p>Das folgende Code-Beispiel zeigt exemplarisch, wie ein Button alternativ aus dem imperativen Java-Code heraus einer View hinzugefügt werden kann. Das ist insbesondere für die Kindelemente von Listen (<a href="https://openjfx.io/javadoc/12/javafx.controls/javafx/scene/control/ListView.html"><code>ListView</code></a>) und Tabellen (<a href="https://openjfx.io/javadoc/12/javafx.controls/javafx/scene/control/TableView.html"><code>TableView</code></a>) sinnvoll, die zur Laufzeit dynamisch angelegt werden müssen, weil ihre Ausprägungen zur Entwurfszeit noch nicht bekannt sind.</p>

<pre><code class="language-java line-numbers">class LoginController {

    @FXML
    private Pane container;

    @FXML
    protected void initialize() {
        Button loginButton = new Button("LOGIN");
        container.getChildren().add(loginButton); // add button to a parent container

        // register button event handler
        loginButton.setOnAction((e) -> {
            // process login action here ...
        });

        // button styling
        loginButton.getStyleClass().add("button-raised");
        loginButton.setStyle("-fx-background-color: deeppink");
    }
}</code></pre>
<ul>
    <li>In Zeile 9 wird der neue Button dem in der zugehörigen FXML-Datei deklarierten <i>UI-Pane</i> <code>container</code> hinzugefügt.</li>
    <li>In den Zeilen 12-14 wird ein anonymer EventHandler für den Button registriert.</li>
    <li>In den Zeilen 17-18 wird das Styling des Buttons mittels CSS angepasst.</li>
</ul>

<h4>Asynchrone Tasks in einer JavaFX-Anwendung</h4>
<p>In einer JavaFX-Anwendung wird das UI aus dem sogenannten <i>JavaFX Application Thread</i> heraus gesteuert. Nur aus diesem Thread heraus darf das UI zur Laufzeit manipuliert werden, z.B. durch das Hinzufügen von Knoten zum <i>Scene Graph</i>. Es können in diesem Thread also nur nicht-blockierende EventHandler ausgeführt werden, damit sichergestellt ist, dass das UI stets auf Eingaben des Anwenders reagieren kann. Blockierende Aufrufe (wie z.B. das Senden eines HTTP-Request an eine REST-API) hingegen sind in nebenläufige Threads zu verschieben. Aus Sicht des <i>JavaFX Application Thread</i> wird in diesem Fall eine asynchrone Aufgabe (engl. <i>Task</i>) gestartet, deren Ergebnis – sobald es verfügbar ist – über eine Callback-Funktion verarbeitet wird, um das UI ggf. zu aktualisieren. Es bietet sich für derartige asynchrone Aufgaben in Java an, die Klasse <a href="https://openjfx.io/javadoc/12/javafx.graphics/javafx/concurrent/Task.html"><code>Task</code></a> zu erweitern. Diese Klasse implementiert die Interfaces <code>Future</code> und <code>Runnable</code> (s. Kapitel <a href="#unit-streams" class="navigate">Futures und parallele Streams</a>). Das folgende Code-Beispiel zeigt, wie das Senden eines HTTP-Request an eine REST-API und die Verarbeitung der zugehörigen HTTP-Response in eine spezielle, nebenläufig ausgeführte Task ausgelagert wird. In diesem Fall wird der HTTP-Request durch das Klicken auf den Login-Button im <i>JavaFX Application Thread</i> ausgelöst. Serverseitig soll geprüft werden, ob der Anwender eine gültige Kombination aus Username und Passwort eingegeben hat, und der Loginversuch damit erfolgreich war oder eben nicht.</p>

<ul class="nav nav-tabs" id="javafx2-tabs" role="tablist">
    <li class="nav-item"><a href="#javafx2-tabs-fxml" class="nav-link active" data-toggle="tab" role="tab">JavaFX Application Thread</a></li>
    <li class="nav-item"><a href="#javafx2-tabs-java" class="nav-link" data-toggle="tab" role="tab">Nebenläufiger Task</a></li>
</ul>
<div class="tab-content" id="javafx2-tabs-content">
    <div class="tab-pane show active" id="javafx2-tabs-fxml" role="tabpanel">
        <pre><code class="language-java line-numbers">// the following code is executed in the event handler of the login button

PostLoginTask loginTask = new PostLoginTask(username, password);
new Thread(loginTask).start();

// login task response handler
loginTask.setOnSucceeded((WorkerStateEvent e) -> {
    User user = loginTask.getValue();
    if (user != null) {
        // login is successfull
    }
    else {
        // login is not successfull
    }
});</code></pre> 
    </div>
    <div class="tab-pane" id="javafx2-tabs-java" role="tabpanel">
        <pre><code class="language-java line-numbers">import javafx.concurrent.Task;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.HttpResponse;
// ...

public class PostLoginTask extends Task&lt;User> {

    private String username;
    private String password;

    public PostLoginTask(String username, String password) { this.username = username; this.password = password; }

	@Override
	protected User call() {
		String url = "https://example.com/login";
		String json = "{\"name\": \"" + username + "\", \"passwordHash\": \"" + password + "\"}";

		try {
			HttpResponse&lt;String> res = Unirest.post(url).header("Content-Type", "application/json").body(json).asString();
			if (res.getStatus() != 403) {
				String jsonWebToken = res.getHeaders().getFirst("Authorization");
				return new User(username, jsonWebToken);
			}
		} catch (UnirestException e) {
			e.printStackTrace();
		}
		return null;
	}
}</code></pre> 
    </div>
</div>

<ul>
    <li>In Zeile 4 des <i>JavaFX Application Thread</i> wird der Task nebenläufig gestartet. In Zeile 7 wird mittels der Methode <code>setOnSucceeded</code> ein EventHandler registriert, der ausgeführt wird, sobald der Task abgeschlossen ist. In diesem EventHandler kann über die Methode <code>getValue</code> auf das Ergebnis des Task zugegriffen werden.</li>
    <li>Die Klasse <code>Task&lt;V></code> ist eine generische Klasse, für die ein bestimmter Rückgabetyp <code>V</code> für die Methode <code>call</code> spezifiziert werden kann. Für die Klasse <code>PostLoginTask</code> ist dies im vorliegenden Beispiel die Datenstruktur-Klasse <code>User</code> (vgl. Zeilen 6 und 14).</li>
    <li>In Zeile 19 des Task wird ein blockierender HTTP-Request gesendet, der ggf. eine Exception wirft, wenn z.B. der Server nicht zu erreichen ist. Insbesondere wegen dieser Zeile wird überhaupt ein asynchroner Task angelegt. In den Zeilen 20-23 wird die HTTP-Response verarbeitet.</li>
    <li>Zum Senden des HTTP-Request wird hier exemplarisch die Bibliothek <a href="http://unirest.io/java.html">Unirest</a> verwendet. Diese würde alternativ auch das Senden eines nicht-blockierenden HTTP-Request ermöglichen, wenn am Ende von Zeile 19 anstatt der Methode <code>asString</code> die Methode <code>asStringAsync</code> aufgerufen wird. Die Rückgabe wäre dann vom Typ <code>Future&lt;HttpResponse&lt;String>></code>. Dadurch würde sich das Auslagern des blockierenden Aufrufs aus dem eigenen Code in den internen Code der Bibliothek verlagern. Ein Task wäre nicht mehr unbedingt erforderlich, falls die Vorbereitung des HTTP-Request und die Verarbeitung der HTTP-Response als nicht sonderlich aufwändig eingestuft werden und es tolerierbar ist, diese im <i>JavaFX Application Thread</i> auszuführen.</li>
</ul>