Gui freezes when using thread - multithreading

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...

Related

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

Threads in JavaFX: Not on FX application thread

I want study how to work with Threads in JavaFX. For example, 2 processes, which should change text on the lables every 100 ms, and updating information on the screen also every 100 ms.
But in this case it doesnt works. IDEA writes:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
I have read many examples with the same problem, but any of their solutions doesnt worked.
What I should to do?
Thanks.
sample.fxml
...
<Button fx:id="startBut" layoutX="100.0" layoutY="50.0" mnemonicParsing="false" onAction="#testingOfThread" prefHeight="25.0" prefWidth="65.0" text="Export" />
<Label fx:id="firstStatus" layoutX="100.0" layoutY="100" text="Status" />
<Label fx:id="secondStatus" layoutX="100.0" layoutY="150" text="Status" />
...
Main.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Sample");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
//Take control to Controller
public void initializeController(){
FXMLLoader loader = new FXMLLoader();
Controller controller = loader.getController();
controller.setMain(this);
}
public static void main(String[] args) {
launch(args);
}
}
Controller.java
package sample;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class Controller {
#FXML
private Label firstStatus;
#FXML
private Label secondStatus;
#FXML
public Button startBut;
//Link to MainApp
private Main Main;
//Constructor
public Controller(){
}
//Link for himself
public void setMain(Main main){
this.Main = main;
}
#FXML
private void testingOfThread(){
Task<Void> task = new Task<Void>() {
#Override public Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
System.out.println(i + 1);
firstStatus.setText(i+"");
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Task<Void> task2 = new Task<Void>() {
#Override public Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
System.out.println(i + 1);
secondStatus.setText(i+"");
}
return null;
}
};
Thread th2 = new Thread(task2);
th2.start();
}
}
Find the code which update the GUI from a thread other than the application thread,then put them in runLater().
Platform.runLater(new Runnable() {
#Override
public void run() {
//update application thread
}
});

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

To Hide JavaFx fxml or JavaFx swing application to System Tray

I want to develop a client app for website .
I want the app to reside in system tray when minimised.
I dont know how to accomplish this task .
Is their any example for this type of operation.
The key here is to set the implicit exit to false Platform.setImplicitExit(false);
Also is important to show and hide the stage in a new thread.
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.show();
}
});
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.hide();
}
});
Next, the whole code:
import java.awt.AWTException;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javax.imageio.ImageIO;
/**
*
* #author alvaro
*/
public class TrayTest extends Application {
private boolean firstTime;
private TrayIcon trayIcon;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
createTrayIcon(stage);
firstTime = true;
Platform.setImplicitExit(false);
Scene scene = new Scene(new Group(), 800, 600);
stage.setScene(scene);
stage.show();
}
public void createTrayIcon(final Stage stage) {
if (SystemTray.isSupported()) {
// get the SystemTray instance
SystemTray tray = SystemTray.getSystemTray();
// load an image
java.awt.Image image = null;
try {
URL url = new URL("http://www.digitalphotoartistry.com/rose1.jpg");
image = ImageIO.read(url);
} catch (IOException ex) {
System.out.println(ex);
}
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
hide(stage);
}
});
// create a action listener to listen for default action executed on the tray icon
final ActionListener closeListener = new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
System.exit(0);
}
};
ActionListener showListener = new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.show();
}
});
}
};
// create a popup menu
PopupMenu popup = new PopupMenu();
MenuItem showItem = new MenuItem("Show");
showItem.addActionListener(showListener);
popup.add(showItem);
MenuItem closeItem = new MenuItem("Close");
closeItem.addActionListener(closeListener);
popup.add(closeItem);
/// ... add other items
// construct a TrayIcon
trayIcon = new TrayIcon(image, "Title", popup);
// set the TrayIcon properties
trayIcon.addActionListener(showListener);
// ...
// add the tray image
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.err.println(e);
}
// ...
}
}
public void showProgramIsMinimizedMsg() {
if (firstTime) {
trayIcon.displayMessage("Some message.",
"Some other message.",
TrayIcon.MessageType.INFO);
firstTime = false;
}
}
private void hide(final Stage stage) {
Platform.runLater(new Runnable() {
#Override
public void run() {
if (SystemTray.isSupported()) {
stage.hide();
showProgramIsMinimizedMsg();
} else {
System.exit(0);
}
}
});
}
}
As far as I know it will be possible in JFX 8. Right now the best solution is to embed your application into AWT and hide the AWT window itself.

