Updating javafx textArea elment using separated thread or task - multithreading

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

The two threading rules in JavaFX are:
Long-running code must not be executed on the FX Application Thread, and
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();
}

Related

Gui freezes when using thread

I want my application to auto-refresh the content in the Vbox from the database. I have started the thread in the initialize method. Why does my Gui freezes. Is there any better way to perform such threading operation for GUI refreshing.
package Messanger.ChatWindow;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import Messanger.Login.Login;
import java.io.IOException;
import Messanger.Settings.Settings;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javax.swing.JOptionPane;
public class Controller implements Initializable {
Settings set = new Settings();
VBox msg_vbox = new VBox();
#FXML
ScrollPane scrlpane;
#FXML
TextField message;
protected Model md;
public Controller() throws SQLException {
this.md = new Model();
}
#FXML
protected void Settings() {
try {
set.loadView();
} catch (IOException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
}
}
#FXML
protected void Logout() throws IOException {
Login lgin = new Login();
lgin.loadView();
ChatWindow.cW.close();
}
protected synchronized void refreshContent() throws SQLException {
ResultSet messageArry = md.getMessages();
while (messageArry.next()) {
msg_vbox.getChildren().clear();
//new label text with message.
Label set_text = new Label();
set_text.setText(messageArry.getString("username") + " Says: \n" + messageArry.getString("message"));
set_text.setStyle("-fx-padding:10;"
+ "-fx-width:100%;"
+ "-fx-background-color:teal;"
+ " -fx-background-insets: 5;"
+ "-fx-font-size:15;"
+ "-fx-background-radius: 3;");
set_text.setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
set_text.setWrapText(true);
set_text.setTextAlignment(TextAlignment.JUSTIFY);
set_text.setPrefWidth(600);
//VBox wrapper
msg_vbox.getChildren().addAll(set_text);
msg_vbox.setPrefWidth(600);
//Further wrapped by ScrollPane
scrlpane.fitToHeightProperty();
scrlpane.setContent(msg_vbox);
scrlpane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrlpane.vvalueProperty().bind(msg_vbox.heightProperty()); //sets the scroll view to new element.
}
}
#FXML
protected void sendMessage() {
//new label text with message.
Label set_text = new Label();
set_text.setText(Messanger.Login.Controller.SESSION_usrname + " Says: \n" + message.getText());
set_text.setStyle("-fx-padding:10;"
+ "-fx-width:100%;"
+ "-fx-background-color:teal;"
+ " -fx-background-insets: 5;"
+ "-fx-font-size:15;"
+ "-fx-background-radius: 3;");
set_text.setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
set_text.setWrapText(true);
set_text.setTextAlignment(TextAlignment.JUSTIFY);
set_text.setPrefWidth(600);
//VBox wrapper
msg_vbox.getChildren().addAll(set_text);
msg_vbox.setPrefWidth(600);
//Further wrapped by ScrollPane
scrlpane.fitToHeightProperty();
scrlpane.setContent(msg_vbox);
scrlpane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrlpane.vvalueProperty().bind(msg_vbox.heightProperty()); //sets the scroll view to new element.
message.setText("");
}
#FXML
protected void check_key(KeyEvent ae) throws SQLException {
if (ae.getCode().equals(KeyCode.ENTER)) {
if (md.addMessage(Messanger.Login.Controller.SESSION_usrname, message.getText())) {
sendMessage();
} else {
JOptionPane.showMessageDialog(null, "Message Sending failed \n "
+ "Please Check Your Internet Connection", "Error ", JOptionPane.INFORMATION_MESSAGE);
}
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
scrlpane.setStyle("-fx-background:#32AED8");
scrlpane.setPrefHeight(300);
try {
refreshContent();
} catch (SQLException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
}
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Platform.runLater(new Runnable() {
#Override
public void run() {
while (true) {
try {
refreshContent();
} catch (SQLException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("asd");
}
}
});
return null;
}
};
}
};
service.start();
}
}
As in the code i've started the thread and i want to run the refreshContent method. I've also tried it implementing the Runnable interface. But same problem occurs.
You are creating a new thread. However from this new thread you immediately post a Runnable to be run on the javaFX application thread that is handling the connection to the DB using a infinite loop, so you are blocking the application thread.
To not block the application thread do the long-running parts of the task on a different thread, then use Platform.runLater to update the UI.
Also you probably shouldn't initialize the rows in the result set loop...
private List<Node> refreshedContent() {
List<Node> result = new ArrayList<>();
ResultSet messageArry = md.getMessages();
while (messageArry.next()) {
// initialize nodes not yet attached to a scene
Label set_text = new Label();
set_text.setText(messageArry.getString("username") + " Says: \n" + messageArry.getString("message"));
set_text.setStyle("-fx-padding:10;"
+ "-fx-width:100%;"
+ "-fx-background-color:teal;"
+ " -fx-background-insets: 5;"
+ "-fx-font-size:15;"
+ "-fx-background-radius: 3;");
set_text.setPrefSize(600, Region.USE_COMPUTED_SIZE);
set_text.setWrapText(true);
set_text.setTextAlignment(TextAlignment.JUSTIFY);
result.add(set_text);
}
return result;
}
#Override
protected Void call() throws Exception {
while (true) {
// do long-running operation
List<Node> newContent = refreshedContent();
Platform.runLater(new Runnable() {
#Override
public void run() {
// there should be no need to do this over and over again
// you should move it outside of the task
msg_vbox.setPrefWidth(600);
//scrlpane.fitToHeightProperty(); // does nothing anyway...
scrlpane.setContent(msg_vbox);
scrlpane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrlpane.vvalueProperty().bind(msg_vbox.heightProperty()); //probably won't work the intended way...
// update ui
msg_vbox.getChildren().setAll(newContent);
}
});
// do more long-running operations
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("asd");
}
}
Furthermore:
Consider using ListView
Usually data access is not done from the UI layer. You could do the updates from a data access layer and make the model properties observable and update the ui on changes...
Try avoid recreating the nodes multiple times a second. ListView would help in this case. If you do not want to use a ListView you should try reusing existing Labels as much as possible instead of replacing them with new instances...

