JavaFX: Stage close handler


Question

I want to save a file before closing my JavaFX application.

This is how I'm setting up the handler in Main::start:

primaryStage.setOnCloseRequest(event -> {
    System.out.println("Stage is closing");
    // Save file
});

And the controller calling Stage::close when a button is pressed:

@FXML
public void exitApplication(ActionEvent event) {
    ((Stage)rootPane.getScene().getWindow()).close();
}

If I close the window clicking the red X on the window border (the normal way) then I get the output message "Stage is closing", which is the desired behavior.

However, when calling Controller::exitApplication the application closes without invoking the handler (there's no output).

How can I make the controller use the handler I've added to primaryStage?

1
43
10/28/2014 10:24:15 PM

Accepted Answer

If you have a look at the life-cycle of the Application class:

The JavaFX runtime does the following, in order, whenever an application is launched:

  1. Constructs an instance of the specified Application class
  2. Calls the init() method
  3. Calls the start(javafx.stage.Stage) method
  4. Waits for the application to finish, which happens when either of the following occur:
    • the application calls Platform.exit()
    • the last window has been closed and the implicitExit attribute on Platform is true
  5. Calls the stop() method

This means you can call Platform.exit() on your controller:

@FXML
public void exitApplication(ActionEvent event) {
   Platform.exit();
}

as long as you override the stop() method on the main class to save the file.

@Override
public void stop(){
    System.out.println("Stage is closing");
    // Save file
}

As you can see, by using stop() you don't need to listen to close requests to save the file anymore (though you can do it if you want to prevent window closing).

82
5/23/2016 6:57:43 PM

Suppose you want to ask the user if he want to exit the application without saving the work. If the user choose no, you cannot avoid the application to close within the stop method. In this case you should add an EventFilter to your window for an WINDOW_CLOSE_REQUEST event.

In your start method add this code to detect the event:

(Note that calling Platform.exit(); doesn't fire the WindowEvent.WINDOW_CLOSE_REQUEST event, see below to know how to fire the event manually from a custom button)

// *** Only for Java >= 8 ****
// ==== This code detects when an user want to close the application either with
// ==== the default OS close button or with a custom close button ====

primaryStage.getScene().getWindow().addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, this::closeWindowEvent);

Then add your custom logic. In my example i use an Alert popup to ask the user if he/she want to close the application without saving.

private void closeWindowEvent(WindowEvent event) {
        System.out.println("Window close request ...");

        if(storageModel.dataSetChanged()) {  // if the dataset has changed, alert the user with a popup
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.getButtonTypes().remove(ButtonType.OK);
            alert.getButtonTypes().add(ButtonType.CANCEL);
            alert.getButtonTypes().add(ButtonType.YES);
            alert.setTitle("Quit application");
            alert.setContentText(String.format("Close without saving?"));
            alert.initOwner(primaryStage.getOwner());
            Optional<ButtonType> res = alert.showAndWait();

            if(res.isPresent()) {
                if(res.get().equals(ButtonType.CANCEL))
                    event.consume();
            }
        }
    }

The event.consume() method prevents the application from closing. Obviously you should add at least a button that permit the user to close the application to avoid the force close application by the user, that in some cases can corrupt data.

Lastly, if you have to fire the event from a custom close button, you can use this :

Window window = Main.getPrimaryStage()  // Get the primary stage from your Application class
                .getScene()
                .getWindow();

window.fireEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSE_REQUEST));

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon