Issue
I'm trying to show the process of a maze being generated on screen, but the screen never updates until the process is complete. I'm using Consumers to notify the JavaFX side. I've tried using Platform.runLater()
to no avail.
"hello-view.fxml"
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<StackPane onMouseClicked="#randomize" fx:id="sp" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.demo2.HelloController">
</StackPane>
Here's a truncated version of the maze generator, "BasicMaze.java":
package com.example.demo2;
import java.util.*;
import java.util.function.Consumer;
public class BasicMaze {
public int width;
public int height;
private transient List<Consumer<BasicMaze>> consumers = new ArrayList<>();
public void reset(int w, int h) {
width = w;
height = h;
alertConsumers();
}
public void addConsumer(Consumer<BasicMaze> l) {
if (consumers == null) consumers = new ArrayList<>();
consumers.add(l);
}
public void alertConsumers() {
consumers.forEach(c -> c.accept(this));
}
public void generate() {
int slots = width * height;
do {
if (Math.random() * 10 < 1) {
alertConsumers(); //Platform.runLater tried here
slots--;
}
} while (slots > 1);
System.out.println("DONE!");
}
}
Here's the Controller, "HelloController.java":
package com.example.demo2;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
public class HelloController {
@FXML
StackPane sp;
final Canvas canvas = new Canvas(250,250);
BasicMaze maze = new BasicMaze();
@FXML
public void initialize() {
sp.getChildren().add(canvas);
maze.addConsumer(basicMaze -> this.draw()); //platform.runLater tried here.
}
public void randomize() {
maze.reset(5, 5);
maze.generate();
}
public void draw() {
GraphicsContext gc = canvas.getGraphicsContext2D() ;
gc.setLineWidth(1.0);
gc.setFill(Color.BLACK);
for (int j = 0; j < maze.height; j++) {
for (int i = 0; i < maze.width; i++) {
gc.moveTo(Math.random()*250, Math.random()*250);
gc.lineTo(Math.random()*250, Math.random()*250);
gc.stroke();
}
}
}
}
And here's the main function stub:
package com.example.demo2;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
If you run this and click on the screen, "DONE!" will appear on the console, there'll be a lag, then the screen will present a bunch of black lines. I'm trying to get the screen to update as each line is drawn.
Solution
OK, this actually turns out to be super easy (barely an inconvenience!), which I sussed out from some of Sergey Grinev's posts. All that has to be changed (almost) is to run the background task on its own thread.
So, we wrap the maze.generate()
in a thread:
public void randomize() {
maze.reset(500, 500);
new Thread(maze::generate).start();
}
Now the runLater in the initialize will work!
@FXML
public void initialize() {
sp.getChildren().add(canvas);
maze.addConsumer(basicMaze -> Platform.runLater(this::draw));
}
The underlying object doesn't need to change at all.
Answered By - user3810626
Answer Checked By - Cary Denson (JavaFixing Admin)