Bonfra
Bonfra

Reputation: 296

Stage.close() doesn't work javafx

I've got a main window and when I press a button on it, another window will open. After a certain amount of time, this window should close. Because it extends stage I use super.close, but it doesn't work. What can I do? Can it be caused because I close it inside a service task?

public Game(int width, int height, int time) {
    this.width = width - 15;
    this.height = height - 15;
    this.time = time;

    this.layout = generateLayout();
    this.scene = new Scene(this.layout, this.width, this.height);

    super.initModality(Modality.APPLICATION_MODAL);
    super.setScene(scene);
    super.setResizable(false);
    super.setTitle(TITLE);

    this.cicle.reset();
    this.cicle.start();

    super.showAndWait();
}

and inside the cicle I call close()

It's a bit confused, but this is the cicle service task:

Service<Void> cicle = new Service<Void>() {
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            volatile boolean waiting = false;

            @Override
            protected Void call() throws Exception {
                Timer.start();
                while (Timer.update() / 1000 < time) {
                    System.out.println(Timer.update())
                }
                close();
                return null;
            }
        };
    }
};

Upvotes: 0

Views: 1460

Answers (1)

Slaw
Slaw

Reputation: 46170

The issue is you are attempting to close the Stage from a thread other than the JavaFX Application Thread. Looking at the documentation of Stage you'll see:

Stage objects must be constructed and modified on the JavaFX Application Thread.

The JavaFX Application Thread is created as part of the startup process for the JavaFX runtime. See the Application class and the Platform.startup(Runnable) method for more information.

Yet a Service, when started, will execute its Task on a background thread. Specifically, the call() method of the Task is executed on said background thread. I just tested trying to close a Stage on a background thread to see what happens (whether or not an exception would be thrown). The result was an java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3.

The reason you aren't seeing this is you aren't observing the Service for failure. You can look at the answer to this question to see ways to watch for failure. The answer specifically talks about Task but the same behavior is present in Service.

What you need to do to make your code work is to make sure that close() is called on the JavaFX Application Thread. The easiest way to do this would be to wrap close() in a Platform.runLater(Runnable) call.

Service<Void> cicle = new Service<Void>() {
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            volatile boolean waiting = false;

            @Override
            protected Void call() throws Exception {
                Timer.start();
                while (Timer.update() / 1000 < time) {
                    System.out.println(Timer.update())
                }
                Platform.runLater(() -> close()); // wrapped in Platform#runLater
                return null;
            }
        };
    }
};

You could also override Service.succeeded() which will always be called on the JavaFX Application Thread (but only if the Service succeeds). In this case, you'd remove the call to close() from the call() method.

Service<Void> cicle = new Service<Void>() {
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            volatile boolean waiting = false;

            @Override
            protected Void call() throws Exception {
                Timer.start();
                while (Timer.update() / 1000 < time) {
                    System.out.println(Timer.update())
                }
                return null;
            }
        };
    }
    @Override
    protected void succeeded() {
        close();
    }

};

There are other ways as well: listening to the state property, using setOnSucceeded(EventHandler), etc...

Upvotes: 1

Related Questions