java.lang.IllegalStateException: Not on FX application thread Calling Function

Something strange is happening.. Untill 10 minutes ago I had no problem with this code. But now I have a problem updating JUST my VBOX from an external thread.
These are my three classes:
Controller Class:
public class Controller implements Initializable{
#FXML
private VBox slaveVbox;
private ButtonBar newNode = new ButtonBar();
private Circle c= new Circle();
private Button b= new Button();
private Label lname = new Label();
private Label lIMEI = new Label();
private Label lroot = new Label();
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void create(String imei, String permission,boolean isOnline) throws IOException{
if(!alreadyExist(imei)){
newNode = new ButtonBar();
b = setButtonSpec(imei + "btnHavefun");
c = setCircleSpec(imei + "statuOnline", isOnline);
lname= setLNameSpec(imei + "name");
lIMEI = setLIMEISpec(imei + "Imei");
lroot = setLrootSpec(imei + "root", permission);
newNode.getButtons().addAll(lname,lIMEI,lroot,b,c);
slaveVbox.getChildren().addAll(newNode);
}
}
}
Main Class:
public class MainApp extends Application {
FXMLLoader loader2;
private Stage primaryStage;
private BorderPane rootLayout;
#Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Thypheon Application");
initRootLayout();
Controller controller2 = initDesign();
Connection con = new Connection(controller2);
Thread t = new Thread(con);
t.start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent e) {
Platform.exit();
System.exit(0);
}
});
}
public static void main(String[] args) {
launch(args);
}
public void initRootLayout(){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Controller initDesign(){
try {
FXMLLoader loader2= new FXMLLoader(getClass().getResource("Design.fxml"));
AnchorPane anchor = (AnchorPane) loader2.load();
rootLayout.setCenter(anchor);
Controller controller = loader2.getController();
return controller;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public Stage getPrimaryStage(){
return primaryStage;
}
}
Connection THREAD:
public class Connection implements Runnable {
String result;
Controller controller;
public Connection(Controller controller) {
this.controller = controller;
}
#Override
public void run() {
try {
controller.create("jhgjhgjh", "dssf", true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Debugging the Application Everything works perfectly untill I reach slaveVbox.getChildren().addAll(newNode); Here comes the exception..
After some attempt to solve the problem I figured out that if I create a ButtonBar and I insert it in the slaveVbox from Main (inside start()) it works fine.. So I ve tied to add controller2.create("FIRST", "FIRST", true); in my start() function like this:
#Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Thypheon Application");
initRootLayout();
Controller controller2 = initDesign();
controller2.create("FIRST", "FIRST", true);
Connection con = new Connection(controller2);
Thread t = new Thread(con);
t.start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent e) {
Platform.exit();
System.exit(0);
}
});
}
But obviously my application shows two ButtonBars... One created in the start() function and one created inside the Connection Thread.. How Can I avoid this?? Why I can't directly add item inside my VBox directly from my Connecton thread??
You cannot update the UI from a thread other than the FX Application Thread. See, for example, the "Threading" section in the Application documentation.
It's not at all clear why you are using a background thread at all here: there doesn't seem to be any long-running code in the method you are calling. In general, if you have long-running code to call, you can call it in a background thread and then update the UI by wrapping UI update in a Platform.runLater(...).
public class Connection implements Runnable {
String result;
Controller controller;
public Connection(Controller controller) {
this.controller = controller;
}
#Override
public void run() {
try {
// execute long-running code here...
// perform any updates to the UI on the FX Application Thread:
Platform.runLater(() -> {
// code that updates UI
});
// more long-running code can go here...
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

Replace a TableView with a ProgressIndicator within VBox JavaFX

I have a TableView associated with some data, and once i hit a run button i perform some processing on that data. Each row of data is handled in a seperate thread, and while those threads are running i want a ProgressInducator to replace the table within its vbox.
In the attached code:
If I stop where is says "WORKS IF STOP HERE" - table is replaced with pi.
If I continue waiting for the threads to join - no replacing.
What am I missing?
runButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
List<Thread> threadList = new ArrayList<Thread>();
int threadCounter = 0;
final ProgressIndicator pi = new ProgressIndicator(threadCounter);
vbox.getChildren().clear();
vbox.getChildren().addAll(pi);
for (ProductInTable product : data) {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try
{
product.calculate();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
});
threadList.add(thread);
thread.start();
}
int x = threadList.size();
/** WORKS IF STOP HERE **/
// wait for all threads to end
for (Thread t : threadList) {
try {
t.join();
threadCounter++;
pi.setProgress(threadCounter / x);
} catch (InterruptedException interE) {
interE.printStackTrace();
}
}
/** DOESNT WORKS IF STOP HERE **/
Thread.join() blocks execution until the thread is completed. Since you are calling this on the FX Application Thread, you block that thread until all your worker threads finish. This means the UI is unable to update until those threads are complete.
A better approach is probably to represent each computation with a task, and update a counter of complete tasks back on the FX Application Thread using setOnSucceeded. Something like:
runButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
final ProgressIndicator pi = new ProgressIndicator(threadCounter);
vbox.getChildren().clear();
vbox.getChildren().addAll(pi);
final int numTasks = data.size();
// only access from FX Application thread:
final IntegerProperty completedTaskCount = new SimpleIntegerProperty(0);
pi.progressProperty().bind(completedTaskCount.divide(1.0*numTasks));
completedTaskCount.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> obs, Number oldValue, Number newValue) {
if (newValue.intValue() >= numTasks) {
// hide progress indicator and show table..
}
}
});
for (final ProductInTable product : data) {
Task<Void> task = new Task<Void>() {
#Override
public Void call() {
try
{
product.calculate();
} catch (IOException ioe) {
ioe.printStackTrace();
}
return null ;
}
});
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
completedTaskCount.set(completedTaskCount.get()+1);
}
});
new Thread(task).start();
}
}
});
If you potentially have a large number of items here, you should use some kind of ExecutorService instead to avoid creating too many threads:
ExecutorService exec = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()); // for example...
and then replace
new Thread(task).start();
with
exec.submit(task);

