One of the options is when the drawing control is executed from another thread, and the drawing itself is executed in the "JavaFX Application Thread" stream:
// Controller.java:
package iterate; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; import javafx.scene.layout.Pane; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; public class Controller { @FXML private Pane idPane; @FXML private Label label; @FXML private ProgressBar progressBar; @FXML private Button buttonStart; @FXML private Button buttonStop; private Task<Void> task; @FXML private void initialize() { startDraw(null); } private Task<Void> createTask() { return new Task<Void>() { @Override protected Void call() { try { draw(); } catch (Exception ex) { updateMessage(ex.getMessage()); } return null; } private void draw() { float step = 0.2f; float mash = 20; Platform.runLater(() -> idPane.getChildren().clear()); updateMessage("Рисование начато"); for (float i = 0; i < 430 / mash; i += step) { if (isCancelled()) { updateMessage("Рисование прервано"); return; } Platform.runLater(new Runnable() { float i; @Override public void run() { MoveTo moveTo = new MoveTo(i * mash, Math.sin(i) * mash + 50); LineTo lineTo = new LineTo(((i + step) * mash), Math.sin(i + step) * mash + 50); Path path = new Path(moveTo, lineTo); idPane.getChildren().add(path); updateMessage("Отрисован шаг: i = " + i); updateProgress(i, 430 / mash); } Runnable param(float i) { this.i = i; return this; } }.param(i)); try { Thread.sleep(100); } catch (InterruptedException interrupted) { if (isCancelled()) { updateMessage("Рисование прервано"); return; } } } updateMessage("Рисование завершено"); } @Override protected void updateMessage(String message) { System.out.println(message); super.updateMessage(message); } }; } public void startDraw(ActionEvent event) { if (task != null && task.isRunning()) { task.cancel(); } task = createTask(); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); label.textProperty().bind(task.messageProperty()); progressBar.progressProperty().bind(task.progressProperty()); buttonStart.disableProperty().bind(task.runningProperty()); buttonStop.disableProperty().bind(task.runningProperty().not()); } public void cancelDraw(ActionEvent event) { if (task != null) task.cancel(); } }
// sample.fxml:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.Pane?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.control.ProgressBar?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.control.Button?> <BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="iterate.Controller"> <center> <Pane fx:id="idPane"/> </center> <bottom> <VBox> <Label fx:id="label"/> <ProgressBar fx:id="progressBar"/> <HBox> <Button fx:id="buttonStart" onAction="#startDraw" text="Поехали!" /> <Button fx:id="buttonStop" disable="true" onAction="#cancelDraw" text="Стоп" /> </HBox> </VBox> </bottom> </BorderPane>
// Main.java:
package iterate; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("sample.fxml")); Scene scene = new Scene(root, 430, 200); primaryStage.setScene(scene); primaryStage.show(); } }
The advantage of this approach is that all calculations can be performed in the background thread, and in the JavaFX stream, only the output of the results. This will preserve the responsiveness of the interface with long calculations. For example:
private void draw() { float step = 0.2f; float mash = 20; Platform.runLater(() -> idPane.getChildren().clear()); updateMessage("Рисование начато"); for (float i = 0; i < 430 / mash; i += step) { if (isCancelled()) { updateMessage("Рисование прервано"); return; } // Расчеты, подготовка данных для вывода: MoveTo moveTo = new MoveTo(i * mash, Math.sin(i) * mash + 50); LineTo lineTo = new LineTo(((i + step) * mash), Math.sin(i + step) * mash + 50); updateMessage("Рисование: i = " + i); updateProgress(i, 430 / mash); Platform.runLater(new Runnable() { MoveTo moveTo; LineTo lineTo; @Override public void run() { // Вывод результатов: Path path = new Path(moveTo, lineTo); idPane.getChildren().add(path); } Runnable param(MoveTo moveTo, LineTo lineTo) { this.moveTo = moveTo; this.lineTo = lineTo; return this; } }.param(moveTo, lineTo)); try { Thread.sleep(100); } catch (InterruptedException interrupted) { if (isCancelled()) { updateMessage("Рисование прервано"); return; } } } updateMessage("Рисование завершено"); }
Another option is to perform everything in the JavaFX stream using one of the JavaFX timers: you can use AnimationTimer with skipping unnecessary iterations or Timeline with one KeyFrame , in which you can specify the desired interval. Note that in this case, Thread.sleep() not needed.
An example of the Timeline can be seen in this answer: https://ru.stackoverflow.com/a/810260/291565