[[oktatas:programozás:java:java_fx|< Java FX]]
====== JavaFX - Eseménykezelés ======
* **Szerző:** Sallai András
* Copyright (c) 2025, Sallai András
* Licenc: [[https://creativecommons.org/licenses/by-sa/4.0/|CC Attribution-Share Alike 4.0 International]]
* Web: https://szit.hu
===== 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.
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 :
@FXML
private Button button;
@FXML
void initialize() {
button.setOnAction(e -> startButton());
}
void startButton() {
System.out.println("start button");
}
Használhatunk metódusreferenciát is:
@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() {
@Override
public void handle(ActionEvent event) {
startButton();
}
});
}
void startButton() {
System.out.println("start button");
}
Az utóbbihoz tartozó teljeskód:
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() {
@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** interfészt.
A példánkban egy beépített osztályt használunk. Teljes kód:
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 {
@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.
A Java kód ebben az esetben:
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:
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 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:
//...
public ListView getListView() {
return listView;
}
//...
A fájlba írás a teljesség kedvéért:
package com.example;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
public class Storage {
public static void writeContent(List content) {
try {
tryWriteContent(content);
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
public static void tryWriteContent(List 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() {
@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();