1

I've used Scenebuilder to place few shapes in my GUI (simplified version of my project). I would like the shapes to change colours but wait 2 seconds between changing colours. I want these changes to happen in my controller class after a button is pressed.

Circle1.setFill(YELLOW)
Wait(2 seconds)
Circle2.setFill(BLUE)

I'm not sure how to do that. I have read online about threading, but I don't really understand how to implement that from my Main and into my Controller class. Also, I could not really find any examples online. My Main class looks like:

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("File.fxml"));
        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();    
    }
}

Please help. Also, if you could provide an example would be helpful for me to understand as I could not really find one online that gives an example of this.

dstar19
  • 25
  • 1
  • 5
  • First of all your code above is not your controller instead I see your main class ( correct me if i am wrong ). Now i would suggest to actually get the fxml's controller and each time the button is been pressed to call a method inside your controller making the changes. To add a delay on changes you would need to make a task wrapped on a Runnable.runLater call cause you are making changes to the UI. Right now I am not to a PC to provide you an example so you will have to wait for someone else. – JKostikiadis Oct 15 '17 at 08:35
  • Ah yes, a typo infact. I have changed that now. For my project I have to do several things that autonomously change with a delay of few seconds. So it will be practical for me to attempt to create that task. Just to clarify, is that task Runnable.runLater call created in the Controller or Main? If its in the Main, how do I reference to it in the Controller. – dstar19 Oct 15 '17 at 08:48
  • Have a look here : https://stackoverflow.com/questions/20497845/constantly-update-ui-in-java-fx-worker-thread of course you can create your task inside your controller or in your main..it's up to you. – JKostikiadis Oct 15 '17 at 09:20

1 Answers1

2

Answering this question is easiest through an example I believe. So I've created a small Traffic Light application, since it allows me to use Circle and a timed sequence similar to your problem, whilst being a familiar concept for all.

I'll be using java.util.Timer alongside java.util.TimerTask for handling the sequence of lights. You may choose to use some animation / time line in JavaFX, but I think that is overkill for this kind of task.

I include the three files used in this project:

  • FXMLTrafficLight.fxml - which defines my FXML layout
  • FXMLTrafficLightController.java - my FXML controller
  • TrafficLightApplication.java - for completeness my subclass of Application, this is just the boiler plate.

FXMLTrafficLight.fxml

Not a fancy layout, just a VBox with three circles redLight, amberLight and greenLight, plus two Button objects startLights and stopLights used to start and stop the timer.

<VBox fx:id="root" id="VBox" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxtimer.FXMLTrafficLightController">
    <children>
        <Circle fx:id="redLight" radius="100"></Circle>
        <Circle fx:id="amberLight" radius="100"></Circle>
        <Circle fx:id="greenLight" radius="100"></Circle>
        <Button fx:id="startLights" text="Start Lights" onAction="#startLights"></Button>
        <Button fx:id="stopLights" text="Start Lights" onAction="#stopLights"></Button>
    </children>
</VBox>

FXMLTrafficLightController.java

I've included the model/state in the controller for simplicity. Whether a light is red / amber / green is determined by a boolean flag. The initial state is set in the initialize() method, and is updated by calling updateState().

When startLights(ActionEvent) is invoked (the EventHandler for startLights) a new Timer is constructed with a TimerTask implementation that first invokes updateState() on the thread created by the Timer and then invokes updateLights() which changes the color of the lights based on the current state on the JavaFX Application Thread using Platform.runLater(Runnable).

Note: the TimerTask itself will not be run on the JavaFX Application Thread, hence the need to use Platform.runLater(Runnable) for updating the GUI.

When stopLights(ActionEvent) is invoked, it will cancel the Timer.

Note that both startLights(ActionEvent) and stopLights(ActionEvent) toggle which Button objects are enabled on the interface as well.

public class FXMLTrafficLightController implements Initializable {

    @FXML
    private Circle redLight;

    @FXML
    private Circle amberLight;

    @FXML
    private Circle greenLight;

    @FXML
    private Button startLights;

    @FXML
    private Button stopLights;

    private Timer timer;
    private static final int DELAY = 2000; // ms

    private boolean red, amber, green;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        red = true;
        amber = false;
        green = false;
        stopLights.setDisable(true);
        updateLights();
    }

    @FXML
    private void startLights(ActionEvent e) {
        toggleButtons();
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // Not run on the JavaFX Application Thread!
                updateState();
                // Using Platform.runLater(Runnable) to ensure updateLights()
                // is run on the JavaFX Application Thread
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        updateLights();
                    }
                });
            }
        }, 0, DELAY); // no initial delay, trigger again every 2000 ms (DELAY)
    }

    @FXML
    private void stopLights(ActionEvent e) {
        toggleButtons();
        timer.cancel();        
    }

    private void toggleButtons() {        
        startLights.setDisable(!startLights.isDisable());
        stopLights.setDisable(!stopLights.isDisable());
    }

    private void updateState() {
        if (red && !amber && !green) {
            amber = true;
        } else if (red && amber && !green) {
            red = false;
            amber = false;
            green = true;
        } else if (!red && !amber && green) {
            green = false;
            amber = true;
        } else {
            red = true;
            amber = false;
            green = false;
        }
    }

    private void updateLights() {
        redLight.setFill(red ? Color.RED : Color.GREY);
        amberLight.setFill(amber ? Color.ORANGE : Color.GREY);
        greenLight.setFill(green ? Color.GREEN : Color.GREY);
    }
}

TrafficLightApplication.java

For completeness... Just the standard boiler plate with file names changed.

public class TrafficLightApplication extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLTrafficLight.fxml"));        
        Scene scene = new Scene(root);        
        stage.setScene(scene);
        stage.show();
    }

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

}
d.j.brown
  • 1,822
  • 1
  • 12
  • 14