Javafx - Updating Gui

JAVAFX how to update GUI elements (nodes)

You need to use a Task, if you have some long running operation. Otherwise when that operation is called from the JavaFX Application Thread it would block the GUI.

If you want to update the GUI from the Task you have to use Platform.runlater, which will run the code on the JavaFX Application Thread:
Platform.runlater

Updates to the Nodes of your GUI have always to be performed on the JavaFx Thread.

When you update status inside the Listener of button it should work.

  button.setOnAction(evt -> {
dbConnect();
status.setText("Connected");
// ...
});

If dbConnect() takes some time, you can use a Task:

 Task<Void> longRunningTask = new Task<Void>() {

@Override
protected Void call() throws Exception {
dbConnect();
Platform.runLater(() -> status.setText("Connected"));
return null;
}
};

button.setOnAction(e -> {
new Thread(longRunningTask).start();
});

Constantly Updating JavaFX GUI

Here is a working version.

import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class ClockView extends Application {
Pane background;
static Text firstDigit;
static Text secondDigit;
static Text thirdDigit;
static Text fourthDigit;
Text middleDecimal;

public final Timer clockTimer = new Timer();

/* Sets the first digit of the base 10 time to the passed through char. */
static void setFirstDigit(char x1) {
String digitString = "";
digitString += x1;
firstDigit.setText(digitString);
}

/* Sets the second digit of the base 10 time to the passed through char. */
static void setSecondDigit(char x2) {
String digitString = "";
digitString += x2;
secondDigit.setText(digitString);
}

/* Sets the third digit of the base 10 time to the passed through char. */
static void setThirdDigit(char y1) {
String digitString = "";
digitString += y1;
thirdDigit.setText(digitString);
}

/* Sets the fourth digit of the base 10 time to the passed through char. */
static void setFourthDigit(char y2) {
String digitString = "";
digitString += y2;
fourthDigit.setText(digitString);
}

/* Main Method that Launches the GUI */
public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception {
final double TEXTFIELD_LAYOUT_Y = 200;

//Background Pane
background = new Pane();

//First digit textField
firstDigit = new Text("0");
firstDigit.setLayoutX(17);
firstDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
firstDigit.setStyle("-fx-font-size: 96pt;");

//Second digit textField
secondDigit = new Text("0");
secondDigit.setLayoutX(117);
secondDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
secondDigit.setStyle("-fx-font-size: 96pt;");

//Middle decimal
middleDecimal = new Text(".");
middleDecimal.setLayoutX(219);
middleDecimal.setLayoutY(210);
middleDecimal.setStyle("-fx-font-size: 72pt;");

//Third digit textField
thirdDigit = new Text("0");
thirdDigit.setLayoutX(250);
thirdDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
thirdDigit.setStyle("-fx-font-size: 96pt;");

//Fourth digit textField
fourthDigit = new Text("0");
fourthDigit.setLayoutX(362);
fourthDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
fourthDigit.setStyle("-fx-font-size: 96pt;");

/* Adding the Nodes to the Pane */
background.getChildren().addAll(firstDigit, secondDigit, middleDecimal, thirdDigit, fourthDigit);

/* Setting the Scene */
Scene scene = new Scene(new Group(), 470, 258);
Group root = (Group)scene.getRoot();
root.getChildren().add(background);
primaryStage.setTitle("Base 10 Clock");
primaryStage.setScene(scene);
primaryStage.show();

clockTimer.scheduleAtFixedRate(new TimerTask() {

Calendar now;
double currentTime;
String timeString;
long timestamp;

@Override
public void run() {
/*
* Calculates the time in base 10 time and calls the 4 methods
* to set the GUI display.
*
* In a constant while loop in order to continuously update
* the GUI.
*/
now = Calendar.getInstance();
timestamp = now.get(Calendar.HOUR_OF_DAY)*60*60 + now.get(Calendar.MINUTE)*60 + now.get(Calendar.SECOND);
currentTime = timestamp/86400.0;
timeString = "" + currentTime;
Platform.runLater(new Runnable() {
@Override public void run() {
setFirstDigit(timeString.charAt(2));
setSecondDigit(timeString.charAt(3));
setThirdDigit(timeString.charAt(4));
setFourthDigit(timeString.charAt(5));
}
});
}
}, 0, 8640 // Sleep for 8.64 seconds since that is how long it is between
); // increments of 0.01 in base 10 time.
}
}

