Seminarium 8 Innehåll Grafiska användargränsnitt i Java Komponenter (fönster, knappar,...) Layout Händelsehantering (Hur man får någonting att hända när användaren t.ex. klickar på en knapp.) Design EDAA30 (Sem8) HT1 2015 1 / 49
Grafiska användargränsnitt i Java Exempel på vad du ska kunna Implementera enkla användargränsnitt med hjälp av JavaFX. Beskriva och kunna tillämpa principerna för händelsehantering i JavaFX. Kunna använda lambda-uttryck i Java. Kunna separera modell och vy (grafiskt användargränssnitt) vid implementering av program med grafiska användargränssnitt. Grafiska användargränssnitt examineras inte i samband med tentamen utan enbart på laboration 3 och 6. De som väljer att lösa sudoku som inlämningsuppgift får ytterligare träning på detta. EDAA30 (Sem8) HT1 2015 2 / 49
Klasspaket i Java AWT (Abstract Window Toolkit) Ett äldre paket Delar av det används fortfarande (händelsehantering och layout). Swing Ett modernare paket JavaFX Standard från Java8. Vanligast i nya projekt. EDAA30 (Sem8) HT1 2015 3 / 49
Använda klasspaketet JavaFX För att kunna använda JavaFX måste man känna till Klasserna som beskriver grafiska komponenter: fönster (Stage), knappar (Button)... Hur man placerar de grafiska komponenterna i ett fönster (layout). Hur man hanterar händelser. T.ex. att användaren trycker på en knapp. EDAA30 (Sem8) HT1 2015 4 / 49
Scengraf De grafiska komponenterna som visas på skärmen representeras av objekt. JavaFX ritar automatiskt alla komponenter, uppdaterar vid behov, t.ex. när fönstrets storlek ändras. Scengrafen - objekten arrangeras i en hierarkisk struktur, som är ett träd. Roten är ett objekt av klassen Stage Stage instansieras av ramverket EDAA30 (Sem8) HT1 2015 5 / 49
Grafiska komponenter Exempel, fler finns Styrkomponenter Används för att interagera med användare. Finns i paketet javafx.scene.control Ex: knappar, menyer, textrutor Reagerar på händelser (musklick, menyval,... ). Former Geometriska former som ritas, men användaren inte kan påverka. Ex. rektangel, linje och cirkel Finns i paketet javafx.scene.shape Behållarkomponenter Kan innehålla andra komponenter och används för att organisera komponenter. Ofta osynliga Lägg till barn med metoderna getchildren().addall() eller getchildren().add() EDAA30 (Sem8) HT1 2015 6 / 49
behållarkomponenter javafx.scene.control.control Objekt som användaren interagerar med. Exempel ToolBar, TabPane. javafx.scene.layout.region anpassar storek när fönstret ändras. Subklasser till Region placerar barnen enligt en layout StackPane, HBox, VBox, TilePane, FlowPane, BorderPane, GridPane, och AnchorPane. javafx.scene.group Fix storlek, ändras inte när fönstret ändras. EDAA30 (Sem8) HT1 2015 7 / 49
Starta en JavaFX applikation import javafx.application.application; import javafx.scene.scene; import javafx.stage.stage; public class MyApp extends Application { public void init() {... public void start(stage stage) throws Exception { Pane root = makescengraph(); Scene scene = new Scene(root, 200, 80); stage.setscene(scene); stage.settitle("hello World"); stage.show(); public void stop() {... public static void main(string[] args) { launch(args); EDAA30 (Sem8) HT1 2015 8 / 49
Starta en JavaFX applikation Kommentarer init anropas innan JavaFX har satt upp ramverket, används normalt inte. start - här skaps normalt scenografen. Koppla den till stage och anropa stage.show(). stop anropas när programmet avslutas. Här kan du t.ex. spara dokument till fil. start är abstract i Application och måste implementeras. init och stop ärvs från Application och behöver inte implementeras. main behöver inte ligga i MyApp. Du kan även starta JavaFX med MyApp.launch(args) och Application.launch(MyApp.class, args). EDAA30 (Sem8) HT1 2015 9 / 49
Trådar JavaFX använder flera trådar. Alla JavaFX-objekt måste skapas i Application.start() eller i en event-hanterare. Lägg all kod i Application.start(), Application.stop() och i event-hanterare så körs allt i en tråd och du behöver inte bekymra dig om tråd-synkronisering. (som behandlas i kursen realtidsprogrammering) EDAA30 (Sem8) HT1 2015 10 / 49
Exempel: Fönster med en knapp import javafx...; public class Counter extends Application { public void start(stage stage) throws Exception { Button button = new Button("Yes!"); Label label = new Label("0"); HBox buttonbox = new HBox(); buttonbox.setspacing(20); // Sets the amount of horizontal buttonbox.setalignment(pos.center); // Sets the overall alig buttonbox.getchildren().addall(button, label); Pane root = new StackPane(); root.getchildren().add(buttonbox); Scene scene = new Scene(root, 200, 80); stage.setscene(scene); stage.show(); EDAA30 (Sem8) HT1 2015 11 / 49
Layoutmanager En layoutmanager är ett objekt som bestämmer storlek och läge för komponenterna i en behållare. Den räknar också ut nya positioner och storlekar om fönstrets storlek ändras. Varje behållare har en egen layoutmanager kopplad till sig. Olika typer av layoutmanagers har olika strategier för placering av komponenterna. Ex: BorderPane delar utrymmet i fem delar; norr, söder, öster, väster och mitten. FlowPane komponenterna placeras i en rad efter varandra. HBox horisontell rad. GridPane rutnät. TilePane rutnät, alla rutor lika stora. AnchorPane ankrat till sida/hörn. EDAA30 (Sem8) HT1 2015 12 / 49
Exempel: BorderPane private Pane makescengraph(){ BorderPane root = new BorderPane(); root.settop(new Button("Top")); root.setleft(new Button("Left")); root.setcenter(new Button("Center")); root.setright(new Button("Right")); root.setbottom(new Button("Bottom")); root.setprefsize(200, 400); return root; EDAA30 (Sem8) HT1 2015 13 / 49
Exempel: FlowPane private Pane makescengraph(){ FlowPane root = new FlowPane(); root.sethgap(10); root.setvgap(10); root.setpadding(new Insets(10, 10, 10, 10)); for(int i = 1; i<=5; i++){ root.getchildren().add(new Button("box " + i)); return root; EDAA30 (Sem8) HT1 2015 14 / 49
FlowPane - ändring av layouten när fönstret ändrar storlek EDAA30 (Sem8) HT1 2015 15 / 49
Exempel: GridPane Rader och kolumner kan ha olika storlek. private Pane makescengraph(){ GridPane root = new GridPane(); root.sethgap(10); root.setvgap(10); root.setpadding(new Insets(10, 10, 10, 10)); root.add(new Button("one"), 0, 0); root.add(new Button("two"), 1, 0); root.add(new Button("three"), 2, 0); root.add(new Button("four"), 0, 1); root.add(new Button("five"), 1, 1); root.add(new Button("six"), 2, 1); return root; EDAA30 (Sem8) HT1 2015 16 / 49
Exempel: TilePane Som FlowPane, men cellerna har samma storlek. private Pane makescengraph(){ TilePane root = new TilePane(); final int SIZE = 40; root.setprefsize(8 * SIZE, 8 * SIZE); for (int i = 1; i <= 8; i++) { for (int k = 1; k <= 8; k++) { Label label = new Label(); label.setprefsize(size, SIZE); if (i % 2!= 0 && k % 2!= 0 i % 2 == 0 && k % 2 == 0) { label.setstyle("-fx-background-color: #000000;"); root.getchildren().add(label); return root; EDAA30 (Sem8) HT1 2015 17 / 49
Exempel: Nästlade layouthanterare private Pane makescengraph(){ HBox topbox = new HBox(); topbox.setpadding(new Insets(12, 12, 12, 12)); topbox.setspacing(5); topbox.setalignment(pos.center); topbox.setstyle("-fx-background-color: #009900;"); topbox.getchildren().addall(new Button("1"), new Button("2")); HBox bottombox = new HBox(); bottombox.getchildren().addall(new Button("3"), new Button("4"), new Button("5")); BorderPane root = new BorderPane(); root.settop(topbox); root.setbottom(bottombox); root.setprefsize(200, 100); return root; EDAA30 (Sem8) HT1 2015 18 / 49
Händelsehantering När användaren klickar på en knapp, väljer ett menyalternativ... händer följande: 1 Ett händelseobjekt skapas. 2 JavaFXsystemet anropar handle-metoden på de lyssnarobjekt som är knutna till komponenten. EDAA30 (Sem8) HT1 2015 19 / 49
Lyssnarobjekt 1 Ett lyssnarobjekt är ett objekt av en klass som implementerar interfacet: public Interface EventHandler<T extends Event> { void handle(t event) 2 Ett lyssnarobjekt kan knytas till flera komponenter. 3 En komponent kan ha flera lyssnarobjekt, alla anropas. 4 Normalt har varje komponent ett eget lyssnarobjekt. 5 De flesta klasser och interface som har med händelser att göra finns i paketet javafx.event. EDAA30 (Sem8) HT1 2015 20 / 49
Händelsehantering För att få någonting att hända när användaren klickat på en knapp måste man: Låta någon klass implementera interfacet EventHandler. I metoden handle skriver man det man vill ska hända när användaren klickar på knappen. Koppla lyssnar-objektet till knappen genom att anropa metoden setonaction. Jfr. tidningsprenumeration. Man kan göra liknelsen att lyssnar-objektet prenumererar på händelsen knappen trycks ned. EDAA30 (Sem8) HT1 2015 21 / 49
Exempel på händelsehantering EDAA30 (Sem8) HT1 2015 22 / 49
Exempel på händelsehantering Klassen Counter public class Counter extends Application { private Button button; private Label label; private int counter; public static void main(string[] args) { launch(args); private class ButtonHandler implements EventHandler<ActionEvent> @Override public void handle(actionevent event) { counter++; label.settext(integer.tostring(counter)); public void start(stage stage) throws Exception { /** se näst sida **/ EDAA30 (Sem8) HT1 2015 23 / 49
Exempel på händelsehantering Klassen Counter, start-metoden public void start(stage stage) throws Exception { button = new Button("Yes!"); button.setonaction(new ButtonHandler()); label = new Label("0"); HBox buttonbox = new HBox(); buttonbox.setspacing(20); buttonbox.setalignment(pos.center); buttonbox.getchildren().addall(button, label); Pane root = new StackPane(); root.getchildren().add(buttonbox); Scene scene = new Scene(root, 200, 80); stage.setscene(scene); stage.settitle("counter"); stage.setscene(scene); stage.show(); EDAA30 (Sem8) HT1 2015 24 / 49
Händelsehantering - flera komponenter Man kan ha flera komponenter som genererar händelser. T.ex. olika knappar, textfält, menyer,... Olika saker ska hända beroende på vilken av komponenterna som genererade händelsen. Detta kan man lösa på olika sätt: Låt ett lyssnarobjekt ta hand om alla händelserna. Då måste detta kunna identifiera vilken komponent händelsen är förknippad med (t ex med hjälp av ActionEvent-objektet). Låt varje komponent ha sitt eget lyssnarobjekt. EDAA30 (Sem8) HT1 2015 25 / 49
Exempel på händelsehantering flera knappar EDAA30 (Sem8) HT1 2015 26 / 49
Lösning 1: en lyssnare för alla komponenter Klassen Counters, attribut samt konstruktor public class Counter extends Application { private Button yesbutton, nobutton; private Label yeslabel, nolabel; private int yescounter, nocounter;... public void handle(actionevent e) { if (e.getsource() == yesbutton){ yescount++; yeslabel.settext("yescount: " + yescount); else if (e.getsource() == nobutton){ nocount++; nolabel.settext("nocount: " + nocount); EDAA30 (Sem8) HT1 2015 27 / 49
Lösning 2: bättre alternativ, en lyssnare per komponent Inre klasser Deklarera en lyssnar -klass per knapp. Dessa klasser deklareras som inre klasser i Counters: private class YesButtonHandler implements EventHandler<ActionEvent> @Override public void handle(actionevent event) { yescounter ++; yeslabel.settext(integer.tostring(yescounter)); private class NoButtonHandler implements EventHandler<ActionEvent> { @Override public void handle(actionevent event) { nocounter ++; nolabel.settext(integer.tostring(nocounter)); EDAA30 (Sem8) HT1 2015 28 / 49
Lösning 2: en lyssnare per komponent Anrop av addactionlistener I start knyter vi lyssnare till knappar med:... yesbutton.setonaction(new YesButtonHandler());... nobutton.setonaction(new NoButtonHandler());... EDAA30 (Sem8) HT1 2015 29 / 49
Lösning 2: en lyssnare per komponent Kommentarer I lösningen slipper vi nu undersöka vilken knapp som användaren klickat på. Det blir enklare att lägga till en komponent. Men men det blir många små lyssnarklasser om man har många komponenter. De lösningar vi visat separerar inte det grafiska användargränssnittet (vyn) från modellen. Det är önskvärt att separera dessa. vyn kan man ofta vilja ändra medan modellen ligger fast. Vissa operationer, t.ex. load/save, berör bara modellen. Det blir besvärligt att implementera och testa dessa om vy och modell blandas samman. En ännu bättre lösning finns alltså (se följande bilder). EDAA30 (Sem8) HT1 2015 30 / 49
Lösning 3 - modell-vy En tredje lösning, som löser problemen på föregående bild; Vi separerar modell och vy. När modellen separeras från det grafiska modellen blir det lättare att skapa metoder som bara berör modellen, t.ex. att spara dokumentet till en fil. Detaljer finns på de följande bilderna. EDAA30 (Sem8) HT1 2015 31 / 49
Lösning 3: designen Button EventHandler <ActionEvent> Counters yescount: int nocount: int incrementyescount() incrementnocount() getyescount(): int getnocount(): int YesButton CountersView yeslabel: JLabel nolabel: JLabel updateyeslabel() updatenolabel() EDAA30 (Sem8) HT1 2015 32 / 49
Lösning 3: modellen Klassen Counters med de båda räknarna public class Counters { private int yescount; private int nocount; public Counters() { yescount = 0; nocount = 0; public void incrementyescount() { yescount++; public void incrementnocount() { nocount++; public int getyescount() { return yescount; public int getnocount() { return nocount; EDAA30 (Sem8) HT1 2015 33 / 49
Lösning 3: vyn Klassen CountersView public class CountersView extends Application { private Counters counters; private Label yeslabel, nolabel; public CountersView(Counters counters) { this.counters = counters; public void start(stage stage) throws Exception {... skapa knappar och etiketter... public void updateyeslabel() { yeslabel.settext("yescount: " + counters.getyescount()); public void updatenolabel() { nolabel.settext("nocount: " + counters.getnocount()); EDAA30 (Sem8) HT1 2015 34 / 49
Lösning 3: knappar som lyssnar på sig själva Klassen YesButton public class YesButton extends Button implements EventHandler<ActionEvent> { private Counters document; private CountersView view; public YesButton(Counters counters, CountersView view) { super("yes!"); document = counters; this.view = view; setonaction(this); public void handle(actionevent e) { document.incrementyescount(); view.updateyeslabel(); EDAA30 (Sem8) HT1 2015 35 / 49
Lösning 3: huvudprogram Klassen Main Modellen och vyn kopplas ihop i main-metoden: public class Main { public static void main(string[] args) { Counters counters = new Counters(); CountersView view = new CountersView(counters); view.launch(args); EDAA30 (Sem8) HT1 2015 36 / 49
Ytterligare refaktoriseringar Knappklasserna, YesButton och NoButton, innehåller nästan likadan kod. Duplicerad kod är någonting man bör undvika. De båda knappklasserna skulle därför kunna slås ihop till en klass. Anm. Med refaktorisering menas att man förbättrar programkoden utan att programmets funktion ändras. Syftet med refaktoriseringen är att programkoden ska bli mer lättläst och lättare att underhålla. EDAA30 (Sem8) HT1 2015 37 / 49
Andra problem Kommentarer Alla lösningar som visats innehåller mycket kod. Det finns alltid en EventHandler för varje knapp. Liknande funktionalitet är spridd i flera klasser och i flera filer. Det finns mycket kod som skymmer funktionaliteten, d.v.s ökning av räknaren, counter++ class... implements EventHandler<ActionEvent> public void handle(actionevent event) Dessa problem kan minskas med anonyma klasser och lambda-uttryck. EDAA30 (Sem8) HT1 2015 38 / 49
Annonyma klasser Klasser som bara instansieras en gång behöver inte namnges och kan skapas inline vid new. button.setonaction(new EventHandler<ActionEvent>() { public void handle(actionevent event) { counter++; label.settext(integer.tostring(counter)); ); Det blir nu tydligare vilken funktionalitet som kopplas till vilken knapp. EDAA30 (Sem8) HT1 2015 39 / 49
Lambda-uttryck Förenklat är lambdauttryck anonyma klasser där du bara skriver innehållet i en metod. Kompilatorn listar ut vilken och alla typer från sammanhanget. button.setonaction(event -> { counter ++; label.settext(integer.tostring(counter)); ); Lambdauttryck skapar objekt, d.v.s. används normalt istället för new. Värden som är synliga i ett lambda-uttryck: attribut i den omgivande klassen variabler som är final i den omgivande metoden EDAA30 (Sem8) HT1 2015 40 / 49
Lambda-uttryck syntax Lambdauttryck skrivs på formen ( formella parametrar ) -> { metodkropp typer på parametrar behöver inte anges är det bara en parameter kan även () utelämnas består kroppen av ett uttryck kan { och utelämnas består kroppen av ett uttryck kan även return utelämnas (Typ1 p1, Typ2 p2) -> { return p1 * p2; (p1, p2) -> p1 * p2 p -> p * p EDAA30 (Sem8) HT1 2015 41 / 49
Fler komponenter MenuBar, Menu, MenuItem Menyer För att välja mellan alternativ som kan grupperas i en eller flera menyer. Händelsehantering analogt med knappar. Kommer att användas på laboration 3 och 6. Menu MenuBar MenuItem EDAA30 (Sem8) HT1 2015 42 / 49
Fler komponenter CheckBox, RadioButton, ChoiceBox CheckBox För att välja ett eller flera alternativ. RadioButton För att välja bland alternativ men bara ett alternativ för varje grupp av radioknappar kan vara valt. ChoiceBox En drop-down-lista. För att göra ett val bland en lista av alternativ EDAA30 (Sem8) HT1 2015 43 / 49
Fler komponenter TextField, TextArea TextField Ett editerbart textfält en rad. När användare skriver in radslutstecken genereras ActionEvent. textfield.gettext() returnerar den sträng som skrivits i fältet. TextArea Flera rader. Kan sättas uneditable och användas för att visa flera rader text. Kan vara editable och användas för att skriva in flera rader text. Radslut genererar ActionEvent. textarea.gettext() returnerar det som skrivits (i form av en sträng). EDAA30 (Sem8) HT1 2015 44 / 49
Fler komponenter dialogrutor Exempel, TextInputDialig public void start(stage arg0) throws Exception { TextInputDialog dialog = new TextInputDialog(" "); dialog.settitle("compute Square root"); dialog.setheadertext(""); dialog.setcontenttext("please enter a number:"); double n = 0; Optional<String> result = dialog.showandwait(); EDAA30 (Sem8) HT1 2015 45 / 49
Fler komponenter dialogrutor Använda Optional och Alert Optional<String> result = dialog.showandwait(); if (result.ispresent()){ try { n = Double.valueOf(result.get()); double sqrroot = Math.sqrt(n); Alert alert = new Alert(AlertType.INFORMATION); alert.settitle("result"); alert.setheadertext(null); alert.setcontenttext(double.tostring(sqrroot)); alert.showandwait(); catch (NumberFormatException e) { Alert alert = new Alert(AlertType.ERROR); alert.settitle("error in input"); alert.setheadertext(null); alert.setcontenttext("wrong input"); alert.showandwait(); EDAA30 (Sem8) HT1 2015 46 / 49
Fler komponenter Listor ListView För att visa en skrollbar lista i en vy finns klassen ListView. Till en ListView kopplas modellen av listan av typ ObservableList<T>. ObservableList<T> är ett interface. Det finns färdiga implementeringar av interfacet t.ex. ObservableArrayList<T>. Alla modifieringar av listan (modellen) görs via operationer på den modell som kopplas till vyn. Dessa modifieringar visas automatiskt i vyn av listan. Det finns operationer, SelectionModel och FocusModel, för att ta reda på vad som är markerat i vyn. EDAA30 (Sem8) HT1 2015 47 / 49
Grafik Man kan enkelt rita figurer som linjer, cirklar, rektanglar... De geometriska figurer finns i paketet javafx.scene.shape. Lägg till dem i scenen på samma sätt som andra GUI-komponenter, container.getchildren().add(new Circle(x, y, r)); EDAA30 (Sem8) HT1 2015 48 / 49
Avsluta program Exempel, TextInputDialig Ett JavaFX program avslutas när alla fönster stängts, av användaren eller programmet. Om du vill avsluta programmet, t.ex. från en händelsehanterare, anropar du Platform.exit(). stop i din Application-klass anropas i båda fallen ovan. Om du anropar System.exit(status) kommer stop inte att anropas. EDAA30 (Sem8) HT1 2015 49 / 49