Widges einfügen
https://oer-informatik.de/jfx_04_widgets_einfuegen
tl/dr; (ca. 4 min Lesezeit): Wie erstelle ich leaf nodes und branch nodes? Welche habe ich zur Auswahl? Und wie versehe ich Widgets - wie zum Beispiel Buttons mit Events? Artikel ist Teil eines JavaFX-Tutorials. ## Den Scenegraph aufbauen: Layout-Elemente wählen.
Eine neu erstellte Scene erwartet als Parameter einen Wurzelknoten (hier: rootNode
), der die Basisstruktur der GUI darstellt:
Dieser Wurzelknoten muss vom Typ Parent
erben. Üblicherweise werden in der ersten Ebene Objekte, die von der Klasse Pane
erben genutzt, da sie als Container die weiteren Inhalte gliedern. Das UML-Klassendiagramm zeigt einen Teil der in Betracht kommenden branchnodes.

Eine Übersicht der einzelnen Panes gibt es hier:
https://docs.oracle.com/javafx/2/layout/builtin_layouts.htm
Den Scenegraph befüllen: Control-Elemente wählen.
https://openjfx.io/javadoc/18/javafx.controls/javafx/scene/control/package-summary.html
Die Struktur kann mit bottom-up- oder top-down-Strategie erzeugt werden. Bottom-Up würden zunächst die Inhaltselemente erzeugt werden (leaf nodes), die dann Stück für Stück in einen übergeordneten Knoten eingefügt werden:
TextField eingabeTextField = new TextField("0");
TextField ausgabeTextField = new TextField("0");
Button berechnenButton = new Button("berechnen");
Button loeschenButton = new Button("löschen");
VBox eingabe = new VBox(eingabeTextField, berechnenButton);
VBox ausgabe = new VBox(ausgabeTextField, loeschenButton);
HBox rootNode = new HBox(eingabe, ausgabe);
Scene scene = new Scene(rootNode, 300, 250);
Top-Down würden zunächst die Knoten beginnend mit dem Wurzelknoten erzeugt und am Ende zusammengefügt:
HBox rootNode = new HBox();
VBox eingabe = new VBox();
VBox ausgabe = new VBox();
TextField eingabeTextField = new TextField("0");
TextField ausgabeTextField = new TextField("0");
Button berechnenButton = new Button("berechnen");
Button loeschenButton = new Button("löschen");
rootNode.getChildren().addAll(eingabe, ausgabe);
ausgabe.getChildren().addAll(ausgabeTextField, loeschenButton);
eingabe.getChildren().addAll(eingabeTextField, berechnenButton);

Controls mit Events versehen
Bislang sind die Buttons noch nutzlos, wir müssen Ereignisse hinterlegen, die passieren, wenn der Button betätigt wird.

