2. JavaFX
•
•
•
•
•
•
Beispiel für Komponenten-Architektur
Graphische Oberflächen mit Schachtel-in-Schachtel-Prinzip
Konfiguration über get/set
Konfiguration über FXML
Event-Verarbeitung
Data-Binding und Properties als
Kommunikationsmechanismus
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
99
JavaFX - kurze Historie
• Zunächst SWT (Abstract Window Toolkit), dann ab Java 1.2
Swing (nur in Java, gleiches Look-and-Feel)
• 2008: JavaFX 1.x in eigener Sprache JavaFX Script;
ursprüngliche Idee Flash abzulösen (klappt nicht wirklich,
HTML 5 unendlich bessere Alternative)
• 2011: JavaFX 2.0 klarer Fokus auf Client-Oberflächen;
Nutzbarkeit über Browser bleibt vorhanden (Lauffähigkeit
hängt von Sicherheitseinstellungen ab)
• JavaFX: Trennung zwischen Spezifikation der Oberfläche
und fachlichem Code; einfache Code-Anbindung an
Oberfläche; gestaltbar u.a. mit CSS
• Zusammenspiel mit Adobe Illustrator und Photoshop
• Wenn notwendig JavaFX in Swing und auch Swing in JavaFX
einbettbar (-> Migrationsprojekte)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
100
Basis-Prinzip von Oberflächen
• Schachtel in Schachtel (auch hierarchisch genannt; Baum)
• Jede Schachtel kann mehrere sichtbare GUI-Elemente
enthalten
• Jede Schachtel kann eigene Gestaltungsmöglichkeiten
haben, die geerbt, vererbt und überschrieben werden
• Jedem GUI-Element und jeder umgebenden Schachtel
können Steuerungselemente zugeordnet werden
• Jedes Steuerelemente reagieren auf einzelne Events (Knopf
über Maus, linke Maustaste auf Knopf gedrückt, …)
• Es kann mehrere Reaktionen auf ein Event in gleichem GUIElement und in verschiedenen oberen Schachteln geben
• GUI-Elemente sind Komponenten
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
101
Erstes Beispiel (1/5)
• Knopf dreht sich beim Klicken
etwas um eigene Achse
• Wenn Knopf wieder an
Ursprungsposition, wird
verbrauchte Zeit angezeigt
nur eine Bühne
Stage
(oft) nur eine Szene
Scene
(meist) ab hier gestalten
AnchorPane
Button
Komponentenbasierte SoftwareEntwicklung
Label
Prof. Dr.
Stephan Kleuker
Schachtel mit Layout
GUI-Komponenten
102
Erstes Beispiel (2/5)
import
import
import
import
import
import
import
import
import
java.time.Duration;
java.time.LocalTime;
javafx.application.Application;
javafx.application.Platform;
javafx.scene.Scene;
javafx.scene.control.Button;
javafx.scene.control.Label;
javafx.scene.layout.AnchorPane;
javafx.stage.Stage;
muss so erben
public class KbSEJavaFXErsteDirekt extends Application {
private double value = 0;
private boolean aktiv = true;
private LocalTime start = LocalTime.now();
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
Winkel in Grad
103
Erstes Beispiel (3/5)
@Override
diese Methode muss
überschrieben werden
public void start(Stage primaryStage) {
Label lbl = new Label("Zeit");
Button btn = new Button ("Dreh mich");
btn.setOnAction(e -> {
ActionEvent e; Verarbeitung
mit Lambda-Ausdruck
if (this.aktiv) {
this.value += 40;
btn.setRotate(value);
konfigurieren mit set
if (this.value >= 360d) {
this.aktiv = false;
Date/Time API seit Java 8
btn.setDisable(true);
Duration dur = Duration.between(start, LocalTime.now());
double zeit = dur.getSeconds()*1e9 + dur.getNano();
lbl.setText("" + zeit/1e9);
}
}
Komponentenbasierte
SoftwareProf. Dr.
104
Stephan Kleuker
});Entwicklung
Erstes Beispiel (4/5)
lbl.setLayoutX(11);
lbl.setLayoutY(79);
btn.setLayoutX(27);
btn.setLayoutY(33);
Pane für das Layout;
hinzufügen immer zu
children
AnchorPane root = new AnchorPane();
root.getChildren().add(btn);
root.getChildren().add(lbl);
Scene scene = new Scene(root, 130, 110);
immer Wurzel in SceneObjekt einbetten
immer eine Scene der
einen Stage zuordnen
primaryStage.setTitle("Dreher");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(e -> Platform.exit());
primaryStage.show();
Komponentenbasierte
Software}
Entwicklung
Prof. Dr.
Stephan Kleuker
105
Erstes Beispiel (5/5)
public static void main(String[] args) {
Application.launch(args);
}
}
Genereller Aufbau
• erben von Application
• muss Methode überschreiben:
public void start(Stage primaryStage)
• kann Methoden überschreiben:
public void init()
public void stop()
// einmal zu Beginn
// einmal am Ende
• Start über Application.launch() [!!!, eigener Classloader]
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
106
Erinnerung: Grundregeln der Ergonomie
• Erinnern Sie sich an Regeln des guten Designs
• Fast nie mit absoluten Werten arbeiten
Hinweise:
• Hier stehen nur Konzepte im Vordergrund
• Im Mittelpunkt: Aufbau, Kombination und Kommunikation
von Komponenten
• Bei weitem werden nicht alle Gestaltungsmöglichkeiten
vorgestellt
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
107
Aufbauvarianten für JavaFX-GUIs
• grundsätzlich immer Oberfläche durch Programmierung
vollständig erstellbar und damit gestaltbar
• Variante: GUI-Aufbau mit FXML beschreiben
– wieder Baumstruktur
– viele Attribute zur detaillierten Gestaltung
– Gestaltung in CSS auslagerbar („fast“ CSS3)
– Verknüpfung zum Code wird über bestimmte Attribute
hergestellt
• FXML mit GUI-Designer-Werkzeug Scene Builder (JavaFXProgramm) erstellbar
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
108
FXML-Beispiel (1/10): Aufgabe
• generell gleiche Basisaufgabe
mit drehendem Knopf
• verbrauchte Zeit soll mitlaufen
(-> eigener Thread)
• Klick auf Label (?!?) links-unten
soll Programm neu starten
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
109
FXML-Beispiel (2/10): GUI.fxml (1/2)
• generell sinnvoll durch Scene Builder generieren lassen
• Anfang mit PI (Processing Instructions)
<?xml version="1.0" encoding="UTF-8"?>
<?import
<?import
<?import
<?import
<?import
<?import
<?import
<?import
javafx.geometry.*?>
javafx.scene.effect.*?>
java.lang.*?>
java.net.*?>
java.util.*?>
javafx.scene.*?>
javafx.scene.control.*?>
javafx.scene.layout.*?>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
110
FXML-Beispiel (3/10): GUI.fxml (2/2)
<AnchorPane id="AnchorPane" prefHeight="137.0"
prefWidth="195.0" styleClass="mainFxmlClass"
xmlns="http://javafx.com/javafx/8.0.40"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="kbsejavafxmitfxml.GUIController">
<stylesheets>
<URL value="@basis.css" />
</stylesheets>
<children>
<Button fx:id="btn" layoutX="27.0" layoutY="33.0"
mnemonicParsing="false" onAction="#klicken"
text="Dreh mich" />
<Label fx:id="lbl" layoutX="14.0" layoutY="95.0"
onMousePressed="#reset" text="Zeit"/>
</children>
</AnchorPane>SoftwareKomponentenbasierte
Prof. Dr.
Entwicklung
Stephan Kleuker
111
FXML-Beispiel (4/10): GUIController (1/3)
package kbsejavafxmitfxml;
import
import
import
import
import
import
import
java.net.URL;
java.util.ResourceBundle;
javafx.event.ActionEvent;
javafx.fxml.FXML;
javafx.fxml.Initializable;
javafx.scene.control.Button;
javafx.scene.control.Label;
muss realisieren
public class GUIController implements Initializable {
@FXML
private Button btn;
@FXML
private Label lbl;
Komponentenbasierte SoftwareEntwicklung
// fx:id="btn"
// fx:id="lbl"
Prof. Dr.
Stephan Kleuker
fx:id und Variablennamen
müssen übereinstimmen
112
FXML-Beispiel (5/10): GUIController (2/3)
private Uhr uhr;
private double value = 0;
private boolean aktiv = true;
public void klicken(ActionEvent event) {
if (this.aktiv) {
this.value += 40;
this.btn.setRotate(value);
if(this.value >= 360d){
this.aktiv = false;
this.btn.setDisable(true);
this.uhr.stoppeUhr();
}
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
113
FXML-Beispiel (6/10): GUIController (3/3)
public void reset(){ // Eventparameter darf fehlen
if(! this.aktiv){
wäre reset private, dann
this.value = 0;
muss Methode mit
this.btn.setRotate(value);
@FXML annotiert sein
this.aktiv = true;
this.btn.setDisable(false);
this.uhr.starteUhr();
}
}
einzige
Pflichtmethode
@Override
public void initialize(URL url, ResourceBundle rb) {
System.out.println("url: "+url+"\nrb: "+rb);
this.uhr = new Uhr(this.lbl);
new Thread(uhr).start();
}
Komponentenbasierte
Software}
Entwicklung
Prof. Dr.
Stephan Kleuker
114
FXML-Beispiel (7/10): Application
public class KbSEJavaFXMitFXML extends Application {
@Override
public void start(Stage stage) throws Exception {
setUserAgentStylesheet(STYLESHEET_CASPIAN);
Parent root
= FXMLLoader.load(getClass().getResource("GUI.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setOnCloseRequest(e -> System.exit(0));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
115
FXML-Beispiel (8/10): basis.css
.mainFxmlClass {
-fx-background-color: aquamarine;
-fx-font-size: 24
}
.label{
-fx-text-fill:blue
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
116
FXML-Beispiel (9/10): Uhr (1/2)
public class Uhr implements Runnable {
private long zeit;
private boolean uhrLaeuft = false;
private Label uhranzeige;
public Uhr(Label uhranzeige) {
this.uhranzeige = uhranzeige;
this.starteUhr();
}
public void starteUhr() {
this.zeit = (new Date()).getTime();
uhrLaeuft = true;
}
public int stoppeUhr() {
uhrLaeuft = false;
return (int) (((new Date()).getTime()) – this.zeit);
}
Komponentenbasierte SoftwareProf. Dr.
117
Entwicklung
Stephan Kleuker
FXML-Beispiel (10/10): Uhr (2/2)
@Override
public void run() {
while (true) {
if (this.uhrLaeuft) {
long tmp = ((new Date()).getTime()) - this.zeit;
Platform.runLater(() -> {
uhranzeige.setText((tmp / 1000d) + "");
});
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Komponentenbasierte
SoftwareProf. Dr.
}
Entwicklung
Stephan Kleuker
118
JavaFX-Thread
• JavaFX läuft als ein eigener Thread
• alle Ereignisse landen in einer Queue
• will man Oberfläche aus anderem Thread bearbeiten, muss
Änderung in Queue einfügen
Platform.runLater(() -> {
uhranzeige.setText((this.zeit / 1000d) + "");
});
• ohne Platform.runlater(Runnable):
Exception in Application init method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
119
Scene Builder
u. A. Vorschau
Detaileinstellungen
Komponenten
strukturiert
nach Themen
GUI-Hierarchie zur
präzisen Auswahl
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
Arbeitsfläche
120
Event-Behandlung
• Bei einer Aktion wird das Ereignis von der Wurzel des Scene
Graphen schrittweise zur benutzten Komponente
durchgeleitet (Event Capturing)
• jede Komponente kann auf Ereignis mit Filter reagieren und
ggfls. Weiterleitung verhindern (consume)
• Zielkomponente erhält Event und kann dies mit einem
Handler bearbeiten
• wird Ereignis nicht konsumiert, wird es schrittweise wieder
an darüber liegende Komponenten weitergegeben (Event
Bubbling)
• jede Komponente kann auf Ereignis mit Filter reagieren und
ggfls. Weiterleitung verhindern (consume)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
121
Capturing und Bubbling
• C wird geklickt
• Jeder Filter (EventHandler) kann
Event verarbeiten, dann keine
Weiterleitung
Klick
Filter
Filter
Filter
Komponentenbasierte SoftwareEntwicklung
A B
C
Filter
A
B
C
A
Filter
Filter
Prof. Dr.
Stephan Kleuker
B
B
122
Analyse Event-Behandlung (1/4)
public class EvHdl implements EventHandler{
private boolean consume;
private String ausgabe;
public EvHdl(boolean consume, String ausgabe) {
this.consume = consume;
this.ausgabe = ausgabe;
}
@Override
public void handle(Event event) {
System.out.println(this.ausgabe);
if(this.consume){
event.consume();
}
}
Komponentenbasierte Software}
Entwicklung
Prof. Dr.
Stephan Kleuker
123
Analyse Event-Behandlung (2/4)
@Override
public void start(Stage stage) {
Button btn = new Button("Button");
Label lbl = new Label("Label");
VBox root = new VBox(btn,lbl);
Scene scene = new Scene(root, 120, 50);
EventType et = MouseEvent.MOUSE_CLICKED;
btn.addEventFilter(et, new EvHdl(false,"Fbtn"));
btn.addEventHandler(et, new EvHdl(false,"Hbtn"));
lbl.addEventFilter(et, new EvHdl(false,"Flbl"));
lbl.addEventHandler(et, new EvHdl(false,"Hlbl"));
root.addEventFilter(et, new EvHdl(false,"Froot"));
root.addEventHandler(et, new EvHdl(false,"Hroot"));
scene.addEventFilter(et, new EvHdl(false,"Fscene"));
scene.addEventHandler(et, new EvHdl(false,"Hscene"));
stage.setScene(scene);
stage.setOnCloseRequest(e -> System.exit(0));
stage.show();
Komponentenbasierte SoftwareProf. Dr.
124
} Entwicklung
Stephan Kleuker
Analyse Event-Behandlung (3/4): Klickausgabe
Fscene
Froot
Fbtn
Hbtn
Button konsumiert
Event per default
Fscene
Froot
Flbl
Hlbl
Hroot
Hscene
Fscene
Froot
Hroot
Hscene
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
125
Analyse Event-Behandlung (4/4): Klickausgabe
root.addEventFilter(et, new EvHdl(true,"Froot"));
Fscene
Froot
Fscene
Froot
Fscene
Froot
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
126
Properties in JavaFX
• Oftmals soll auf Änderung von Komponenten reagiert werden
• Ansatz: Exemplarvariable wird als Property Observable, mit
Möglichkeit sich bei Variable an- und abzumelden
• gibt Klassen wie StringProperty, DoubleProperty
• weitere Vereinfachung mit Bindings
• Idee: Objekt möchte in seiner Exemplarvariablen immer den
Wert einer anderen Exemplarvariablen eines anderen Objekts
haben (unidirectional Binding)
• Erweiterung: zwei Exemplarvariablen sollen immer gleichen
Wert haben, können beide geändert werden (bidirectional
Binding)
• es entsteht ein Kommunikationsmechanismus für
Komponenten
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
127
Beispielklasse mit Property (1/2)
public class Person {
private StringProperty name = new SimpleStringProperty();
public String getName(){
return this.name.get();
}
public void setName(String name){
this.name.set(name);
}
public StringProperty nameProperty(){
return this.name;
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
128
Beispielklasse mit Property (2/2)
// nur Beispiel: Objekt beobachtet Aenderungen von sich
// selbst (generell untypisch)
public Person(){
this.name.addListener((obs,alt,neu)
-> System.out.println("von "+alt+" nach "+neu));
}
public void dialog(){
String eingabe="";
while (!eingabe.equals("X")){
eingabe = new Scanner(System.in).next();
this.setName(eingabe);
}
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
129
Binding-Beispiel (1/3)
@Override
public void start(Stage primaryStage) {
TextField tf1 = new TextField();
TextField tf2 = new TextField();
TextField tf3 = new TextField();
TextField tf4 = new TextField();
tf1.textProperty().bindBidirectional(tf2.textProperty());
tf3.textProperty().bind(tf2.textProperty());
VBox vbox = new VBox(tf1,tf2,tf3,tf4);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(e -> System.exit(0));
primaryStage.show();
Person p = new Person();
tf4.textProperty().bind(p.nameProperty());
new Thread(() -> p.dialog()).start();
Komponentenbasierte
SoftwareProf. Dr.
130
}
Entwicklung
Stephan Kleuker
Binding-Beispiel (2/3)
• Eingaben im ersten Feld werden
automatisch im zweiten und
dritten Feld sichtbar
• Eingaben im zweiten Feld werden
automatisch im ersten und dritten
Feld sichtbar
• Eingaben im dritten und vierten
Feld sind nicht möglich
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
131
Binding-Beispiel (3/3)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
132
Beispiel: Propertynutzung (1/3)
• Anforderung: Erst wenn in beiden Textfeldern Eingaben
erfolgt sind, soll der Knopf klickbar sein
• Ansatz: Neue BooleanProperty, die gewünschten
Knopfzustand enthält, Knopfeigenschaft meldet sich da an
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
133
Beispiel: Propertynutzung (2/3)
@Override
public void start(Stage primaryStage) {
Button btn = new Button("LogIn");
TextField tf1 = new TextField();
TextField tf2 = new TextField();
VBox vbox = new VBox(tf1, tf2);
GridPane gp = new GridPane();
gp.addRow(0, vbox, btn);
BooleanProperty boolp = new SimpleBooleanProperty(true);
boolp.bind(tf1.textProperty().isEmpty()
.or(tf2.textProperty().isEmpty()) );
btn.disableProperty().bind(boolp);
Scene scene = new Scene(gp);
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(e -> Platform.exit());
primaryStage.show();
Komponentenbasierte
SoftwareProf. Dr.
134
}
Entwicklung
Stephan Kleuker
Beispiel: Propertynutzung (3/3)
• Nachteil des vorherigen Ansatzes: Auch Leerzeichen werden
als Eingabe angesehen
• flexiblere, etwas aufwändigere Variante für gelben Kasten
BooleanProperty boolp = new SimpleBooleanProperty(true);
EventHandler<KeyEvent> handler = e
-> boolp.set(tf1.getText().trim().isEmpty()
|| tf2.getText().trim().isEmpty());
tf1.setOnKeyReleased(handler); // Typed, Pressed geht nicht, da
tf2.setOnKeyReleased(handler); // sonst letztes Zeichen fehlt
btn.disableProperty().bind(boolp);
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
135
Beispiel: Listen (1/5)
• links Auswahlbox (drop-down;
nur ein Element wählbar)
• rechts Auswahlkasten (Liste,
mehrere Elemente markierbar)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
136
Beispiel: Listen (2/5)
public class JavaFXListen extends Application {
private ChoiceBox<Person> chb;
private ListView<Person> lv;
//private ComboBox<Person> cob;
private ObservableList<Person> auswahl; //neue,
//threadsichere Collection
private String[] namen ={"Leila", "Oleg", "Ute", "Uwe"};
private void neueAuswahl(String wer, Person wahl){
System.out.println(wer + ": " + wahl + " ListView:"
+ this.lv.getSelectionModel().getSelectedItems());
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
137
Beispiel: Listen (3/5)
@Override
public void init(){
this.auswahl = FXCollections.observableArrayList();
for(String n:this.namen){
this.auswahl.add(new Person(n));
}
this.chb = new ChoiceBox<>();
this.chb.setItems(this.auswahl);
this.chb.getSelectionModel()
.selectedItemProperty().addListener((obj,alt,neu)
-> neueAuswahl("chb",neu));
this.lv = new ListView<>();
this.lv.setItems(this.auswahl);
this.lv.getSelectionModel()
.setSelectionMode(SelectionMode.MULTIPLE);
this.lv.setOnMouseClicked(e -> neueAuswahl("mouse",null));
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
138
Beispiel: Listen (4/5)
@Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.add(this.chb, 0, 0);
root.add(this.lv, 1, 0);
root.getColumnConstraints().add(new ColumnConstraints(80));
root.getColumnConstraints().add(new ColumnConstraints(100));
GridPane.setHalignment(this.chb, HPos.CENTER);
GridPane.setValignment(this.chb, VPos.TOP);
Scene scene = new Scene(root,190,130);
primaryStage.setTitle("Auswahlen");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(e -> System.exit(0));
primaryStage.show();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
139
Beispiel: Listen (5/5)
Ausgabe danach:
chb: Oleg ListView:[]
Ausgabe danach:
mouse: null ListView:[Leila]
Strg-Taste gedrückt gehalten, Ausgabe danach
mouse: null ListView:[Leila, Ute]
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
140
kleiner Ausblick JavaFX
•
•
•
•
•
Java-Code in FXML-Datei möglich
JavaScript-Code in FMXL-Datei möglich
viele, viele GUI-Elemente
nicht-kommerzielle und kommerzielle Erweiterungen
einfache Standardtechnologien zur Anbindung an Webserver
• automatisierbares GUI-Testing
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
141