0

I'm trying to update text inside a javafx textArea element instantly to show execution information using both thread and task but nothing seems working, althought when I print something in console it works thus the thread is executing. The program prints all the messages once the program is executed, but i want show the messages as the same time as the program is executing.

Here I have my tsak and thread declarations

    @Override
public void initialize(URL url, ResourceBundle rb) {
    System.setProperty("webdriver.gecko.driver", "C:\\Users/lyesm/Downloads/geckodriver-v0.26.0-win64/geckodriver.exe");
    try {
        restoreValues();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    text = new Text(this.getLogs());



    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Runnable updater = new Runnable() {
                @Override
                public void run() {
                    printMessages();
                    System.out.println(" working on ... \n");

                }
            };

            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }
                //Platform.runLater(updater);

            }
        }

    });
    thread.setDaemon(true);
    thread.start();

    service = new Service<Void>() {
        @Override
        protected Task<Void> createTask() {
            return new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    Platform.runLater(() -> textArea.appendText(logs));
                    return null;
                }
            };
        }
    };

    service.start();

}

I'm calling the service from this method

 public void launchTest() {
    this.setLogs("\n\n");
    service.restart();
    this.setLogs("   Test starting ...\n");
    service.restart();
    //this.setLogs("   Opening the navigator \n");
    this.setDriver(new FirefoxDriver());
    //this.setLogs("   Reaching http://127.0.0.1:8080/booksManager ... \n");
    driver.get("http://127.0.0.1:8080/booksManager");
    //this.setLogs("   Setting test data \n");
    driver.findElement(By.id("lyes")).click();
    driver.findElement(By.name("email")).sendKeys(pseudo.getText());
    driver.findElement(By.name("password")).sendKeys(password.getText());
    //this.setLogs("   Submitting ... \n");
    driver.findElement(By.name("submit")).click();
    if(driver.getCurrentUrl().equals("http://127.0.0.1:8080/booksManager/Views/index.jsp") == true) {
        //InputStream input= getClass().getResourceAsStream("https://w0.pngwave.com/png/528/278/check-mark-computer-icons-check-tick-s-free-icon-png-clip-art-thumbnail.png");
        //Image image = new Image(input);
        //ImageView imageView = new ImageView(image);
        Label label = new Label("   Test successed");
        testsInfos.getChildren().add(label);
    }else {
        Text textRes = new Text("\n  Test failed  ");
        textRes.setFill(javafx.scene.paint.Color.RED);
        testsInfos.getChildren().add(textRes);
    }
    driver.close();

}

And here the printMessage method called from the thread

public void printMessages() {
    String ll = this.getLogs();
    this.text.setText(ll);
    testsInfos.getChildren().remove(text);
    testsInfos.getChildren().add(text);
    textArea.clear();
    textArea.setText(ll);
}

Neither method seems to work.

Does anybody have any idea how to fix it ?

Edited:

   package application;


import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform; 
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Main extends Application {


private Service<Void> service;



@Override
public void start(Stage primaryStage) throws InterruptedException {
    StackPane root = new StackPane();
    TextArea ta = new TextArea();
    ta.setDisable(true);
    root.getChildren().add(ta);
    Scene scene = new Scene(root, 200, 200);

    // longrunning operation runs on different thread
    /*Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
            Runnable updater = new Runnable() {

                @Override
                public void run() {
                    incrementCount();
                }
            };

            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }

                // UI update is run on the Application thread
                Platform.runLater(updater);
            }
        }

    });
    // don't let thread prevent JVM shutdown
    thread.setDaemon(true);
    thread.start();*/

    primaryStage.setScene(scene);
    primaryStage.show();
    service = new Service<Void>() {
        @Override
        protected Task<Void> createTask() {
            return new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    final CountDownLatch latch = new CountDownLatch(1);
                    Platform.runLater(new Runnable() {                          
                        @Override
                        public void run() {
                            try{
                                ta.appendText("\n Printed ");
                            }finally{
                                latch.countDown();
                            }
                        }
                    });
                    latch.await();
                    return null;
                }
            };
        }
    };

    service.start();
    showIT();
}

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