Updating UI from different threads in JavaFX

Not sure if I completely understand, but I think this may help.

Using Platform.runLater(...) is an appropriate approach for this.

The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater if it's been set back to its sentinel value.

I figured this out by looking at the source code for Task. Have a look at how the updateMessage method (line 1131 at the time of writing) is implemented.

Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater.

In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.

import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class ConcurrentModel extends Application {

@Override
public void start(Stage primaryStage) {

final AtomicInteger count = new AtomicInteger(-1);

final AnchorPane root = new AnchorPane();
final Label label = new Label();
final Model model = new Model();
final NumberFormat formatter = NumberFormat.getIntegerInstance();
formatter.setGroupingUsed(true);
model.intProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(final ObservableValue<? extends Number> observable,
final Number oldValue, final Number newValue) {
if (count.getAndSet(newValue.intValue()) == -1) {
Platform.runLater(new Runnable() {
@Override
public void run() {
long value = count.getAndSet(-1);
label.setText(formatter.format(value));
}
});
}

}
});
final Button startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
model.start();
}
});

AnchorPane.setTopAnchor(label, 10.0);
AnchorPane.setLeftAnchor(label, 10.0);
AnchorPane.setBottomAnchor(startButton, 10.0);
AnchorPane.setLeftAnchor(startButton, 10.0);
root.getChildren().addAll(label, startButton);

Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}

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

public class Model extends Thread {
private IntegerProperty intProperty;

public Model() {
intProperty = new SimpleIntegerProperty(this, "int", 0);
setDaemon(true);
}

public int getInt() {
return intProperty.get();
}

public IntegerProperty intProperty() {
return intProperty;
}

@Override
public void run() {
while (true) {
intProperty.set(intProperty.get() + 1);
}
}
}
}

If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an AnimationTimer. An AnimationTimer has a handle(...) which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. The handle(...) method is invoked on the FX Application Thread.

The handle(...) method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.

For example:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;

public class Main extends Application {
@Override
public void start(Stage primaryStage) {

final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);

TextArea console = new TextArea();

Button startButton = new Button("Start");
startButton.setOnAction(event -> {
MessageProducer producer = new MessageProducer(messageQueue);
Thread t = new Thread(producer);
t.setDaemon(true);
t.start();
});

final LongProperty lastUpdate = new SimpleLongProperty();

final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.

AnimationTimer timer = new AnimationTimer() {

@Override
public void handle(long now) {
if (now - lastUpdate.get() > minUpdateInterval) {
final String message = messageQueue.poll();
if (message != null) {
console.appendText("\n" + message);
}
lastUpdate.set(now);
}
}

};

timer.start();

HBox controls = new HBox(5, startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);

BorderPane root = new BorderPane(console, null, null, controls, null);
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.show();
}

private static class MessageProducer implements Runnable {
private final BlockingQueue<String> messageQueue ;

public MessageProducer(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue ;
}

@Override
public void run() {
long messageCount = 0 ;
try {
while (true) {
final String message = "Message " + (++messageCount);
messageQueue.put(message);
}
} catch (InterruptedException exc) {
System.out.println("Message producer interrupted: exiting.");
}
}
}

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

JavaFx - Updating GUI

If you run the entire process on the FX Application thread, then that is (effectively) the same thread that is being used to display the UI. If both the display of the UI, and your file iteration process are running in the same thread, only one can happen at once. So you prevent the UI from updating until the process is complete.