SWT update / redraw / layout prοblem

I know many people experience this problem, but the solutions I found online do not seem to solve mine. I have a composite that has three buttons. What I want is the following :
When I click one button, I want some other button to be grayed out ( setEnabled(false) ) and after a while (after a method execution), I want the button to be enabled again.
Many such problems are solved by calling layout() method on the parent container, or this very similar one is solved by calling Display.getCurrent().update();
Simply, my code could be summarized as follows :
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
public class app1 {
protected Shell shell;
/**
* Launch the application.
* #param args
*/
public static void main(String[] args) {
try {
app1 window = new app1();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open() {
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Create contents of the window.
*/
Button button1 , button2 , button3;
Label label;
protected void createContents() {
shell = new Shell();
shell.setSize(450, 300);
shell.setText("SWT Application");
shell.setLayout(new GridLayout(1,false));
{
final Composite composite = new Composite(shell, SWT.NONE);
composite.setLayout(new GridLayout(3,false));
GridData gd_composite = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL);
gd_composite.grabExcessHorizontalSpace = true;
gd_composite.horizontalSpan = 10; //?
gd_composite.verticalIndent = 5;
composite.setLayoutData(gd_composite);
GridData gd_button;
{
button1 = new Button(composite, SWT.NONE);
button1.setText("Button 1");
gd_button = new GridData(SWT.FILL, GridData.BEGINNING, false, false);
gd_button.horizontalSpan = 1;
button1.setLayoutData(gd_button);
button1.addSelectionListener(new SelectionListener(){
public void widgetSelected(SelectionEvent e){
try{
button2.setEnabled(false);
button2.redraw();
button2.update();
//composite.redraw();
//composite.update();
//composite.layout();
shell.redraw();
shell.update();
shell.layout();
Display.getCurrent().update();
} catch (Exception e2) {
System.err.println("exception e : " + e2.toString());
}
System.out.println("basla");
try {
System.out.println("sleep1");
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (Throwable th) {
System.err.println("th: " + th.toString());
}
try {
System.out.println("sleep2");
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (Throwable th) {
System.err.println("th: " + th.toString());
}
try {
System.out.println("sleep3");
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (Throwable th) {
System.err.println("th: " + th.toString());
}
for(int i=0 ; i &lt 10000 ; i++)
{
System.out.println(i);
}
}
public void widgetDefaultSelected(SelectionEvent e) {
System.err.println("widgetDefault !");
}
});
}
{
button2 = new Button(composite, SWT.NONE);
button2.setText("Button 2");
gd_button = new GridData(SWT.FILL, GridData.CENTER, false, false);
gd_button.horizontalSpan = 1;
button2.setLayoutData(gd_button);
button2.addSelectionListener(new SelectionListener(){
public void widgetSelected(SelectionEvent e){
button1.setEnabled(false);
composite.layout();
for (int i=1; i&lt=100; i++) {
try {
Thread.sleep(10);
} catch (Throwable th) {}
label.setText(i + " %");
label.update();
}
}
public void widgetDefaultSelected(SelectionEvent e) {}
});
}
{
label = new Label(composite , SWT.NONE);
label.setText("0 %");
label.update();
}
}
}
}
What happens is, the button gets disabled after the end of widgetSelected() method is reached. However, the label gets updated frequently without any problem (even when the label.update() method is not there)
Additional information : Say, I disable the button, then put a Thread.sleep() and then enable the button ; it sleeps first and then quickly disables and enables the button. So I believe all such paint requests are queued and are processed at the end of the execution.
Useful information: I realized that, when I create and display a MessageBox right after my display changes, the display changes occur. So, if I make the following change in my widgetSelected method :
button2.setEnabled(false)
MessageBox mBox = new MessageBox(Display.getCurrent().getActiveShell(), SWT.ICON_INFORMATION | SWT.OK);
mBox.setText("Information");
mBox.setMessage("Buttons updated!");
mBox.open();
the button will be grayed out as soon as the widgetSelected() method is called. This makes me believe my solution lies within Display.getCurrent() methods. However, I tried
Display.getCurrent().getActiveShell().redraw()
Display.getCurrent().getActiveShell().update()
Display.getCurrent().getActiveShell().layout()
methods and they didnt solve my problem.
Thanks,
Ege
Ok, i've corrected the answer from ginu:
New Runnable().run() does actually not much to nothing, but the idea is correct:
You need a new thread to do your work in. Problem is, from that thread you can't call setEnabled on the buttons, because that can only be done from within the SWT-Event thread.
So you need another runnable to reset the buttons. The second runnable is passed to Display.callAsync and returns before it is actually executed, but that doesn't matter here. You could also use Display.callSync( Runnable ), that call would block your calling thread until the runnable returns.
Tested it in Eclipse, looks good so far.
Edit: Btw, the reason why calling layout() or Display.update() did not work is that you're currently blocking the SWT-Thread with your work, so the calls to layout/update are queued and only executed when you leave the event handler. Never block an event handler to do long work. :)
package test;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Test {
public static void main(final String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(3, false));
final Button button1 = new Button(shell, SWT.PUSH);
button1.setText("Click");
final Button button2 = new Button(shell, SWT.PUSH);
button2.setText("Me");
final Button button3 = new Button(shell, SWT.PUSH);
button3.setText("Dude");
button1.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button3.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button2.setEnabled(false);
button3.setEnabled(false);
button1.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
button1.setEnabled(false);
button2.setEnabled(true);
button3.setEnabled(true);
new Thread( new Runnable() {
public void run() {
try {
// Do your operation here.
//
// Dummy sleep performed here instead.
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
shell.getDisplay().asyncExec( new Runnable() {
public void run() {
button1.setEnabled(true);
button2.setEnabled(false);
button3.setEnabled(false);
}
});
}
} ).start();
}
});
shell.open();
shell.pack();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
It doesn't appear that your snippet is complete, but a few things came to mind regarding your problem. You can probably use setEnabled as seen in the snippet below. For more advance things you could look at GridLayout and GridData with the .exclude property in conjunction with setVisible. For reference the SWT Snippets page is really great.
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class App2 {
public static void main(final String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(3, false));
final Button button1 = new Button(shell, SWT.PUSH);
button1.setText("Click");
final Button button2 = new Button(shell, SWT.PUSH);
button2.setText("Me");
final Button button3 = new Button(shell, SWT.PUSH);
button3.setText("Dude");
button1.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button3.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button2.setEnabled(false);
button3.setEnabled(false);
button1.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
button1.setEnabled(false);
button2.setEnabled(true);
button3.setEnabled(false);
}
});
button2.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
button1.setEnabled(false);
button2.setEnabled(false);
button3.setEnabled(true);
}
});
button3.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
button1.setEnabled(true);
button2.setEnabled(false);
button3.setEnabled(false);
}
});
shell.open();
shell.pack();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
I am not really sure if you have a problem with the enabling / disabling of the buttons, or putting the delay in between the execution flow.
I have modified Jared's code above to perform both these operations mentioned. Please have a look at this and let me know if this what you were looking for.
Cheers. :-)
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class app1 {
public static void main(final String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(3, false));
final Button button1 = new Button(shell, SWT.PUSH);
button1.setText("Click");
final Button button2 = new Button(shell, SWT.PUSH);
button2.setText("Me");
final Button button3 = new Button(shell, SWT.PUSH);
button3.setText("Dude");
button1.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button3.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
button2.setEnabled(false);
button3.setEnabled(false);
button1.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
button1.setEnabled(false);
button2.setEnabled(true);
button3.setEnabled(true);
new Runnable() {
public void run() {
try {
// Do your operation here.
//
// Dummy sleep performed here instead.
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}.run();
button1.setEnabled(true);
button2.setEnabled(false);
button3.setEnabled(false);
}
});
shell.open();
shell.pack();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}

Resources