public void showIT() throws InterruptedException {
    service.restart();
    for(int i = 0;i<1000000;i++) {
        System.out.println(i);
    }
    for(int i = 0;i<1000000;i++) {
        System.out.println(i);
    }
    service.restart();
    for(int i = 0;i<1000000;i++) {
        System.out.println(i);
    }
    for(int i = 0;i<1000000;i++) {
        System.out.println(i);
    }
    service.restart();
}

}

  • Does this answer your question? [JavaFX working with threads and GUI](https://stackoverflow.com/questions/16708931/javafx-working-with-threads-and-gui) – twobiers Jun 03 '20 at 12:50
  • No, it doesn't , it prints all the messages once the program is executed. – lyes makhloufi Jun 03 '20 at 12:59
  • Can you create a [mre]? There are many different things that could cause this not to be working... without a reproducible example we are just guessing. – James_D Jun 03 '20 at 13:10
  • @James_D I updated my post see below edited, this code prints all the messages once its finished exactly like in my initial program. – lyes makhloufi Jun 03 '20 at 13:29
  • Well, yes, it would; you're are blocking the JavaFX Application Thread with `Thread.sleep()`, so it can't render the UI. – James_D Jun 03 '20 at 13:30
  • @James_D are the for loops like edited blocking the javafx UI too ? because it doesn't work event when I remove the sleep. – lyes makhloufi Jun 03 '20 at 13:37
  • Yes, of course. You are running the `showIT()` method on the JavaFX Application Thread. The UI won't be rendered until that method exits. – James_D Jun 03 '20 at 13:38
  • @James_D So how should I do in order to print the messages instantly ? – lyes makhloufi Jun 03 '20 at 13:44
  • I'm not clear what you're really trying to do here. The basic rules are: 1. any long-running task should be performed in a background thread, and 2. the UI must only be updated from the FX Application Thread. You can schedule things to run on the FX Application Thread using `Platform.runLater(...)`, or (better) use the `Task` API, and in particular `Task.setOnSucceeded(...)` etc to do something on the FX thread when the task finishes. See if https://stackoverflow.com/questions/30249493/using-threads-to-make-database-requests/30250308#30250308 helps. – James_D Jun 03 '20 at 13:51
  • In your example at the end of the question, everything is backwards: the task created by the service doesn't take a long time to run, so could just be run on the FX Application Thread, while the `showIT()` method, which does take a long time to run, is executed on the FX Application Thread. – James_D Jun 03 '20 at 13:52
  • @James_D Even when I update the TextArea in the javaFx thread without using any of thread or task the messages are all printed at the end of the program. – lyes makhloufi Jun 03 '20 at 14:20
  • If you have long-running code, that's to be expected. Long-running code must be executed on a background thread. – James_D Jun 03 '20 at 14:22
  • Why are you starting a new Runnable inside a Platform.runLater? Doesn't that defeat the purpose of Platform.runLater? You should be updating controls within Platform.runLater but you should not be starting new threads within Platform because you won't be in the Platform context if you do... or am I missing something here? – Michael Sims Jun 03 '20 at 15:04
  • @MichaelSims I don't see anywhere the OP is starting a new thread inside a `Platform.runLater(...)`, though I may be missing something. – James_D Jun 03 '20 at 15:06
  • @James_D About midway down in the code there is a Platform.runLater(new Runnable() { .... – Michael Sims Jun 04 '20 at 22:59
  • @MichaelSims But there’s no new thread created. – James_D Jun 04 '20 at 23:01
  • @James_D Then I profess ignorance on the topic ... Which thread will the new Runnable execute on? And what's the point in defining a new Runnable inside a Platform.runLater? – Michael Sims Jun 04 '20 at 23:02
  • @James_D It just occured to me, every time I do a Platform.runLater, I write them like this: Platform.runLater(()->{}); not realizing that new Runnable is implied... my bad. – Michael Sims Jun 04 '20 at 23:04
  • @MichaelSims The parameter you pass to `Platform.runLater(...)` is a `Runnable`, so you can’t call it without creating a `Runnable`. `Platform.runLater(...)` will execute that `Runnable` on the FX Application Thread. That’s the entire purpose, and sole functionality, of that method. See [docs](https://openjfx.io/javadoc/14/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)) – James_D Jun 04 '20 at 23:06

1 Answers1

2

The two threading rules in JavaFX are:

  1. Long-running code must not be executed on the FX Application Thread, and
  2. Any code that updates the UI must be executed on the FX Application Thread.

The reason for the first rule is that the FX Application Thread is responsible for rendering the UI (among other things). So if you perform a long-running task on that thread, you prevent the UI from being rendered until your task is complete. This is why you only see the updates once everything is finished: you are running your long-running code on the FX Application Thread, preventing it from re-rendering the text area until everything is complete.

Conversely, the code you do run on a background thread (via the Task.call() method) doesn't do anything that takes a long time to run:

@Override
protected Void call() throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    Platform.runLater(new Runnable() {                          
        @Override
        public void run() {
            try{
                ta.appendText("\n Printed ");
            }finally{
                latch.countDown();
            }
        }
    });
    latch.await();
    return null;
}

The only thing you do here is schedule an update on the FX Application thread; the call to Platform.runLater() exits immediately. There's no long-running code at all, so no purpose for the background thread on which this runs. (Technically, the call to latch.await() is a blocking call, but it's redundant anyway, since you simply exit the method after waiting.) With this task implementation, there's no difference between calling service.restart();, and ta.appendText("\n Printed");.

So, your showIT() method should be called on a background thread, and can use Platform.runLater() to append text to the text area. Something like:

import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform; 
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Main extends Application {


    private Service<Void> service;



    @Override
    public void start(Stage primaryStage) throws InterruptedException {
        StackPane root = new StackPane();
        TextArea ta = new TextArea();
        ta.setDisable(true);
        root.getChildren().add(ta);
        Scene scene = new Scene(root, 200, 200);


        primaryStage.setScene(scene);
        primaryStage.show();

        // run showIT() on a background thread:
        Thread thread = new Thread(this::showIT);
        thread.setDaemon(true);
        thread.start();
    }

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

    public void showIT() {
        try {
            Platform.runLater(() -> ta.appendText("\nPrinted"));
            Thread.sleep(1000);

            Platform.runLater(() -> ta.appendText("\nPrinted"));
            Thread.sleep(1000);

            Platform.runLater(() -> ta.appendText("\nPrinted"));
            Thread.sleep(1000);
        } catch (InterruptedException exc) {
            Thread.currentThread().interrupt();
        }

    }

}

For your original code, I have to make some guesses about which parts of the API you're using are long-running and which aren't. I would start by creating a utility log() method that you can call from any thread:

private void log(String message) {
    Runnable update = () -> ta.appendText(message);
    // if we're already on the FX application thread, just run the update:
    if (Platform.isFxApplicationThread()) {
        update.run();
    }
    // otherwise schedule it on the FX Application Thread:
    else {
        Platform.runLater(update);
    }
}

And now you can do something like:

public void launchTest() {
    log("\n\n");
    log("   Test starting ...\n");
    log("   Opening the navigator \n");

    Task<Boolean> task = new Task<>() {
        @Override
        protected Boolean call() throws Exception {
            this.setDriver(new FirefoxDriver());
            log("   Reaching http://127.0.0.1:8080/booksManager ... \n");
            driver.findElement(By.name("email")).sendKeys(pseudo.getText());
            driver.findElement(By.name("password")).sendKeys(password.getText());

            driver.get("http://127.0.0.1:8080/booksManager");
            log("   Setting test data \n");
            driver.findElement(By.id("lyes")).click();

            log("   Submitting ... \n");
            driver.findElement(By.name("submit")).click();

            boolean result = driver.getCurrentUrl().equals("http://127.0.0.1:8080/booksManager/Views/index.jsp");
            driver.close();
            return result ;
        }
    };

    task.setOnSucceeded(e -> {
        if (task.getValue()) {

            //InputStream input= getClass().getResourceAsStream("https://w0.pngwave.com/png/528/278/check-mark-computer-icons-check-tick-s-free-icon-png-clip-art-thumbnail.png");
            //Image image = new Image(input);
            //ImageView imageView = new ImageView(image);
            Label label = new Label("   Test successed");
            testsInfos.getChildren().add(label);
        } else {
            Text textRes = new Text("\n  Test failed  ");
            textRes.setFill(javafx.scene.paint.Color.RED);
            testsInfos.getChildren().add(textRes);
        }
    });

    Thread thread = new Thread(task);
    thread.setDaemon(true);
    thread.start();

}
James_D
  • 201,275
  • 16
  • 291
  • 322