Lately I encountered some issues with code that asynchonously updates GUI. I then came across this article, which shined some light on the problem - I was not using Platform.runLater() to update my GUI components, however, consider the original code:
public class Main extends Application {
private TextArea textArea = new TextArea();
private Label statusLabel = new Label("Not Started...");
private Button startButton = new Button("Start");
private Button exitButton = new Button("Exit");
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(final Stage stage) {
startButton.setOnAction(event -> startTask());
exitButton.setOnAction(event -> stage.close());
HBox buttonBox = new HBox(5, startButton, exitButton);
VBox root = new VBox(10, statusLabel, buttonBox, textArea);
root.setStyle("-fx-padding: 10;" +
"-fx-border-style: solid inside;" +
"-fx-border-width: 2;" +
"-fx-border-insets: 5;" +
"-fx-border-radius: 5;" +
"-fx-border-color: blue;");
Scene scene = new Scene(root, 400, 300);
stage.setScene(scene);
stage.setTitle("A simple Concurrency Example");
stage.show();
}
private void startTask() {
Runnable task = this::runTask;
Thread backgroundThread = new Thread(task);
backgroundThread.setDaemon(true);
backgroundThread.start();
}
private void runTask() {
for (int i = 1; i <= 10; i++) {
try {
String status = "Processing " + i + " of " + 10;
statusLabel.setText(status);
textArea.appendText(status + "\n");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The problematic part is the runTask() method. The article explains that instead of simply using statusLabel.setText(status), I should use Platform.runLater(() -> statusLabel.setText(status));. That makes sense to me.
What doesn't make sense, however, is why I don't have to apply the same logic to textArea updates? Notice the fifth line of runTask() method - the textArea.appendText(status + "\n"); part. Why it doesn't give me an exception (java.lang.IllegalStateException: Not on FX application thread) about modifying an FX component from non-FX thread, since it's clearly a way of updating GUI FX component? What operations should I put inside the Platform.runLater() and what operations do now have to be there?
Run later will interfere more or less with the UI Thread. The execution will be queued in the UI thread. When you have a thread sleep or a long calculation the UI will freeze. You can use it for simple tasks. But in your case I do recommend it, because it is only a "set-operation".
Generally you should use Observable properties and bind them to the UI when possible. Then you don't need to do anything of the "Thread" and "Queuing" and "RunLater" operations.
When the WHOLE operation is needed to executed with the "run later", and the calculation would be time costly, you should look, for what you can run later or use a different Mechanism.
But when you just add something with out run laterform a difficult or longer calculation from another thread, an exception will pop up.
So what you want is to update the UI-Elements when it is needed and the new values are calculated or provided by whatever mechanism.
Here the way to go with Properties:
StringProperty text = new SimpleStringProperty("Hello");
Label label = new Label();
label.getTextProperty().bind(text);
Now whenever you Update text from another Thread, the update will automatically occur in the UI without Run Later.
If there is no way around:
What you could (in the case of more costly operations) do is have a queue that contains all the executions of updates for the view that come asynchronously. And execute the changes only in the JavaFX Thread.
You should use the Animationtimer for that.
//thread safe queue
Queue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
Now the approach with runLater:
Platform.runLater(()->{
while(!queue.isEmpty()) {
queue.remove().run();
}
});
And the approach with the AnimationTimer.
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
//Whatever condition or how many changes you would make at one tick
//In this case we just run all "updates" in that tick.
// I would recommend it, you could update 100 things each tick
while(!queue.isEmpty()) {
queue.remove().run();
}
}
};
timer.start();
Then have a thread that would calculate something and when it's done it adds the new change to the queue. Or sleeps or waits for a response etc.
private void startTask() {
Runnable task = this::runTask;
Thread backgroundThread = new Thread(task);
backgroundThread.setDaemon(true);
backgroundThread.start();
}
private void runTask() {
for (int i = 1; i <= 10; i++) {
try {
String status = "Processing " + i + " of " + 10;
//Add the new change as a Runnable here
//It will be run in the in the next update of the UI
tasks.add(()->{
statusLabel.setText(status);
textArea.appendText(status + "\n");
});
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
NOTE
It still depends for what purpose you are going to use it.
Those are just two approaches.
When you only update declared values, you could also use Property Bindings.
(StringProperty in your case)
And I would personally always go with the AnimationTimer.
RunLater decides when it is "fine" with queuing the Runnable. With the Animationtimer, you can be sure it is executed each tick, when a update is needed.
You could also Store all the updates and then do a "poll" of the new Values each tick.
TASK
When you know exactly where you want to put which value you should use Task and bind it the the UI element, that would also work.
Label statusLabel = new Label("Not Started...");
Task<String> t = new Task<String>() {
#Override
protected String call() throws Exception {
int i = 0;
while(//any condition ... ) {
i++;
Thread.sleep(1000);
updateValue("i is " + i);
}
return "last value!";
}
// now Bind the TextProperty of the Label to the ValueProperty of the
// Task
statusLabel.getTextproperty().bind(task.getValueProperty());
Thread backgroundThread = new Thread(task);
backgroundThread.start();
Related
So i'm trying to make a downloader which shows the progress of the download with a progress bar.
But i'm having problems since it doesn't actually update the progress bar. Basically it stays white, when it is meant to be blue. If anyone could help, code is below.
JProgressBar progressBar = new JProgressBar(0, ia);
con.add(progressBar, BorderLayout.PAGE_START);
con.validate();
con.repaint();
progressBar = new JProgressBar(0, ia);
progressBar.setValue(0);
System.out.print("Downloading Files");
while ((count = in.read(data, 0, downloadSpeed)) != -1){
fout.write(data, 0, count);
if (count >= 2){
progressBar.setString("Downloading : " + ia + " # " + count + "Kbs per second");
} else {
progressBar.setString("Downloading : " + ia + " # " + count + "Kb per second");
}
progressBar.setValue(count);
con.add(progressBar, BorderLayout.PAGE_START);
try{
Thread.sleep(1000);
} catch (Exception e){}
}
As #happyburnout has pointed out, you'd be better of processing you download in a separate thread, using a SwingWorker is probably the best solution for what you are doing.
The main reason is you're blocking the Event Dispatching Thread (AKA EDT) from running, preventing any repaint requests (and other UI important things) from been processed.
You should have a read through
Concurrency in Swing
Worker Threads and Swing Worker
Now this is taken almost directly from the API docs, but gives a basic idea of a SwingWoker with a JProgressBar
The "Worker"...
public class Worker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
// The download code would go here...
for (int index = 0; index < 1000; index++) {
int progress = Math.round(((float)index / 1000f) * 100f);
setProgress(progress);
Thread.sleep(10);
}
// You could return the down load file if you wanted...
return null;
}
The "progress pane"
public class ProgressPane extends JPanel {
private JProgressBar progressBar;
public ProgressPane() {
setLayout(new GridBagLayout());
progressBar = new JProgressBar();
add(progressBar);
}
public void doWork() {
Worker worker = new Worker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
}
}
Remember the golden rules of Swing
Never, never, never update a UI component from any Thread other then the EDT
Always perform time consuming tasks on a different Thread
(Something, something, something about layout managers - that's more of a personal thing ;))
And you will have a happy and easy time with Swing :D
Use a combination with SwingWorker.
See an example here:
SwingWorker and Progress Bar
#Hovercraft: You're right. Allow me to refer to the corresponding SwingWorker page of JavaDoc, in my opinion this explains the situation best.
I have a task to work with threads and JavaFX library. A separate thread 1 need to be started by pressing a button. The thread is supposed to fulfill a list with Integer values (100 - 150) with a small interval (e.g. 5 ms).
And after values are generated, it should stop. A new thread 2 must start and fill a ViewList with generated values.
But I get an exception each time when a new value is added to my list by thread 1:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
at javafx.scene.Scene.addToDirtyList(Scene.java:485)
at javafx.scene.Node.addToSceneDirtyList(Node.java:424)
at javafx.scene.Node.impl_markDirty(Node.java:415)
I tried to use Platform.runLater() in button event listener instead of creating a new Thread, but the program stops responding in that case.
Could anyone please help me, how to fill a collection with values in a separate thread and how to start a second one to update ViewList element after the first thread finishes?
Here is my class:
int itemName = 100;
Button btnOne = new Button("Button one");
Label label = new Label("Press the button to start");
ObservableList<String> listOptions = FXCollections.observableArrayList();
ListView<String> list;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hurry");
list = new ListView<>(listOptions);
list.setPrefSize(120, 120);
MultipleSelectionModel<String> lvSelModel =
list.getSelectionModel();
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 180, 200);
primaryStage.setScene(myScene);
lvSelModel.selectedItemProperty().addListener(
(changed, oldValue, newValue) -> {
label.setText("List option selected: " + newValue);
});
rootNode.getChildren().addAll(btnOne, list, label);
primaryStage.show();
Task<Integer> threadOne = new Task<Integer>(){
#Override
protected Integer call() throws Exception{
while(itemName < 130){
final int finalValue = itemName++;
listOptions.add(String.valueOf(itemName));
Platform.runLater(
() -> label.setText("Generating: " + finalValue));
Thread.sleep(100);
}
label.setText("Thread 1 finished");
return itemName;
}
};
btnOne.setOnAction(
ae -> {
Thread th = new Thread(threadOne);
th.setDaemon(true);
th.start();
});
}
public static void main(String[] args) {
launch(args);
}}`
The result program should be like this:
Thanks everyone for your time!
Since listOptions is the list of items in the list view, calling listOptions.add(...) modifies the UI (the list view). Thus it needs to be executed on the FX Application Thread, similarly to setting the text of the label. Just move that line in side the Platform.runLater(...):
Task<Integer> threadOne = new Task<Integer>(){
#Override
protected Integer call() throws Exception{
while(itemName < 130){
final int finalValue = itemName++;
Platform.runLater(() -> {
listOptions.add(String.valueOf(itemName));
label.setText("Generating: " + finalValue);
});
Thread.sleep(100);
}
label.setText("Thread 1 finished");
return itemName;
}
};
To start another task (or, indeed, do anything) when the first task finishes, use the task's onSucceeded handler:
threadOne.setOnSucceeded(e -> {
// create another task and run it in another thread...
});
I am creating a javafx app. It uses some heavy programming (heavy mapping). I need to multithread it because the user-experience becomes laggy.
I don't want to re-write the whole code if it can be possible. But it is not necessary. I need someone to completely explain the life-cycle, how to control the thread and how to ask it to do something.
For instance, i provide a full list of mapping characters in my fxml controller:
#FXML
private static final Map <Character, String> myMap = new HashMap <> ();
static {
myMap.put('a', "5");
myMap.put('b', "6");
myMap.put('c', "7");
myMap.put('d', "8");
//And so on...
}
Then i encode the input text on button press:
String codedTextOut;
textToCode = enteredText.getText();
StringBuilder encoderTextSB = new StringBuilder();
for (char codeChar : textToCode.toCharArray()) {
encoderTextSB.append(myMap.get(codeChar));
}
codedTextOut = encoderTextSB.toString();
It gives a laggy user experience. I want to create a separate thread to do the encoding action on button press. Please help and also explain the various properties of thread. (i have checked out http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm, but it is not much informative)
Use predefined pool of threads, or new Thread(), where you provide runnable with your data.
it will move computations from javafx thread to some enother thread, so that user can continue interacting with the application.
When result is ready, use runLater() - this call wil be done on javafx queue, so that you will not run into concurrency troubles.
This allows you to return results of evaluation to the UI. (you shouldn't interact with UI components from another thread).
Use another features from javafx concurrent package, like Task for instance, as an option.
Here is a code snippet :
#Override
public void start(Stage primaryStage) {
final TextArea ta = new TextArea();
ta.setMinSize(100, 100);
Button btn = new Button();
btn.setText("Encode'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ta.setDisable(true);
new Thread(new Runnable() {
#Override
public void run() {
final StringBuilder codedTextOut = new StringBuilder();
String textToCode = ta.getText();
StringBuilder encoderTextSB = new StringBuilder();
for (char codeChar : textToCode.toCharArray()) {
encoderTextSB.append(codeChar + 15);
}
codedTextOut.append(encoderTextSB);
Platform.runLater(new Runnable() {
#Override
public void run() {
ta.setText(codedTextOut.toString());
ta.setDisable(false);
}
});
}
}).start();
}
});
VBox root = new VBox();
root.getChildren().addAll(ta, btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Encoder");
primaryStage.setScene(scene);
primaryStage.show();
}
On click on button, you disable a text area, create a new Thread, execute code in it, after that put runnable on javafx queue, and execute piece of code from the second runnable on JavaFX thread, where you assign new text and enable the text area back.
So i'm trying to make a downloader which shows the progress of the download with a progress bar.
But i'm having problems since it doesn't actually update the progress bar. Basically it stays white, when it is meant to be blue. If anyone could help, code is below.
JProgressBar progressBar = new JProgressBar(0, ia);
con.add(progressBar, BorderLayout.PAGE_START);
con.validate();
con.repaint();
progressBar = new JProgressBar(0, ia);
progressBar.setValue(0);
System.out.print("Downloading Files");
while ((count = in.read(data, 0, downloadSpeed)) != -1){
fout.write(data, 0, count);
if (count >= 2){
progressBar.setString("Downloading : " + ia + " # " + count + "Kbs per second");
} else {
progressBar.setString("Downloading : " + ia + " # " + count + "Kb per second");
}
progressBar.setValue(count);
con.add(progressBar, BorderLayout.PAGE_START);
try{
Thread.sleep(1000);
} catch (Exception e){}
}
As #happyburnout has pointed out, you'd be better of processing you download in a separate thread, using a SwingWorker is probably the best solution for what you are doing.
The main reason is you're blocking the Event Dispatching Thread (AKA EDT) from running, preventing any repaint requests (and other UI important things) from been processed.
You should have a read through
Concurrency in Swing
Worker Threads and Swing Worker
Now this is taken almost directly from the API docs, but gives a basic idea of a SwingWoker with a JProgressBar
The "Worker"...
public class Worker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
// The download code would go here...
for (int index = 0; index < 1000; index++) {
int progress = Math.round(((float)index / 1000f) * 100f);
setProgress(progress);
Thread.sleep(10);
}
// You could return the down load file if you wanted...
return null;
}
The "progress pane"
public class ProgressPane extends JPanel {
private JProgressBar progressBar;
public ProgressPane() {
setLayout(new GridBagLayout());
progressBar = new JProgressBar();
add(progressBar);
}
public void doWork() {
Worker worker = new Worker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
}
}
Remember the golden rules of Swing
Never, never, never update a UI component from any Thread other then the EDT
Always perform time consuming tasks on a different Thread
(Something, something, something about layout managers - that's more of a personal thing ;))
And you will have a happy and easy time with Swing :D
Use a combination with SwingWorker.
See an example here:
SwingWorker and Progress Bar
#Hovercraft: You're right. Allow me to refer to the corresponding SwingWorker page of JavaDoc, in my opinion this explains the situation best.
I am probably missing something here, but I'll try and explain what I want to achieve and then someone please tell me that I am doing it wrong(which I am :) ) and point me in the right direction?
I am using JavaFX 2.0, but I think this problem would lend itself to Swing, or any UI framework.
I want to develope a simple splash screen for my application, when the splash screen starts, I want to have a message label that will be used to update a user on whats happening, in regards to configuring up the back end of the application. My application start up has 2 steps, the first step uses Spring to initialise the Application Context, which in turn initialises the DB (JPA 2.0/Hibernate/etc). The second part of my application start up process will query the DB for the initial data which will be used to populate the UI. Both these steps need to be complete before I can close the splash screen, and between each step I want to update the label in the splash screen to let a user know which stage is being done at that time.
I have broken this down into the following simple program which uses JavaFX and a button, when the button is pressed a new thread is created, that starts another class, which just performs some count to an abitary value, and then another another thread is created to simlate the second step of the start up process, But my issue is here that the second thread attempts to run before the first thread has finished, and as a result runs into a NPE.
Below is a breakdown of some simple code that highlights this issue:
public class Test extends Application
{
private LongTester lt;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage)
{
Button btn = new Button();
final Label lblText = new Label("Starting");
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
new Thread(new ConstructorRunnable()).start();
lblText.setText("More Loading?");
new Thread(new MethodRunnable()).start();
lblText.setText("Finished");
}
});
HBox root = new HBox();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
private class ConstructorRunnable implements Runnable
{
#Override
public void run()
{
lt = new LongTester();
}
}
private class MethodRunnable implements Runnable
{
#Override
public void run()
{
lt.countAgain();
}
}
private class LongTester
{
public LongTester()
{
for (int idx = 0; idx < 1000000; idx++)
{
System.out.println("Constructor: " + idx);
}
}
public Boolean countAgain()
{
for (int idx = 0; idx < 1000000; idx++)
{
System.out.println("Method: " + idx);
}
return Boolean.TRUE;
}
}
}
Can anyone point out my mistake?
I'd advise using a Task to execute your startup tasks and message progress back to your splash screen (similar to the approach in this sample created for a prior stackoverflow question on splash screens). If you want stuff in your task to run sequentially, just use one thread for the task rather than two.
Sample code for your task might look something like:
final Task<Data> initTask = new Task() {
#Override protected Data call() throws InterruptedException {
updateMessage("Initializing Application");
MyApp.initializeAppContext();
updateMessage("Loading Data");
Data data = DB.loadData();
updateMessage("Data Loaded");
return data;
}
showSplash(initStage, initTask);
new Thread(initTask).start();
showMainStage(initTask.valueProperty());
To fix your problem, you can use a CountDownLatch
This can be used the following way:
private class ConstructorRunnable implements Runnable {
CountDownLatch gate_ = null;
public ConstructorRunnable(CountDownLatch gate){
gate_ = gate;
}
#Override
public void run() {
lt = new LongTester();
gate_.countDown(); // Signal the second thread to start
}
}
private class MethodRunnable implements Runnable{
CountDownLatch gate_ = null;
public MethodRunnable(CountDownLatch gate){
gate_ = gate;
}
#Override
public void run(){
CountDownLatch.await(); // Wait for the first thread to finish
lt.countAgain();
}
}
This can now be used like this:
CountDownLatch gate = new CountDownLatch(1);
new Thread(new ConstructorRunnable(gate)).start();
lblText.setText("More Loading?");
new Thread(new MethodRunnable(gate)).start();
lblText.setText("Finished");
On a side note: as your tasks are sequential, why is there a need for having several threads? Threads are made to run multiple tasks running in parallel and your case does not make a use case for threads as these operations are sequential