Here's a simple example where I just pause for 250 milliseconds between each iteration (simulating reading a reasonably large file). One button launches this in the FX Application thread (notice how the UI is unresponsive while this runs - you can't type in the text field). The other button uses a Task to run it in the background, properly scheduling updates to the UI on the FX Application thread.

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class UpdateTaskDemo extends Application {

@Override
public void start(Stage primaryStage) {
Label label = new Label();
Button runOnFXThreadButton = new Button("Update on FX Thread");
Button runInTaskButton = new Button("Update in background Task");
HBox buttons = new HBox(10, runOnFXThreadButton, runInTaskButton);
buttons.setPadding(new Insets(10));
VBox root = new VBox(10, label, buttons, new TextField());
root.setPadding(new Insets(10));

runOnFXThreadButton.setOnAction(event -> {
for (int i=1; i<=10; i++) {
label.setText("Count: "+i);
try {
Thread.sleep(250);
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption");
}
}

});

runInTaskButton.setOnAction(event -> {
Task<Void> task = new Task<Void>() {
@Override
public Void call() throws Exception {
for (int i=1; i<=10; i++) {
updateMessage("Count: "+i);
Thread.sleep(250);
}
return null ;
}
};
task.messageProperty().addListener((obs, oldMessage, newMessage) -> label.setText(newMessage));
new Thread(task).start();
});

primaryStage.setScene(new Scene(root, 400, 225));
primaryStage.show();
}

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

Updating JavaFX GUI from a service thread

The "low-level" approach is to use Platform.runLater(Runnable r) to update the UI. This will execute r on the FX Application Thread, and is the equivalent of Swing's SwingUtilities.invokeLater(...). So one approach is simply to call Platform.runLater(...) from inside your call() method and update the UI. As you point out, though, this essentially requires the service knowing details of the UI, which is undesirable (though there are patterns that work around this).

Task defines some properties and has corresponding updateXXX methods, such as the updateMessage(...) method you call in your example code. These methods are safe to call from any thread, and result in an update to the corresponding property to be executed on the FX Application Thread. (So, in your example, you can safely bind the text of a label to the messageProperty of the service.) As well as ensuring the updates are performed on the correct thread, these updateXXX methods also throttle the updates, so that you can essentially call them as often as you like without flooding the FX Application Thread with too many events to process: updates that occur within a single frame of the UI will be coalesced so that only the last such update (within a given frame) is visible.

You could leverage this to update the valueProperty of the task/service, if it is appropriate for your use case. So if you have some (preferably immutable) class that represents the result of parsing the packet (let's call it PacketData; but maybe it is as simple as a String), you make

public class StatusListener implements Service<PacketData> {

// ...

@Override
protected Task<PacketData> createTask() {
return new Task<PacketData>() {
// ...

@Override
public PacketData call() {
// ...
while (! isCancelled()) {
// receive packet, parse data, and wrap results:
PacketData data = new PacketData(...);
updateValue(data);
}
return null ;
}
};
}
}

Now you can do

StatusListener listener = new StatusListener();
listener.valueProperty().addListener((obs, oldValue, newValue) -> {
// update UI with newValue...
});
listener.start();

Note that the value is updated to null by the code when the service is cancelled, so with the implementation I outlined you need to make sure that your listener on the valueProperty() handles this case.

Also note that this will coalesce consecutive calls to updateValue() if they occur within the same frame rendering. So this is not an appropriate approach if you need to be sure to process every data in your handler (though typically such functionality would not need to be performed on the FX Application Thread anyway). This is a good approach if your UI is only going to need to show the "most recent state" of the background process.

SSCCE showing this technique:

import java.util.Random;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class LongRunningTaskExample extends Application {

@Override
public void start(Stage primaryStage) {
CheckBox enabled = new CheckBox("Enabled");
enabled.setDisable(true);
CheckBox activated = new CheckBox("Activated");
activated.setDisable(true);
Label name = new Label();
Label value = new Label();

Label serviceStatus = new Label();

StatusService service = new StatusService();
serviceStatus.textProperty().bind(service.messageProperty());

service.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) {
enabled.setSelected(false);
activated.setSelected(false);
name.setText("");
value.setText("");
} else {
enabled.setSelected(newValue.isEnabled());
activated.setSelected(newValue.isActivated());
name.setText(newValue.getName());
value.setText("Value: "+newValue.getValue());
}
});

Button startStop = new Button();
startStop.textProperty().bind(Bindings
.when(service.runningProperty())
.then("Stop")
.otherwise("Start"));

startStop.setOnAction(e -> {
if (service.isRunning()) {
service.cancel() ;
} else {
service.restart();
}
});

VBox root = new VBox(5, serviceStatus, name, value, enabled, activated, startStop);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}

