lyes makhloufi
lyes makhloufi

Reputation: 59

Updating javafx textArea elment using separated thread or task

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();
}

}

Upvotes: 0

Views: 286

Answers (1)

James_D
James_D

Reputation: 209339

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();

}

Upvotes: 2

Related Questions