How to update status and progress from a seperate thread in JavaFX

I need to be able to call a separate thread. The thread analysis a file and pull stats from that.
The analyzing of the file can take up to 2 minutes and during the analysis data is printed to the logs.
I would like to have a TextArea on the front end that needs to print out the analysis (as it analysis) and I would also like to have a progress bar to indicate the progress. All of this is determined inside the separate thread.
What I have done is creating a method in the UI class to add a string to the Text Area and pass in a reference of this class to the launched thread.
My Main Class
package trymutilthread;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TryMutilThread extends Application {
TextArea ta;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Start");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
startScheduledExecutorService();
}
});
ta = new TextArea();
VBox vBox = new VBox();
vBox.getChildren().addAll(btn, ta);
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 300, 750);
primaryStage.setScene(scene);
primaryStage.show();
}
private void startScheduledExecutorService() {
final TryMutilThread classI = this;
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
try {
ta.appendText("Starting Thread\n");
new SomeProcess(classI).doTheLogic();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
public void appendText(String string) {
ta.appendText(string);
}
}
The class that is executed in the thread
package trymutilthread;
public class SomeProcess {
TryMutilThread taClass = null;
public SomeProcess (TryMutilThread taClass) {
this.taClass = taClass;
}
public void doTheLogic() throws Exception{
taClass.appendText("Staring Thread");
for (int i = 0; i < 5000; i++) {
taClass.appendText(String.valueOf(i));
}
taClass.appendText("Ending Thread");
}
}
Now when I execute this it still only output the text to the TextArea once the thread has ended.
I did had a look at the following 2 posts:
JavaFX update textArea
Java client / server thread null pointer exception when quickly communicating messages
I am not able to get the data printed to logs until the process has ended.
I updated my code to create a Task.
But now I am getting the following error when it executes
Executing com.javafx.main.Main from F:\DEV\Projects\TryMutilThread\dist\run404234128\TryMutilThread.jar using platform C:\Program Files\Java\jdk1.7.0_10/bin/java
java.lang.NullPointerException
at com.sun.javafx.sg.prism.NGTextHelper$TextAttributes.computeLinePadding(NGTextHelper.java:405)
at com.sun.javafx.sg.prism.NGTextHelper$TextAttributes.access$200(NGTextHelper.java:292)
at com.sun.javafx.sg.prism.NGTextHelper.buildTextLines(NGTextHelper.java:2357)
at com.sun.javafx.sg.prism.NGTextHelper.validateText(NGTextHelper.java:1847)
at com.sun.javafx.sg.prism.NGTextHelper.getCaretShape(NGTextHelper.java:1435)
at javafx.scene.text.Text.getDecorationShapes(Text.java:1150)
at javafx.scene.text.Text.impl_geomChanged(Text.java:757)
at javafx.scene.text.Text$1.invalidated(Text.java:214)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:127)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:161)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:67)
at javafx.scene.text.Text.setText(Text.java:188)
at com.sun.javafx.scene.control.skin.TextAreaSkin$17.invalidated(TextAreaSkin.java:610)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:359)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
at javafx.scene.control.TextInputControl$TextProperty.fireValueChangedEvent(TextInputControl.java:1034)
at javafx.scene.control.TextInputControl$TextProperty.markInvalid(TextInputControl.java:1038)
at javafx.scene.control.TextInputControl$TextProperty.invalidate(TextInputControl.java:978)
at javafx.scene.control.TextInputControl$TextProperty.access$200(TextInputControl.java:950)
at javafx.scene.control.TextInputControl$1.invalidated(TextInputControl.java:119)
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:155)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
at javafx.scene.control.TextArea$TextAreaContent.insert(TextArea.java:196)
at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:373)
at javafx.scene.control.TextInputControl.insertText(TextInputControl.java:308)
at javafx.scene.control.TextInputControl.appendText(TextInputControl.java:298)
at trymutilthread.TryMutilThread.appendText(TryMutilThread.java:80)
at trymutilthread.SomeProcess.doTheLogic(SomeProcess.java:26)
at trymutilthread.TryMutilThread$2.call(TryMutilThread.java:66)
at trymutilthread.TryMutilThread$2.call(TryMutilThread.java:62)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.lang.Thread.run(Thread.java:722)
I then google'd the error and it seems that I need to put the interaction code in a Platform.runlater().
Java client / server thread null pointer exception when quickly communicating messages
I changed the class to execute the thread to be
package trymutilthread;
import javafx.application.Platform;
public class SomeProcess {
TryMutilThread taClass = null;
public SomeProcess(TryMutilThread taClass) {
this.taClass = taClass;
}
public void doTheLogic() throws Exception {
taClass.appendText("Staring Thread");
for (int i = 0; i < 5000; i++) {
//remove this append line
//taClass.appendText(i + "\n");
//And replaced it with platform.runlater
Platform.runLater(new Runnable() {
#Override
public void run() {
taClass.appendText("AGREED" + "\n");
}
});
}
taClass.appendText("Ending Thread");
}
}
It executes without any errors but now it seems to be back to the start... The UI is frozen until all is added to the TextArea
The problem is just that you're flooding the FX Application Thread with too many requests; there's no actual work happening between your Platform.runLater(...) calls. This problem probably goes away with your real application instead of this test, but to mimic the actual long-running work, you can just put a Thread.sleep(...) in there:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TextAreaBackgroundUpdateExample extends Application {
#Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
final TextArea textArea = new TextArea();
final ProgressBar progress = new ProgressBar();
final Button startButton = new Button("Start");
final int maxCount = 5000 ;
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
for (int i = 1; i <= maxCount; i++) {
Thread.sleep(10);
final int count = i ;
Platform.runLater(new Runnable() {
#Override
public void run() {
textArea.appendText("Processed part " + count + " (of "+maxCount+")\n");
}
});
updateProgress(i, maxCount);
}
return null;
}
};
progress.progressProperty().bind(task.progressProperty());
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
});
root.setCenter(textArea);
root.setTop(progress);
root.setBottom(startButton);
final Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Javafx Several GUI Threads - Progress Indicator

