Desidero creare un'applicazione che esegua molti rendering in un'area di disegno. Il normale modo JavaFX blocca la GUI: è davvero difficile premere il pulsante nel codice dell'applicazione sottostante (eseguito con Java 8).Attività di rendering pesante (in tela) in blocchi JavaFX GUI
Ho cercato sul Web, ma JavaFX non supporta il rendering in background: tutte le operazioni di rendering (come strokeLine) vengono archiviate in un buffer e vengono eseguite successivamente nel thread dell'applicazione JavaFX. Quindi non posso nemmeno usare due tele e scambiare poi dopo il rendering.
Anche javafx.scene.Node.snapshot (SnapshotParameters, WritableImage) non può essere utilizzato per creare un'immagine in un thread in background, poiché deve essere eseguito all'interno del thread dell'applicazione JavaFX e pertanto bloccherà anche la GUI.
Qualche idea per avere una GUI non bloccante con molte operazioni di rendering? (Voglio solo di premere pulsanti, ecc, mentre il rendering viene eseguito in qualche modo in background o in pausa regolarmente)
package canvastest;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
public class DrawLinieTest extends Application
{
int interations = 2;
double lineSpacing = 1;
Random rand = new Random(666);
List<Color> colorList;
final VBox root = new VBox();
Canvas canvas = new Canvas(1200, 800);
Canvas canvas2 = new Canvas(1200, 800);
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<?> drawShapesFuture;
{
colorList = new ArrayList<>(256);
colorList.add(Color.ALICEBLUE);
colorList.add(Color.ANTIQUEWHITE);
colorList.add(Color.AQUA);
colorList.add(Color.AQUAMARINE);
colorList.add(Color.AZURE);
colorList.add(Color.BEIGE);
colorList.add(Color.BISQUE);
colorList.add(Color.BLACK);
colorList.add(Color.BLANCHEDALMOND);
colorList.add(Color.BLUE);
colorList.add(Color.BLUEVIOLET);
colorList.add(Color.BROWN);
colorList.add(Color.BURLYWOOD);
}
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage primaryStage)
{
primaryStage.setTitle("Drawing Operations Test");
System.out.println("Init...");
// inital draw that creates a big internal operation buffer (GrowableDataBuffer)
drawShapes(canvas.getGraphicsContext2D(), lineSpacing);
drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);
System.out.println("Start testing...");
new CanvasRedrawTask().start();
Button btn = new Button("test " + System.nanoTime());
btn.setOnAction((ActionEvent e) ->
{
btn.setText("test " + System.nanoTime());
});
root.getChildren().add(btn);
root.getChildren().add(canvas);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private void drawShapes(GraphicsContext gc, double f)
{
System.out.println(">>> BEGIN: drawShapes ");
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
gc.setLineWidth(10);
gc.setLineCap(StrokeLineCap.ROUND);
long time = System.nanoTime();
double w = gc.getCanvas().getWidth() - 80;
double h = gc.getCanvas().getHeight() - 80;
int c = 0;
for (int i = 0; i < interations; i++)
{
for (double x = 0; x < w; x += f)
{
for (double y = 0; y < h; y += f)
{
gc.setStroke(colorList.get(rand.nextInt(colorList.size())));
gc.strokeLine(40 + x, 10 + y, 10 + x, 40 + y);
c++;
}
}
}
System.out.println("<<< END: drawShapes: " + ((System.nanoTime() - time)/1000/1000) + "ms");
}
public synchronized void drawShapesAsyc(final double f)
{
if (drawShapesFuture != null && !drawShapesFuture.isDone())
return;
drawShapesFuture = executorService.submit(() ->
{
drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);
Platform.runLater(() ->
{
root.getChildren().remove(canvas);
Canvas t = canvas;
canvas = canvas2;
canvas2 = t;
root.getChildren().add(canvas);
});
});
}
class CanvasRedrawTask extends AnimationTimer
{
long time = System.nanoTime();
@Override
public void handle(long now)
{
drawShapesAsyc(lineSpacing);
long f = (System.nanoTime() - time)/1000/1000;
System.out.println("Time since last redraw " + f + " ms");
time = System.nanoTime();
}
}
}
EDIT A cura il codice per dimostrare che un thread in background che invia le operazioni di disegno e di scambiare la tela non risolve il problema! Perché Tutte le operazioni di rendering (come strokeLine) vengono archiviate in un buffer e vengono eseguite successivamente nel thread dell'applicazione JavaFX.
In passato si disegnava su una bitmap e poi su un bitblt sullo schermo. –
* "Quindi non posso nemmeno usare due tele e scambiare dopo il rendering." * - dovresti essere in grado di farlo. La documentazione [GraphicsContext] (https://docs.oracle.com/javase/8/javafx/api/javafx/scene/canvas/GraphicsContext.html) dice: ** "Un Canvas contiene solo un GraphicsContext e un solo buffer Se non è collegato a nessuna scena, può essere modificato da qualsiasi thread, purché venga utilizzato solo da un thread alla volta. "** – Marco13
@Romain Hippeau: _" Nei vecchi tempi si disegnava una bitmap e poi bitblt sullo schermo "_: questo è quello che ho fatto nella vecchia versione con BufferedImage. Ma ora voglio una soluzione JavaFX pura. E come ho detto: JavaFX non consente di creare un'istantanea in un thread in background. – Mahe