JavaFX many task work in GUI - javafx

I ran a thread that updates the open time of the application. It works well. I've expanded the Service class. The time from this task update my GUI, textField by Platform.runLater
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (!isCancelled()) {
if (isPause == false) {
try {
Platform.runLater(() -> {
currentTimeInApp = currentTimeInApp + 1;
upPanelController.timeInApp.setText
(currentTimeInApp.toString());
}
});
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
if (isCancelled())
break;
}
}
return null;
}
};
}
I would like to run a second thread which also updates GUI. I can't run the same thread. Can two independent threads be updated GUI ?
Most of the information on the internet is dedicated to one topic. Thank you for every suggestion

Yes, you can use as many threads as you like. You just have to make sure that you always do the GUI update via Platform.runLater.

Related

JavaFX: how to read CurrentTime of MediaPlayer while not in JavaFX App Thread [duplicate]

This question already has answers here:
Return result from javafx platform runlater
(2 answers)
Closed 3 years ago.
Is there a thread safe way to read properties of the JavaFX MediaPlayer (such as CurrentTime)?
To play/change a media I typically use Platform.runLater, but I need the CurrentTime immediately returned.
Is it safe to do myplayer.getCurrentTime()? Or will I get into trouble if the player is disposed off by another thread?
EXAMPLE:
private MediaPlayer player;
public void playMe(){
Platform.runLater(
new Runnable() {
#Override
public void run() {
player = new MediaPlayer(media);
player.play();
}});
}
public void deleteMe(){
Platform.runLater(
new Runnable() {
#Override
public void run() {
if (player != null) player.dispose();
player = null;
}});
}
public Double getCurrentTime(){
if (player != null) return player.getCurrentTime().toSeconds(); // thread issues???
else return null;
}
JavaFX properties are not thread-safe. Memory consistency is not guaranteed: Java is allowed to create a copy of the memory containing the property object for the background thread at any time. When this happens, any changes to the property happening later won't be visible to the background thread.
It's not too hard to make sure the value is accessible from the thread though. Depending on the frequency of the access and the delay you're willing accept for the information retrieval, the following approaches could work for you:
Updating a AtomicReference from a listener
This way you simply make sure the updates become visible to the background thread by assigning the value to a AtomicReference on the application thread:
final AtomicReference<Duration> time = new AtomicReference<>(player.getCurrentTime());
player.currentTimeProperty().addListener((o, oldValue, newValue) -> time.set(newValue));
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
System.out.println(time.get());
}
});
t.setDaemon(true);
t.start();
The drawback is that the updates to the reference happen more often than necessary. (A volatile field may do the trick too btw.)
Querying the property using Platform.runLater
As alternative you could simply schedule a runnable reading the variable using Platform.runLater. This approach does not require a reference to be continuously updated:
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
CompletableFuture<Duration> future = new CompletableFuture<>();
Platform.runLater(() -> future.complete(player.getCurrentTime()));
try {
Duration time = future.get(); // get value as soon as evaluated on application thread
System.out.println(time);
} catch (InterruptedException e) {
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
});
t.setDaemon(true);
t.start();
Note: for both approaches you need to deal with the fact that you may set the player field to null. Note that any test is subject to the same memory consistency issues as described at the start of the answer. The first approach would require you to make the field volatile to make sure the change is visible to the background thread too, for the second approach you could either cancel the future or throw cause an exception to notify the caller: future.completeExceptionally and future.cancel(true) result in future.get() yielding an ExecutionException or a CancelationException respecively.

JavaFX extends Service<> return value