Eine Methode ist dafür ausschlaggebend: setOnAction()
(Klasse ButtonBase
) legt fest, was bei Betätigung des Buttons passiert. Das Ergeignis wird als Objekt erzeugt und im Attribut onAction
gespeichert.
Die Funktionalität wiederum, die durch einen Knopfdruck ausgelöst wird, wird in der Methode handle()
in einem Objekt vom Typ Eventhandler
gespeichert. Wir müssen also mit Hilfe einer Klasse, die das Interface EventHandler
implementiert die Methode handle()
implementieren.
Das geht auf einem einfachen und auf einem komplizierten Weg. Der komplizierte deckt auf, wie der einfache Weg funktioniert, daher fangen wir zunächst damit an.
Events über Objekte von inneren Klassen
In Java kann ich an jeder Stelle Klassen definierten - auch mitten in einer Methode. Da diese Klasse als Scope (Gültigkeitsbereich) an die umgebende Klasse gebunden sind nennt man diese Klassen inner classes. Wir benötigen eine Klasse, die das Interface EventHandler
und darin die Methode handle()
implementiert. Unsere Funktionalität ist eine einfache Textausgabe auf dem Terminal:
Button berechnenButton = new Button("berechnen");
class MeineEventklasse implements EventHandler<ActionEvent>{
public void handle(ActionEvent event){
System.out.println("Button betätigt");
}
}
EventHandler<ActionEvent> meinEventHandler = new MeineEventklasse();
berechnenButton.setOnAction(meinEventHandler);
Wir instanziieren unsere Klasse (Objekt meinEventHandler
) und weisen dem Button diesen Event per setOnAction()
zu. Das klappt schonmal wunderbar, ist aber sehr umständlich…
Events über Objekte von anonymen inneren Klassen
Es stellt sich bei der Variante oben die Frage, warum wir die Klasse gesondert erstellen und bezeichnen, wenn wir doch ohnehin nur ein Objekt benötigen? Hierfür weist Java anonyme innere Klassen aus: hier wird die Implementierung der Klasse direkt bei der Objekterzeugung übergeben und ist für kein zweites Objekt verfügbar. Wir kürzen so unser obiges Beispiel etwas ab:
Button berechnenButton = new Button("berechnen");
EventHandler<ActionEvent> meinEventHandler = new EventHandler<ActionEvent>() {
public void handle(ActionEvent event){
System.out.println("Button betätigt");
}
};
berechnenButton.setOnAction(meinEventHandler);
Es wurde also ein Objekt erzeugt und dabei direkt die Methode implementiert. Wichtig sind hierbei: die runden Klammern bei new EventHandler<ActionEvent>()
und der Semicolon am Ende der Objekterzeugung. Der Code funktioniert 100% genauso wie im ersten Beispiel.
Events über Lambda-Ausdrücke
Aber es ist immer noch ganz schön viel Code für eine einfache Ausgabe. Bei Interfaces mit genau einer abstrakten Methode (in Java werden sie “Funktionale Interfaces” genannt) können wir eine Kurzform für anonyme innere Klassen anwenden: die Lambda-Ausdrücke. Wir beschreiben nur, welcher Parameter übergeben wird und welche Implementierung für die Methode genutzt wird.
In unserem Fall:
Parameter:
event
Implementierung: ‘System.out.println(“Button betätigt”);’
Die Schreibweise für einen Lambda-Ausdruck ist: Parameter->{Implementierung}
Wir können also abkürzen:
Button berechnenButton = new Button("berechnen");
EventHandler<ActionEvent> meinEventHandler = (event->{System.out.println("Button betätigt");});
berechnenButton.setOnAction(meinEventHandler);
Genau genommen weiß Java ja bereits bei der Methode setOnAction()
, dass wir ein Objekt vom Typ EventHandler
übergeben wollen. Wir können also noch weiter abkürzen:
Button berechnenButton = new Button("berechnen");
berechnenButton.setOnAction(event->{System.out.println("Button betätigt");});
Die Umrechnung und das Löschen implementieren
Häufig sollen bei ausgelösten Events (wie dem Knopfdruck) nicht einzelne Operationen, sondern ganze Abläufe ausgelöst werden. Diese in Lambda-Ausdrücken zu schreiben kann schnell unübersichtlich werden. Daher rufen wir mit dem Lambda-Ausdruck eine neue Methode auf, die alles weitere übernimmt:
public void berechne(){
double zahl = Double.parseDouble(eingabeTextField.getText());
double ergebnnis = ((int) zahl / 2);
ausgabeTextField.setText(Double.toString(ergebnnis));
}
Nächste Schritte
Dieser Artikel ist ein Teil der Artikelserie zu einer Energiemonitors mit JavaFX.
to be continued…
Links und weitere Informationen
Quellen und offene Ressourcen (OER)
Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich in weiterbearbeitbarer Form im gitlab-Repository unter https://gitlab.com/oer-informatik/java-fx/ersteschritte und sind zur Nutzung als Open Education Resource (OER) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0).