import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.geometry.Pos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; /** * A program that shows randomly generated "art". When the user * clicks a "Start" button, a new random artwork is generated every * two seconds. * * This program demonstrates using a thread for a very simple * animation. The thread uses Platform.runLater() to redraw * the canvas on the JavaFX application thread. */ public class RandomArtWithThreads extends Application { public static void main(String[] args) { launch(args); } //------------------------------------------------------------------- private Canvas canvas; // A panel where the random "art" is drawn private volatile boolean running; // Set to true while thread is started; // It is set to false as a signal to // the thread that it should stop. private Runner runner; // The thread that drives the animation. // Class Runner is a nested class, defined below. private Button startButton; // A button that is used to start // and stop the animation. public void start(Stage stage) { canvas = new Canvas(640,480); redraw(); // fill the canvas with white startButton = new Button("Start!"); startButton.setOnAction( e -> doStartOrStop() ); HBox bottom = new HBox(startButton); bottom.setStyle("-fx-padding: 6px; -fx-border-color: black; -fx-border-width: 3px 0 0 0"); bottom.setAlignment(Pos.CENTER); BorderPane root = new BorderPane(canvas); root.setBottom(bottom); Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("Click Start to Make Random Art!"); stage.setResizable(false); stage.show(); } /** * This class defines the threads that drive the animation. */ private class Runner extends Thread { public void run() { while (running) { Platform.runLater( () -> redraw() ); try { Thread.sleep(2000); // Wait two seconds between redraws. } catch (InterruptedException e) { } } } } /** * If called when an animation is running, this method * fills the canvas with a random work of "art". If no thread * is running, it just fills the canvas with white. */ private void redraw() { GraphicsContext g = canvas.getGraphicsContext2D(); double width = canvas.getWidth(); double height = canvas.getHeight(); if ( ! running ) { g.setFill(Color.WHITE); g.fillRect( 0, 0, width, height ); return; } Color randomGray = Color.hsb( 1, 0, Math.random() ); g.setFill(randomGray); g.fillRect( 0, 0, width, height ); int artType = (int)(3*Math.random()); switch (artType) { case 0: g.setLineWidth(2); for (int i = 0; i < 500; i++) { int x1 = (int)(width * Math.random()); int y1 = (int)(height * Math.random()); int x2 = (int)(width * Math.random()); int y2 = (int)(height * Math.random()); Color randomHue = Color.hsb( 360*Math.random(), 1, 1); g.setStroke(randomHue); g.strokeLine(x1,y1,x2,y2); } break; case 1: for (int i = 0; i < 200; i++) { int centerX = (int)(width * Math.random()); int centerY = (int)(height * Math.random()); Color randomHue = Color.hsb( 360*Math.random(), 1, 1); g.setStroke(randomHue); g.strokeOval(centerX - 50, centerY - 50, 100, 100); } break; default: g.setStroke(Color.BLACK); g.setLineWidth(4); for (int i = 0; i < 25; i++) { int centerX = (int)(width * Math.random()); int centerY = (int)(height * Math.random()); int size = 30 + (int)(170*Math.random()); Color randomColor =Color.color( Math.random(), Math.random(), Math.random() ); g.setFill(randomColor); g.fillRect(centerX - size/2, centerY - size/2, size, size); g.strokeRect(centerX - size/2, centerY - size/2, size, size); } break; } } /** * This method is called when the user clicks the Start button, * If no thread is running, it sets the signaling variable, running, * to true and starts a new thread; it also changes * the text on the Start button to "Stop". If the user clicks the button while * a thread is running, then a signal is sent to the thread to terminate, * by setting the value of the signaling variable, running, to false; * and the method also sets the text on the Start button back to "Start." */ private void doStartOrStop() { if (running == false) { // start a thread startButton.setText("Stop"); runner = new Runner(); running = true; // Set the signal before starting the thread! runner.start(); } else { // stop the running thread startButton.setDisable(true); // Disable button until thread exits, // so user can't start another // thread until after the current // thread exits. /* Set the value of the signaling variable to false as a signal * to the thread to terminate. */ running = false; redraw(); // Redraw the canvas, which will show only white since running = false. /* Wake the thread, in case it is sleeping, to get a more * immediate reaction to the signal. */ runner.interrupt(); /* Wait for the thread to stop before setting runner = null. * One second should be plenty of time for this to happen, but * in case something goes wrong, it's better not to wait forever. */ try { runner.join(1000); // Wait for thread to stop. } catch (InterruptedException e) { } runner = null; startButton.setText("Start"); startButton.setDisable(false); } } } // end RandomArtWithThreads