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
© Copyright 2024 ExpyDoc