private static class StatusService extends Service<Status> {
@Override
protected Task<Status> createTask() {
return new Task<Status>() {
@Override
protected Status call() throws Exception {
Random rng = new Random();
updateMessage("Running");
while (! isCancelled()) {

// mimic sporadic data feed:
try {
Thread.sleep(rng.nextInt(2000));
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
if (isCancelled()) {
break ;
}
}

Status status = new Status("Status "+rng.nextInt(100),
rng.nextInt(100), rng.nextBoolean(), rng.nextBoolean());
updateValue(status);
}
updateMessage("Cancelled");
return null ;
}
};
}
}

private static class Status {
private final boolean enabled ;
private final boolean activated ;
private final String name ;
private final int value ;

public Status(String name, int value, boolean enabled, boolean activated) {
this.name = name ;
this.value = value ;
this.enabled = enabled ;
this.activated = activated ;
}

public boolean isEnabled() {
return enabled;
}

public boolean isActivated() {
return activated;
}

public String getName() {
return name;
}

public int getValue() {
return value;
}
}

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

Javafx Gui update

You're using a variable with local scope in an anonymous inner class. You can only do this if the variable is final. If you need to change the value of the variable, you could define it as an instance variable.

public class MainApp extends Application {

private int c;

@Override
public void start(Stage primaryStage) {
Button btn = new Button();
final Line l1 = new Line(200,80,200,0);

btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {

@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");

Platform.runLater(new Runnable() {
Random rn = new Random();
@Override

public void run() {
if(c==0)
{
System.out.println("c=" + c);
l1.setStartX(rn.nextInt(400));
l1.setStartY(rn.nextInt(400));
c = 1;
}
else
{
System.out.println("c=" + c);
l1.setEndX(rn.nextInt(400));
l1.setEndY(rn.nextInt(400));
c = 0;
}
}
});
}
});

l1.setStroke(Color.YELLOW);
l1.setStrokeWidth(2);

StackPane root = new StackPane();
root.getChildren().add(btn);
root.getChildren().add(l1);

Scene scene = new Scene(root, 400, 400, Color.WHITE);

primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}

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

}

Output after a couple of button clicks:

Hello World!

c=0

Hello World!

c=1

Hello World!

c=0

JavaFX make threads wait and threadsave gui update

The (main) problem with your code is that you are calling latch.await(), which is a blocking method, on the JavaFX Application Thread. Since the JavaFX Application Thread is responsible for updating the UI, this prevents the UI from being redrawn until latch.await() releases.

The basic premise of your question is wrong: you never want to make the UI thread pause, as it will always render the UI unresponsive and prevent any updates. Instead, you should think in terms of "performing a unit of work" in the background, potentially with updates to the UI as it proceeds, and then doing something in response to the background work completing.

Another potential issue with your code is that you are submitting a vast number of Runnables to the FX Application Thread via Platform.runLater(). You probably need to throttle these so that they don't "flood" the FX Application Thread.

You can solve all these issues using the Task API. The Task class is an implementation of Runnable whose call() method is invoked from the run() method. It has various updateXXX methods, including updateProgress(), that update various properties on the FX Application thread, and throttle these calls so that no more are scheduled than the FX Application thread can handle. Finally, it has callback methods, such as setOnSucceeded() that are invoked on the FX Application Thread when the background work completes (or, generally, when the task changes its lifecycle state).

(Note: I renamed your classes so they conform to recommended naming conventions. Like most Java developers, I find it extremely difficult to read code that doesn't conform to these.)

public class Test {
final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final Worker work = new Worker();
final Worker2 work2 = new Worker2();

// GUI Scene change happens here

work.setOnSucceeded(e -> work2.doSomething(work.getValue(), area));
bar.progressProperty().bind(work.progressProperty());
new Thread(work).start();

}
public class Worker extends Task<Integer> {
@Override
protected Integer call(){
for(int i = 1; i <= 1000000; i++)
updateProgress(i, 1000000);
return 51;
}
}
public class Worker2{
ArrayList<String> list = new ArrayList<>();

// this is now executed on the FX Application Thread: there is no need
// for Platform.runLater():

public int doSomething(int index, TextArea area){
area.append(list.get(index));
}
}

Your second example is a little more complicated, and I'm not really sure you need the additional threads at all: the only thing your Worker3 seems to do is append a line to a text area, which has to be done on the FX Application Thread anyway. But in case your real application would need background work for each file, this is what it would look like. I would recommend using a task pool for this instead of creating so many tasks by hand. This would look something like



Related Topics



Leave a reply



Submit