Changing cursor in JavaFX application for long operations


Question

I would like to change the cursor to Cursor.WAIT when executing potentially long operations in JavaFX application. I thought that Platform.runLater will help me:

this.getStage().getScene().setCursor(Cursor.WAIT);

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        // Do some stuff
        getStage().getScene().setCursor(Cursor.DEFAULT);
    }
});

But cursor does not change. Why doesn't this work?

1
9
12/13/2013 12:33:26 AM

Accepted Answer

Assuming you are running the following code off of the JavaFX Application thread, a pattern for displaying a wait cursor could be:

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        getStage().getScene().setCursor(Cursor.WAIT);
    }
});

// Do some stuff

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        getStage().getScene().setCursor(Cursor.DEFAULT);
    }
});

Or, if you are already on the JavaFX application thread, you could use a Service:

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ServiceWithCursorControl extends Application {
    private final Button runButton = new Button("Run");
    private final Label label = new Label();

    private final Service service = new Service() {
        @Override
        protected Task createTask() {
            return new Task<Void>() {
                @Override protected Void call() throws Exception {
                    // do stuff
                    Thread.sleep(5000);
                    return null;
                }
            };
        }
    };

    @Override public void start(Stage stage) {
        VBox layout = createLayout();

        Scene scene = new Scene(layout, 100, 80);
        stage.setScene(scene);

        bindUIandService(stage);

        stage.show();
    }

    private void bindUIandService(Stage stage) {
        label.textProperty()
                .bind(
                        service.stateProperty().asString()
                );

        stage.getScene()
                .getRoot()
                .cursorProperty()
                .bind(
                        Bindings
                            .when(service.runningProperty())
                                .then(Cursor.WAIT)
                                .otherwise(Cursor.DEFAULT)
                );

        runButton
                .disableProperty()
                .bind(
                        service.runningProperty()
                );

        runButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                service.restart();
            }
        });
    }

    private VBox createLayout() {
        VBox layout = new VBox(10);

        layout.getChildren().setAll(runButton, label);
        layout.setPadding(new Insets(10));
        layout.setAlignment(Pos.CENTER);

        return layout;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

It's unclear from your description which approach fits your problem best, but hopefully you can work it out.

17
12/13/2013 8:19:15 AM

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