JavaFX: Stage close handler

JavaJavafxHandlerJavafx 8

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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).

Solution 2 - Java

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));

Solution 3 - Java

Ahh this is a known bug in JavaFX where the Stage will not close if a modal dialog is present at the time of closing. I will link you to the bug report which I just saw today. I think it is fixed in the latest release.

Here you go:

https://bugs.openjdk.java.net/browse/JDK-8093147?jql=text%20~%20%22javafx%20re-entrant%22

resolved in 8.4 it says. I think this what you are describing.

Solution 4 - Java

public Stage getParentStage() {
    return (Stage) getFxmlNode().getScene().getWindow();
}

btnCancel.setOnAction(e -> {
    getParentStage().close();
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHeine FradeView Question on Stackoverflow
Solution 1 - JavaJosé PeredaView Answer on Stackoverflow
Solution 2 - JavaDomenicoView Answer on Stackoverflow
Solution 3 - Javauser9659191View Answer on Stackoverflow
Solution 4 - JavaKim DequillaView Answer on Stackoverflow