In javafx gui processes are done on a separate thread. Putting progress indicator on a background thread with Service and Task is not allowed as it is not an FX thread and indicator is FX element. Is it possible to create multiple gui threads in javafx?
Or is there another way to make progress indicator keep rolling, when other gui elements are being loaded? Currently it starts rolling, then stucks, until the pane is loaded.
#FXML
public void budgetShow(ActionEvent event) {
progressIndicator = new ProgressIndicator(-1.0);
rootPane.getChildren().add(progressIndicator);
progressIndicator.setVisible(true);
progressIndicator.toFront();
threadBudgetShow().start();
}
public Service<Void> threadBudgetShow() {
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
// Background Thread operations.
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
// FX Thread opeartions.
// budgetAnchorPane - reload.
if (budgetAnchorPane == null || !budgetAnchorPane.isVisible()) {
budgetAnchorPane = new BudgetAnchorPane();
rootPane.getChildren().add(budgetAnchorPane);
budgetAnchorPane.setVisible(true);
budgetAnchorPane.getChildren().remove(budgetAnchorPane.budgetTypeComboBox);
budgetAnchorPane.budgetTypeComboBox = new BudgetTypeCombobox();
budgetAnchorPane.getChildren().add(budgetAnchorPane.budgetTypeComboBox);
}
} finally {
rootPane.getChildren().remove(progressIndicator);
latch.countDown();
}
}
});
latch.await();
// Other background Thread operations.
return null;
}
};
}
};
return service;
}
Indeterminate Progress Indicators
progress indicator keep rolling
I think by this you mean an indeterminate progress indicator.
Progress indicators start in an indeterminate state by default and you can change the indicator back to an indeterminate state at any time by setting it's progress to indeterminate:
progressIndicator.setProgress(ProgressIndicator.INDETERMINATE);
Indeterminate Progress Indicators and Tasks
As progress by default is indeterminate, if you don't update the progress of a task until the task is done, then a progress indicator bound to task progress will remain indeterminate while the task is running.
Sample
The progress indicator in this sample will just be a set of spinning dots indicating indeterminate progress until the task is complete.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ProgressTracker extends Application {
final int N_SECS = 10;
#Override
public void start(Stage stage) throws Exception {
Task task = createTask();
stage.setScene(
new Scene(
createLayout(
task
)
)
);
stage.show();
new Thread(task).start();
}
private Task<Void> createTask() {
return new Task<Void>() {
#Override public Void call() {
for (int i=0; i < N_SECS; i++) {
if (isCancelled()) {
break;
}
// uncomment updateProgress call if you want to show progress
// rather than let progress remain indeterminate.
// updateProgress(i, N_SECS);
updateMessage((N_SECS - i) + "");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return null;
}
}
updateMessage(0 + "");
updateProgress(N_SECS, N_SECS);
return null;
}
};
}
private HBox createLayout(Task task) {
HBox layout = new HBox(10);
layout.getChildren().setAll(
createProgressIndicator(task),
createCounter(task)
);
layout.setAlignment(Pos.CENTER_RIGHT);
layout.setPadding(new Insets(10));
return layout;
}
private ProgressIndicator createProgressIndicator(Task task) {
ProgressIndicator progress = new ProgressIndicator();
progress.progressProperty().bind(task.progressProperty());
return progress;
}
private Label createCounter(Task task) {
Label counter = new Label();
counter.setMinWidth(20);
counter.setAlignment(Pos.CENTER_RIGHT);
counter.textProperty().bind(task.messageProperty());
counter.setStyle("-fx-border-color: forestgreen;");
return counter;
}
public static void main(String[] args) {
launch(args);
}
}

Resources