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ä:

JavaFX program for a range calculator

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.

Ohjelmointidemo (kesto 9:09)

Käyttöliittymiä toteutettaessa