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) 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) 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 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 chb; private ListView lv; //private ComboBox cob; private ObservableList 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