I have task in my app, and i do not know how it works return from this task.
public class TimeManager extends Service<String> {
#Override
protected Task<String> createTask() {
return new Task<String>() {
#Override
protected String call() throws Exception {
String txt = null;
while (!isCancelled()) {
try {
txt = "some txt";
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
return txt;
}
};
}
And in Main Class:
TimeManager time = new TimeManager();
time.start();
time.getValue();
time allways return null. What do I have to do to return the value?
Thread works good and I can send data from the thread to the application
Your task does not publish intermediate updates. Furthermore Service is used to run tasks in background threads to avoid blocking the JavaFX application thread. For this reason the value is likely to not be assigned if you access it directly after starting the service. It would be better to use a binding or a listener to the value property to retrieve the data when it's assigned.
public class TimeManager extends Service<String> {
#Override
protected Task<String> createTask() {
return new Task<String>() {
int i = 0;
#Override
protected String call() throws Exception {
String txt = null;
while (!isCancelled()) {
txt = Integer.toString(++i);
updateValue(txt); // publish new value
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
return txt;
}
};
}
}
TimeManager time = new TimeManager();
label.textProperty().bind(time.valueProperty());
time.start();
Note that in this case a service may not be required since you're running only a single task. Running the Task instance using new Thread(task).start() may actually suffice.
Furthermore there are better options for scheduling fast repeating updates of the GUI, see JavaFX periodic background task

JavaFX - Call "updateMessage" for TextArea from background Task - Two problems found

I am having two problems when trying to use "updateMessage" in a JavaFX task.
Issue #1
seems to be a known behavior, but I am not yet sure how exactly I can workaround it.This one is not (yet) critical to me.
The problem is that not all the updates I am performing in a background Task are displayed in the UI (at least the UI does not hang/freezes anymore, which was my initial issue).
My Code of the UI part:
TextArea console = new TextArea();
Button button01 = new Button("Start");
button01.setOnAction(new EventHandler() {
#Override
public void handle(Event event) {
if (ms.getState() == State.READY) {
ms.messageProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
console.appendText(newValue+"\n");
}
});
ms.start();
}
}
});
My Service:
public class MyService extends Service<Object> {
#Override
protected Task createTask() {
//here we use "MyTask" first to show problem #1
MyTask ct = new MyTask();
//here we use "MyTask2" first to show problem #2
// MyTask2 ct = new MyTask2();
try {
ct.call();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("MyService end");
return ct;
}
}
My Task (#1)
public class MyTask extends Task<Object> {
#Override
public EventHandler<WorkerStateEvent> call() {
System.out.println("call() is called");
if (Thread.currentThread().getName().equals("JavaFX Application Thread")){//yes, this might not be right, but if I do not do this, my stuff is executed twice because "call()" is called twice, but the textarea area is just updated in the second run (the non javafx application thread).
return null;
} else{
//actually here I want to do some 'heavy' stuff in the background
//and many things of this heavy stuff should be displayed / logged within the UI
//but very likely (hopefully) new messages (updateMessage) will not be send as fast as in the following loop
for (int i=0;i<10000000;i++){
updateMessage("This is update number'"+i+"' from the background thread");
}
Platform.runLater(new Runnable() {
#Override
public void run() {
try{
//here is the chance to get back to the view
}finally{
}
}
});
return null;
}
}
This basically works, but not every single loop is displayed in the UI.
How do I (correctly) make sure every loop is displayed?
Screenshot: Messages are displayed but not for every loop
Issue #2
Currently blocks my attempt to bring my little text-based game into a JavaFX application.
The main problem is that I am able to call "updateMessage" from the Task directly (see above), but not from a another (sub-)class which I would need to bring all message updates from my game (each message describes the progress of the game) to the UI.
The Task I use (Task #2):
public class MyTask2 extends Task<Object> {
#Override
public EventHandler<WorkerStateEvent> call() {
// ...
UITools myTools = new UITools();
myTools.logToUITest("Just one simple message");
// ...
Platform.runLater(new Runnable() {
#Override
public void run() {
try{
//here is the chance to get back to the view
}finally{
}
}
});
return null;
}
and the (sub-)class that I want to use to do the updateMessage (actually in my little game there would be even more classes that are called during the game and almost all of them trigger an update/message).
public class UITools {
public void logToUITest(String message){
updateMessage(message);
//how to allow 'updateMessage' from the Task to be executed from here?
}
This already results in "The method updateMessage(String) is undefined...".
How could I make it possible to call the updateMessage outside of the Task itself?
updateMessage() can only be called from within the call() method of a Task. It's a constraint imposed by the design of the Task class.
The missed message updates are due to the fact that there are too many updates and not all of them are forwarded to the event queue. Try to reduce the number of updates or sleep for a little while to separate them out in time

Vaadin button setEnabled(false) doesn't enable

Vaadin newbie: When a user presses a button, I like to disable it so he knows that he clicked it and there's some work going on in the background. When the (long) task is completed, I'd like to enable the button.
For this, I'm using 2 threads (background and work) but for some reason the button doesn't enabled at the end of the task.
In other words, once clicked it goes to enabled(false) and never coming back. Why? and how can I fix it?
button.addClickListener(new ClickListener()
{
public void buttonClick(ClickEvent event)
{
Thread background = new Thread(new Runnable(){
#Override
public void run()
{
Thread work = new Thread(new Runnable(){
#Override
public void run()
{
button.setEnabled(false);
try
{
Thread.sleep(2000); //long work here!
} catch (InterruptedException e)
{
e.printStackTrace();
}
button.setEnabled(true); //doesn't enable at the end of the long work!
}});
work.start();
try
{
work.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}});
background.start();
}
});
Maybe the best approach would be to use Button.setDisableOnClick(true) for the button and do the processing directly in the event handler without a background thread. This will show the standard loading indicator to the user until processing is done.
Otherwise you need to enable server push (#Push) and remember to use UI.access() in the background thread before updating the UI. See https://vaadin.com/book/-/page/advanced.push.html

JavaFX auto-scroll auto-update text

Newbie question about JavaFX that I haven't been able to answer, despite knowing it must be pretty simple to do and not finding any resources on it anywhere I've looked (tutorials, many of the Oracle online docs, articles, the well-known JavaFX bloggers, etc.)
I'm developing a command line (script) running application and I have successfully gotten output (via ProcessBuilder) from the script that I can display in an ongoing manner, as things happen on the command line. That is, I can do System.out.println(line); all day long, showing the output in the console, which simply returns output from an input stream returned by the 'myProcess' that's running, created like this:
BufferedReader bri = new BufferedReader(new InputStreamReader(myProcess.getInputStream()))
So I am able to see all the output coming back from the script.
I'd like to set-up a JavaFX TextArea or ScrollPane or, not sure what, to display this output text (there's a lot of it, like several thousand lines) as an ongoing 'progress' of what's taking place in the script, as it happens. I have a Scene, I have buttons and get input from this scene to start the script running, but now I'd like to show the result of clicking the button "RUN THIS SCRIPT", so to speak.
I assume I need to create a TextArea as described here or perhaps a TextBuilder would be useful to begin making it. Not sure.
I need a bit of help in how to setup the binding or auto-scroll/auto-update part of this.
Can someone provide me a place to start, to do this with JavaFX? I'd rather not use Swing.
(I'm using JavaFX 2.2, JDK 1.7u7, all the latest stuff, and yes, this is an FXML app--so doing it that way would be preferred.)
UPDATE: Sergey Grinev's answer was very helpful in the binding part. But here is some more detail on what I mean when I ask for "a bit of help in how to setup" -- basically, I need to return control to the main Scene to allow the user to Cancel the script, or to otherwise monitor what's going on. So I'd like to "spawn" the process that runs that script (that is, have some kind of 'free running process'), but still get the output from it. (I wasn't very clear on that in my initial question.)
The technique I'm using here (see below) is to do a waitFor on the process, but of course this means the dialog/Scene is 'hung' while the script executes. I'd like to gain control back, but how do I pass the 'p' (Process) to some other controller piece (or alternatively, simply kick off that other process passing in the parameters to start the script and have it start the script) that will then do the auto-update, via the binding Sergey Grinev mentions--without 'hanging' the Scene/window? Also: Can I then 'stop' this other process if the user requests it?
Here is my current code ('waits' while script--which takes 20-40 min to run!--completes; this is not what I want, I'd like control returned to the user):
public class MyController implements Initializable {
#FXML
private void handleRunScript(ActionEvent event) throws IOException {
ProcessBuilder pb = new ProcessBuilder("myscript.sh", "arg1", "arg2", ...);
Process p = pb.start();
try {
BufferedReader bri = new BufferedReader
(new InputStreamReader(p.getInputStream()));
String line;
while ((line = bri.readLine()) != null) {
System.out.println(line);
textAreaRight.setText(line);
}
bri.close();
p.waitFor();
}
catch (Exception err) {
err.printStackTrace();
}
}
#FXML
private void handleCancel(ActionEvent event) {
doSomethingDifferent();
}
}
To log strings you can use TextArea
To make it asynchronious you need to make a separate thread for output reader.
public class DoTextAreaLog extends Application {
TextArea log = new TextArea();
Process p;
#Override
public void start(Stage stage) {
try {
ProcessBuilder pb = new ProcessBuilder("ping", "stackoverflow.com", "-n", "100");
p = pb.start();
// this thread will read from process without blocking an application
new Thread(new Runnable() {
#Override
public void run() {
try {
//try-with-resources from jdk7, change it back if you use older jdk
try (BufferedReader bri = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = bri.readLine()) != null) {
log(line);
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}).start();
stage.setScene(new Scene(new Group(log), 400, 300));
stage.show();
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public void stop() throws Exception {
super.stop();
// this called on fx app close, you may call it in user action handler
if (p!=null ) {
p.destroy();
}
}
private void log(final String st) {
// we can access fx objects only from fx thread
// so we need to wrap log access into Platform#runLater
Platform.runLater(new Runnable() {
#Override
public void run() {
log.setText(st + "\n" + log.getText());
}
});
}
public static void main(String[] args) {
launch();
}
}

Resources