- COMP.CS.140
- 12. Graafiset käyttöliittymät
- 12.2 JavaFX-kirjasto
- 12.2.3 Hiukan isompi esimerkki
Hiukan isompi esimerkki¶
Käyttöliittymien toteutuksessa käytetään yleensä valmiiksi toteutettuja kirjastokomponentteja. Yksinkertaisimpia tällaisia ovat nappula (engl. Button) ja tekstikenttä (engl. TextField). Listan tavallisista komponenteista löydät JavaFx-tutoriaalista.
Jokaisella elementillä on sijainti ruudulla. Sijainnin voi määritellä useammalla erilaisella elementillä.
Aiempi esimerkki käytti FlowPanea
. Muita ladontaruutuja:
BorderPane: asemoi lapsielementtinsä ylös, alas, vasemalle, oikealle ja keskelle
HBox: asemoi lapsielementtinsä vaakasuoraan riviin
VBox: asemoi lapsielementtinsä pystysuoraksi sarakkeeksi
GridPane: asemoi lapsielementtinsä joustavaan ruudukkoon rivejä ja sarakkeita
TilePane: asemoi lapsielementtinsä vakiokokoisten ruutujen ruudukkoon
Ladontoja voi myös asettaa sisäkkäin. Ladonnat sijaitsevat pakkauksessa javafx.scene.layout. Otetaan esimerkki:
package example.range;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
// Toteutetaan ohjelma, joka laskee sähköauton odotettavissa olevan käyttömatkan
// sen hetkisellä kulutuksella
public class App extends Application {
@Override
public void start(Stage stage) {
// Ohjelman pääikkunan otsikoksi asetetaan "Range Calculator"
stage.setTitle("Range Calculator");
// Käytetään käyttöliittymäkomponenttien asemointiin
// ruutuasettelua
GridPane grid = new GridPane();
// Luodaan skene, johon ruudukko sijoitetaan
Scene scene = new Scene(grid, 350, 275);
stage.setScene(scene);
// Text luo yksinkertaisen tekstialueen
Text scenetitle = new Text("Calculate expected range");
// jonka voi sijoittaa ruudukolle
// 0,0 tarkoittaa vasenta yläkulmaa
// lisäksi määritellään, kuinka monta saraketta ja riviä elementti kattaa
grid.add(scenetitle, 0, 0, 2, 1);
// Vastaavasti voidaan lisätä paikoilleen tarvittavat syötekentät
// Label eroaa Textistä siinä, ettei Labelin sisältöä voi muuttaa
Label battery = new Label("Battery kWh:");
grid.add(battery, 0, 1);
Label consumption = new Label("Consumption kWh/100km");
grid.add(consumption, 0, 2);
Label result = new Label("Current Range");
grid.add(result, 0, 3);
// Lisätään ruudukkoon vielä tekstikentät
// akuston kapasiteetille
TextField inputBatt = new TextField();
grid.add(inputBatt, 1, 1);
// sen hetkiselle keskikulutukselle
TextField inputCon = new TextField();
grid.add(inputCon, 1, 2);
// ja tulokselle
TextField resultField = new TextField();
grid.add(resultField, 1, 3);
// Ohjelman kolme nappulaa ladotaan vaakasuoraan
// HBoxin avulla
// Rakentajan parametri määrittää elementtien välisen tilan suuruuden
HBox hbBtn = new HBox(10);
grid.add(hbBtn, 1, 5);
Button btn1 = new Button("Calculate");
hbBtn.getChildren().add(btn1);
Button btn2 = new Button("Clear");
hbBtn.getChildren().add(btn2);
Button exitBtn = new Button("Exit");
hbBtn.getChildren().add(exitBtn);
// Nappula laskee käyttömatkan syötekenttien arvojen mukaisesti
// Huom! ei sisällä virhetarkasteluja!
btn1.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
var b = Double.parseDouble(inputBatt.getText());
var c = Double.parseDouble(inputCon.getText());
var r = (b/c)*100;
resultField.setText(String.format("%.2f", r));
}
});
// Nappulan painaminen tyhjentää kentät
btn2.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
inputBatt.clear();
inputCon.clear();
resultField.clear();
}
});
// Nappulan painaminen sulkee ohjelman
// ensin suljetaan stage
exitBtn.setOnAction((event) -> {stage.close();});
// ja stagen sulkeminen sulkee koko ohjelman
stage.setOnCloseRequest((event) -> { Platform.exit();});
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Ohjelma näyttää tältä:
Useiden näkymien käyttö¶
Ohjelmassa on usein tilanteita, joissa tarvitaan enemmän kuin esimerkeissä käytetty yksi ikkuna. Skenejä voi luoda useita ja niiden välillä vaihtaa tapahtumaan sidotusti.
public class App extends Application {
@Override
public void start(Stage stage) {
Button printButton = new Button();
printButton.setText("Print");
TextField output = new TextField();
output.setPrefWidth(250);
Button switchButton = new Button();
switchButton.setText("Switch");
// Nappulat asemoidaan flowpanella
var group = new FlowPane();
group.getChildren().add(printButton);
group.getChildren().add(switchButton);
group.getChildren().add(output);
printButton.setOnAction((event) -> {
output.setText("And all the men and women merely players;");
});
printButton.setOnKeyPressed( new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent k) {
if (k.getCode().equals(KeyCode.ENTER)) {
output.clear();
}
}
});
// Ensimmäinen skene ohjelman ikkunalle
Scene scene = new Scene(group, 300, 150);
stage.setScene( scene );
stage.setTitle("All the world's a stage");
// Gridpane ja uusi skene toiselle näkymälle
GridPane grid = new GridPane();
Scene scene2 = new Scene(grid, 350, 275);
// Toinen skene sisältää nappulat poistumiseen
// ja paluuseen ensimmäiseen näkymään
Button exitButton = new Button("Exit");
Button switchBack = new Button("Back");
// jotka asemoidaan gridpanella
grid.add(exitButton, 0,1);
grid.add(switchBack, 0,0);
// Ykkösnäkymässä nappula vaihtaa kakkosnäkymään
switchButton.setOnAction((event) -> {
stage.setScene(scene2);
});
// ja kakkosnäkymässä nappula palaa takaisin ykkösnäkymään
switchBack.setOnAction((event) -> {
stage.setScene(scene);
});
exitButton.setOnAction((event) -> {stage.close();});
stage.setOnCloseRequest((event) -> {Platform.exit();});
stage.show();
}
public static void main(String[] args) {
launch( args );
}
}
Tapahtumakäsittelijän sisällä voidaan luoda myös kokonaan uusi ikkuna ohjelmaan.
Toimintalogiikka erilleen!¶
On tärkeää pitää ohjelman toimintalogiikka erillään käyttöliittymän toteutuksesta.
start()
-funktion alussa voi luoda ohjelman tarvitsemia olioita, jotka toteuttavat ohjelman toimintalogiikan.
Vältä ns. jumalluokkia. Jokaisella luokalla pitäisi olla selkeä vastuualue ohjelmassa.