0

I'm working with Javafx and threads simultaneously and I constanly run into this problem where I make a button and then when the button is clicked (using event handlers) I make a for loop that changes the button to 1,2,3,4,5 and then delays for a second in the middle of each. Like a count down!

But what happens is it delays for 5 seconds and changes the text of button to 5.

The problem is I want to see it change between 1 and 5 but all I see is 5 at the end of a 5 second delay. I would assume that it changing the button text but I don't see it. I might have to to do with the .show() method in the Javafx class.

public class HewoWorld extends Application implements EventHandler<ActionEvent>
{
    Thread t = new Thread();
    Button butt;
    boolean buttWasClicked = false;
    Circle circ1 = new Circle(40, 40, 30, Color.RED);
    Circle circ2 = new Circle(100, 100, 30, Color.BLUE);
    Group root;
    Scene scene;
    Stage disStage = new Stage();
    int i = 1;
    public static void main(String[] args)
    {
        launch(args);
    }
    public void start(Stage stage) throws Exception 
    {

        disStage.setTitle("tests stuffs");
        Screen screen = Screen.getPrimary();
        Rectangle2D bounds = screen.getVisualBounds();
        double windh = bounds.getHeight()/2+150;//sets height of screen 
        double windw = bounds.getWidth()/3;//sets width of screen 
        Pane layout = new Pane();
        butt = new Button();
        butt.setText("Hello world");


        root = new Group(circ1, circ2, butt);
        scene = new Scene(root, 800, 400);
        disStage.setWidth(windw);
        disStage.setHeight(windh);


        butt.setLayoutX(200);
        butt.setLayoutY(200);
        butt.setOnAction(this);
        disStage.setScene(scene);
        disStage.show();
    }
     public void handle(ActionEvent event) 
    {
        if (event.getSource() == butt && buttWasClicked == false) 
        {
            try
            {
                butt.setText(i+"");
                t.sleep(1000);
                i++;
            }
            catch(Exception q)
            {

            } 
            circ1 = new Circle(40, 40, 30, Color.BLACK);
            circ2 = new Circle(100, 100, 30, Color.RED);
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Sam Hoffmann
  • 310
  • 2
  • 15
  • Can't add my code but, but no of you answered my problem so far I simply call button.setOnAction(this) and then with my event handler I call – Sam Hoffmann May 21 '15 at 15:35
  • For()//up to five times, set the text to the number and then sleep for 1 second – Sam Hoffmann May 21 '15 at 15:36
  • Why it no work need help not random other code people please – Sam Hoffmann May 21 '15 at 15:36
  • Why don't the answers answer your question? What are they doing that is different from what you describe you want? (I updated my answer so you can directly run it.) As for why your code won't work, no-one can answer that if you don't show your code... – James_D May 21 '15 at 15:44

3 Answers3

6

Why your code doesn't work

The reason your code doesn't work is that you are blocking the FX Application Thread.

Like (almost?) all UI toolkits, JavaFX is a single-threaded UI toolkit. This means that all event handlers, and all the rendering of the UI, are performed on a single thread (called the FX Application Thread).

In your code, you have an event handler that takes more than a second to run, because it pauses for a second via a call to Thread.sleep(...). While that event handler is running, the UI cannot be redrawn (because a single thread cannot do two things at once). So while the value of the button's text has changed immediately, the new value won't actually be rendered on the screen until the handle(...) method has finished running. If you had a for loop in the handle method, nothing would be rendered until the entire loop (and anything else in the method) had completed.

How to fix it

The simplest way to do what you want in JavaFX is to use a Timeline to handle the pause. The Timeline manages the threading appropriately for you:

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class CountingButton extends Application {


    @Override
    public void start(Stage primaryStage) {

        Button button = new Button("Count");

        Timeline timeline = new Timeline();
        for (int count = 0; count <= 5 ; count++) {
            final String text = Integer.toString(count);
            KeyFrame frame = new KeyFrame(Duration.seconds(count), event -> 
                button.setText(text));
            timeline.getKeyFrames().add(frame);
        }

        button.setOnAction(e -> timeline.play());

        primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
        primaryStage.show();
    }


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

In general, for changing the appearance of the user interface at specific time points, the JavaFX Animation API (see also the tutorial) can be useful, especially Timeline and PauseTransition.

A "lower-level" way to do this would be to create a Thread yourself and pause in that thread. This is much more advanced: you need to be careful to update the UI on the FX Application Thread, not on the thread you created. You can do this with a call to Platform.runLater(...):

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class CountingButton extends Application {


    @Override
    public void start(Stage primaryStage) {

        Button button = new Button("Start");

        button.setOnAction(e -> {
            Thread thread = new Thread(() -> {
                for (int i = 0; i <= 5 ; i++) {
                    final String text = "Count: "+i ;
                    Platform.runLater(() -> button.setText(text));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException exc) {
                        exc.printStackTrace();
                    }
                }
            });
            thread.start();
        });

        primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
        primaryStage.show();
    }


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

For more general information on threading in JavaFX, have a look at this post: Using threads to make database requests

Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • so the solution is basically frames? – Sam Hoffmann May 21 '15 at 21:15
  • 1
    The solution is to execute the pause in a thread that is not the FX Application Thread. The `Timeline` provides a very convenient mechanism to do this in this use case. I updated the answer now you provided code, and I could see for certain why your code wasn't working. – James_D May 21 '15 at 21:47
  • @James_D: why aren't you using the JavaFX concurrency classes, such as Task? – Pablo Fernandez Oct 24 '17 at 07:14
  • @Pablo Because the easier way for this use case is to use a timeline. – James_D Oct 24 '17 at 09:03
0

What you have to do is to replace the thread use by the following method :

scheduler = Executors.newScheduledThreadPool(1);

scheduler.scheduleAtFixedRate(

        new Runnable(){


            @Override
            public void run() {
               Platform.runLater(new Runnable() {

                   @Override
                   public void run() {

                           //Here your code to change the number by for example incrementig the value of the button
                   }
               });

            }
        }, 
        1000, 
        80, 
        TimeUnit.MILLISECONDS);  

+1 if it helps :D

0

In this case you need a timer to run every second and increment a counter on every hit. To my knowledge, the best way to make a timer in javafx is to use a timeline. https://stackoverflow.com/a/9966213/4683264.

int i = 0;// class field
// ....
Timeline fiveSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(1), event -> 
                                             button.setText(++i)));
fiveSecondsWonder.setCycleCount(5);// repeat five times
fiveSecondsWonder.play();
Community
  • 1
  • 1
J Atkin
  • 3,080
  • 2
  • 19
  • 33