Tartalomjegyzék

< Java FX

JavaFX - Eseménykezelés

Bevezetés

Ebben a leírásban VSCode JavaFX készítő bővítményét használtuk. A projekt amit kapunk FXML és Java kontroller fájlokkal dolgozik. Az alkalmazás belépési pontja pedig az App.java fájl.

Gomb

Az eseménykezelés megvalósítható az FXML fájlban meghatározott fx:id alapján is. Tegyük fel, hogy van egy button nevű nyomógombunk. Az initialize() metódusban a setOnAction() metódussal adhatom meg mit tegyünk kattintásra.

<Button 
    fx:id="button" 
    layoutX="270.0" 
    layoutY="187.0" 
    mnemonicParsing="false" 
    text="Button" />

A következő példában a setOnAction() számára egy névtelen függvényt adunk meg, ami egyébként egy lambda kifejezés :

MainController.java
    @FXML
    private Button button;
 
    @FXML
    void initialize() {
        button.setOnAction(e -> startButton());
    }
    void startButton() {
        System.out.println("start button");
    }

Használhatunk metódusreferenciát is:

MainController.java
    @FXML
    private Button button;
 
    @FXML
    void initialize() {
        button.setOnAction(this::startButton);
    }
 
    void startButton(ActionEvent event) {
        System.out.println("start button");
    }

A this::startButton a metódusreferencia, amivel megmondjuk melyik metódust kell végrehajtani kattintásra.

A két megvalósítás valójában ennek a rövidített változata:

    @FXML
    private Button button;
 
    @FXML
    void initialize() {
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                startButton();
            }
        });
    }
    void startButton() {
        System.out.println("start button");
    }

Az utóbbihoz tartozó teljeskód:

MainController.java
package com.example;
 
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
 
public class MainController {
 
    @FXML
    private Button button;
 
    @FXML
    void initialize() {
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                startButton();
            }
        });
    }
    void startButton() {
        System.out.println("start button");
    }
}

Külön osztályt is létrehozhatunk ami implementálja a EventHandler<ActionEvent> interfészt. A példánkban egy beépített osztályt használunk. Teljes kód:

MainController.java
package com.example;
 
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
 
public class MainController {
 
    @FXML
    private Button button;
 
    @FXML
    void initialize() {
        button.setOnAction(new ButtonHandler());
    }
    void startButton() {
        System.out.println("start button");
    }
    class ButtonHandler implements EventHandler<ActionEvent> {
        @Override
        public void handle(ActionEvent event) {
            startButton();
        }
    }
}

Eseménykezelő megadása

Az FXML fájlban megadhatjuk azt a metódust, amivel kezeljük az eseményt.

<Button 
    layoutX="270.0" 
    layoutY="187.0" 
    mnemonicParsing="false" 
    onAction="#onClickStartButton" 
    text="Button" />

A Java kód ebben az esetben:

MainController.java
package com.example;
 
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
 
public class MainController {
 
    @FXML
    private Button button;
 
    @FXML
    void onClickStartButton(ActionEvent event) {
        startButton();
    }    
 
    void startButton() {
        System.out.println("start button");
    }
 
}

Alkalmazás indítása

Az alkalmazás indításakor ha szeretnénk a betöltött kontrollerekkel dolgozni az initialize() metódusra van szükségünk a kontrollerben. A metódust jelöljük meg az FXML annotációval.

Egy kontrollerben:

    @FXML
    void initialize() {
        System.out.println("start");
    }

Tötlsük fel például egy listView-t:

    @FXML
    void initialize() {
        listView.getItems().addAll(
            "item1", 
            "item2", 
            "item3"
        );
    }

A kontroller konstruktora nem használható, mivel annak lefutása idején még nem léteznek UI elemek.

Alkalmazás bezárása

Ha az alkalmazás bezárására szeretnénk reagálni, akkor az App.java fájlban írjuk felül a stop() metódust.

    @Override
    public void stop() throws Exception {
        System.out.println("stop");
    }

Lista mentése bezáráskor

Tegyük fel, hogy a VSCode JavaFX generáló bővítményét használtuk, ezért kaptunk egy App.java fájlt, és használunk valamilyen kontrollert, például MainController. Az alkalmazás tartalmaz egy listView objektumot, aminek az elemeit szeretnénk fájlba menteni.

Mivel a bezárás eseménynél szükségünk lesz a kontrollerre, ezért vegyünk fel egy statikus mainController objektumot, ami a start() metódusban előkészítünk. Ehhez szükség van az fxmlLoadedr objektumra, ami alapértelmezetten csak a loadFXML() metódusból érhető el. Ezért a fxmlLoader objektumot globálissá tesszük az osztályon belül.

Teljes kód:

App.java
package com.example;
 
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
 
import java.io.IOException;
import java.util.List;
 
/**
 * JavaFX App
 */
public class App extends Application {
 
    private static Scene scene;
 
    //A statikus mainController
    private static MainController mainController;
 
    // globális fxmlLoader:
    private static FXMLLoader fxmlLoader;
 
    @Override
    public void start(Stage stage) throws IOException {
        scene = new Scene(loadFXML("mainScene"), 640, 480);        
        mainController = fxmlLoader.getController();
        stage.setScene(scene);
        stage.show();
    }
 
    @Override
    public void stop() throws Exception {
        System.out.println("stop");
        List<String> items = mainController.getListView().getItems();
        Storage.writeContent(items);
    }
 
    static void setRoot(String fxml) throws IOException {
        scene.setRoot(loadFXML(fxml));
    }
 
    private static Parent loadFXML(String fxml) throws IOException {
        //Eltávolítottuk a típust:
        fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
        return fxmlLoader.load();
    }
 
    public static void main(String[] args) {
        launch();
    }
 
}

A stop() metódusban így már elérhető a MainController osztály.

Esetünkben a MainControllerben a listView private elérésű, ezért készítünk hozzá egy gettert:

MainController.java
//...
    public ListView<String> getListView() {
        return listView;
    }
//...

A fájlba írás a teljesség kedvéért:

Storage.java
package com.example;
 
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
 
public class Storage {
  public static void writeContent(List<String> content) {
    try {
      tryWriteContent(content);
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
  }
  public static void tryWriteContent(List<String> content) throws IOException {
    FileWriter writer = new FileWriter("data.txt");
    for (String line : content) {
      writer.write(line + "\n");
    }
    writer.close();
  }
}

A megvalósítás megfordítható. Ha a stage objektumot globálissá tesszük az App osztályban, a kontrollerben elérhetővé válik, így reagálhatunk a Close eseményre.

A Close esemény

Reagálhatunk a Close eseményre is. Egészítsük ki a start() metódust:

    @Override
    public void start(Stage stage) throws IOException {
        scene = new Scene(loadFXML("mainScene"), 640, 480);        
        stage.setScene(scene);
        stage.show();
 
        stage.setOnCloseRequest(e -> {
            System.out.println("close");
        });
    }

A lambda kifejezés a következőt helyettesíti:

        stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            @Override
            public void handle(WindowEvent event) {
                System.out.println("close");
            }
        });

Az alkalmazás bezárásának megakadályozása a consume() metódus hívásával lehetséges:

        stage.setOnCloseRequest(e -> {
            System.out.println("close");
            e.consume();
        });

Ebben az esetben valószínűleg szeretnék valamilyen más eseményre kilépni az alkalmazásból. Kilépéshez használhatjuk a javafx.application.Platform osztályt.

import javafx.application.Platform;
//...
Platform.exit();