Changing mouse cursor for long actions in JavaFX [duplicate] - javafx

I would understand why
scene.setCursor(Cursor.WAIT);
long task...
scene.setCursor(Cursor.DEFAULT);
needs new threads; it works with:
private void set_cursore_attesa(final Scene scene)
{
Runnable r=new Runnable() {
#Override
public void run() {
scene.setCursor(Cursor.WAIT);
}
};
Thread t=new Thread(r);
t.start();
}
private void set_cursore_normale(final Scene scene)
{
Runnable r=new Runnable() {
#Override
public void run() {
scene.setCursor(Cursor.DEFAULT);
}
};
Thread t=new Thread(r);
t.start();
}
in my function:
set_cursore_attesa(scene);
long task...
set_cursore_normale(scene);
why I can't use the same thread?
I:
set my cursor to WAIT (it goes in GUI queue)
do my long task... (it goes in GUI queue but I expected that cursor changing, that is up in queue, it is executed before this)
reset my cursor to DEFAULT (after my task has finished)
So, my long task doesn't go in MAIN queue? because, if it goes in main queue, I expected it's executed after my WAIT cursor that is inserted in queue first.
Why this behavior?

Without the threads, your code is being executed on the FX Application Thread. This is the thread that is (effectively) responsible for rendering the UI to the screen and for processing user input. If you execute a long-running task on this thread, then you prevent any of the normal functionality of the FX Application Thread from occurring until your long-running task is complete. In particular, if you do
scene.setCursor(Cursor.WAIT);
longRunningTask();
scene.setCursor(Cursor.DEFAULT);
then the settings take place in the order you specify, but the scene does not get rerendered until all lines of code are complete. Hence you never actually see any changes to the UI - including the change to the cursor - until after your code is complete. The next time the FX Application Thread has an opportunity to render the scene, the cursor is set to Cursor.DEFAULT, and you never see the wait cursor.
There are two basic rules for multithreading and JavaFX (and the same rules generally apply to most UI toolkits):
Any changes to the UI must be performed on the FX Application Thread
Long-running processes should not be performed on the FX Application Thread (as they make the UI unresponsive)
So your solution is not actually correct, because you violate both of those rules. You should
Set the cursor to WAIT on the FX Application Thread
Start your long running task on a background thread
When the task is complete, set the cursor back to DEFAULT, on the FX Application Thread.
You can do this using a Task:
scene.setCursor(Cursor.WAIT);
Task<Void> task = new Task<Void>() {
#Override
public Void call() {
// long running task here...
return null ;
}
};
task.setOnSucceeded(e -> scene.setCursor(Cursor.DEFAULT));
new Thread(task).start();

Related

Task doesn't update local variable

I'm using a Task to read a text file, the task is invoked when the user clicks "open file" menu, it is supposed to read the text file, and then update the local variable "text", the problem occurs at the first try, if i open a file, nothing happens, and the value of the text string stays as it is, if i open any file again, everything works as expected, i couldn't find the cause of this.
The method that has the task
private void readFile(File file){
Task<String> task = new Task<String>() {
#Override
protected String call() {
List<String> list = EditorUtils.readFromFile(file);
String str = list.stream().collect(Collectors.joining("\n"));
return str;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
setCurrentText(task.getValue());
}
});
task.setOnFailed(e -> setCurrentText("FAILED"));
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
SetCurrentText
private void setCurrentText(String text){
this.text = text;
}
The method of the controller
#FXML
void openMenuItemClick(ActionEvent event) {
fileChooser.setTitle("title");
fileChooser.getExtensionFilters().add
(new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt"));
File file = fileChooser.showOpenDialog(open.getParentPopup().getScene().getWindow());
if (file != null){
readFile(file);
System.out.println(text); //prints null since "text" isn't initialized yet
}
}
EditorUtils#readFromFile
public static List<String> readFromFile(File file){
List<String> lines = new ArrayList<>();
try {
lines = Files.readAllLines(Paths.get(file.getPath()), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
return lines;
}
Your readFile method creates a Task, gives it to a Thread, launches the Thread, and returns. You then try to immediately print out the value of text. There's no guarantee your Task will have completed by the time you call println(text). In fact, it's very likely your Task has not completed yet. But that's not the only problem.
The call to readFile and println are both done on the same thread—in this case, the JavaFX Application Thread. The problem here is that the EventHandler you pass to setOnSucceeded will be invoked on the FX thread as well. The way this is achieved internally is with a Platform.runLater call which schedules the action with the FX thread to be ran some time in the future. This can't happen while the FX thread is executing openMenuItemClick and must wait until the method returns.
What this all means is that setCurrentText will never run until after the call to println. But by the second time openMenuItemClick is invoked the text will have been set1. So what you're seeing the second time is actually the result of the first Task.
If you want to do something with text once the Task completes then you should do it inside the onSucceeded or onFailed handler. Or you can make text a StringProperty and observe it for changes.
1. Technically, it may have been set. There's still no guarantee the Task has completed by then.
This is perfectly normal behaviour when using multiple threads. You access the file from a task running on background thread. On completion this task triggers an update on the JavaFX application thread.
By the time readFile returns the task may not have been completed. The fact that Task uses Platform.runLater to execute the onSucceeded handler results in this handler never being invoked before the openMenuItemClick method completes, even if the file is read before System.out.println is reached.
If you need to update the GUI based on the result of the Task, you should do so from the event handler. The code updating the text field runs after the System.out.println(text); statement. The second time you start the task, you print results of the task started the first time the menu item was clicked, not the new one. You can verify this by moving the println to the beginning of the openMenuItemClick method.

How to wait for user input on JavaFX application thread without using showAndWait?

I'd like to pause the execution of a method on the JavaFX application thread and wait until the user does interaction with the UI. It's important not to freeze the UI.
Example:
Button start = ...
Button resume = ...
start.setOnAction(evt -> {
System.out.println("starting");
start.setDisable(true);
System.out.println("please press resume button.");
pause();
System.out.println("done");
start.setDisable(false);
});
resume.setOnAction(evt -> resume());
How should I implement the pause() and resume() methods?
The execution of the event handler should wait at pause(); call until the user presses the resume button and the resume method is called.
You can do so by using Platform.enterNestedEventLoop to pause the execution of the event handler and Platform.exitNestedEventLoop (available since JavaFX 9) to resume the execution:
private final Object PAUSE_KEY = new Object();
private void pause() {
Platform.enterNestedEventLoop(PAUSE_KEY);
}
private void resume() {
Platform.exitNestedEventLoop(PAUSE_KEY, null);
}
Platform.enterNestedEventLoop returns when Platform.exitNestedEventLoop is called with the same parameter passed as first argument.
I am currently running JFX 8 where I have the similar feature in the Toolkit class.
Toolkit.getToolkit().enterNestedEventLoop(obj);
and
Toolkit.getToolkit().exitNestedEventLoop(obj);
Have not looked at the JFX 9 source, but my bet is that the Platform methods are simply shortcuts to the same.

Multithreading using Callable while having a responsive graphical interface

I'm trying to get a responsive JavaFX graphical interface while executing a cmd command.
The command I'm executing is the following.
youtube-dl.exe --audio-format mp3 --extract-audio https://www.youtube.com/watch?v=l2vy6pJSo9c
As you see this is a youtube-downloader that converts a youtube link to an mp3-file.
I want this to be executed in a second thread and not in the main FX thread.
I've solved this by implementing interface Callable in the class StartDownloadingThread.
#Override
public Process call() throws Exception {
Process p = null;
p = ExecuteCommand(localCPara1, localCPara2, localDirectory).start();
try {
Thread.sleep(30);
}catch (InterruptedException e){}
return p;
}
The method ExecuteCommand just returns a ProcessBuilder object.
I try to use Thread.sleep to make the program return to the main thread and thus making the application responsive. Unfortunately the program still freezes.
This is how the method call is called.
ExecutorService pool = Executors.newFixedThreadPool(2);
StartDownloadingThread callable = new StartDownloadingThread(parameter1, parameter2, directory);
Future future = pool.submit(callable);
Process p = (Process) future.get();
p.waitFor();
How do I make my GUI responsive using the interface Callable?
Using a executor to run a task just for you to use the get method of the Future that is returned when submitting the task does not actually free the original thread to continue with other tasks. Later you even use the waitFor method on the original thread, which is likely to take even more time than anything you do in your Callable.
For this purpose the Task class may be better suited, since it allows you to handle success/failure on the application thread using event handlers.
Also please make sure an ExecutorService is shut down after you're done submitting tasks.
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
Process p = null;
p = ExecuteCommand(localCPara1, localCPara2, localDirectory).start();
// why are you even doing this?
try {
Thread.sleep(30);
}catch (InterruptedException e){}
// do the rest of the long running things
p.waitFor();
return null;
}
};
task.setOnSucceeded(event -> {
// modify ui to show success
});
task.setOnFailed(event -> {
// modify ui to show failure
});
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(task);
// add more tasks...
// shutdown the pool not keep the jvm alive because of the pool
pool.shutdown();

Java FX showing a LoadingScreen while code and other objects are running/drawn

So I want to display a Loading Screen(simple Stackpane with an ProgressIndicator on top of a blur etc..) over my Desktop Application while another Screen is loading data from the database and drawing itself. Up until now I used a Task in a new Thread to "split" my loading animation running in the FX thread from the logic in the background, problem is this time logic and drawing objects on the screen in the bakckground are heavily intervowen and I cannot split them without stupenduous amount of work.
So I found out about AnimationTimer, which apparently is called on every frame and sends a pulse which causes an equivalent to a repaint() in Swing. And so I tried to use it thus:
public void showApplicationScreen(){
public void addAndShowTransporterStatus() {
AnimationTimer at = new AnimationTimer() {
int i;
#Override
public void handle(long now) {
i++;
LOG.info(i); //testing to see frames per second
}
};
at.start();
showLoadingIndicator(true);
loadDataFromDBandDrawObjects();
showLoadingIndicator(false);
at.stop();
}
Is there some kind of trick to it that I am missing? Or some other (simple) way?
I cant believe something so simple is so complicated to do. Gotta say I wish every Node had a repaintAtIntervall(double timespan) method that would suspend everything else the application is doing atm and repaint itself before continuing with the normal flow. Might not be pretty but it sure as hell would be useful.
You really shouldn't need an AnimationTimer for something like this. If you are doing the initial loading in a background thread, use a Task. Show a loading screen and hide it in the task's onSucceeded handler. You can create node instances in a background thread as long as they are not part of the scene graph, so while it's not a particularly good design, you can do something like:
Task<Parent> createMainScene = new Task<Parent>() {
#Override
public Parent call() {
Parent root = ... ;
// load data etc., create structure below root
// call updateMessage(...) to update a status message if needed
// call updateProgress(...) to update the progress if needed
// ...
return root ;
}
};
ProgressBar pBar = new ProgressBar();
pBar.progressProperty().bind(createMainScene.progressProperty());
Label statusLabel = new Label();
statusLabel.textProperty().bind(createMainScene.messageProperty());
VBox root = new VBox(5, statusLabel, pBar);
Stage loadingStage = new Stage(new Scene(root));
loadingStage.show();
createMainScene.setOnSucceeded(e -> {
primaryStage.setScene(new Scene(createMainScene.getValue()));
primaryStage.show();
loadingStage.hide();
});
new Thread(createMainScene).start();
A better (more properly-separated) design would be to have the task just load the application data and process it, and return an object encapsulating the data. Then in the onSucceeded handler you would create the UI from the data (which should not take a long time). However, it sounds like you cannot do that with the current code with which you're working.

JavaFX show loading dialog for longer operations

I got some operations in my Controller class which could take some time. So I want to show a loading dialog while this operation is running.
I tried this:
Platform.runLater(new Runnable() {
#Override
public void run() {
loadingDialog.show();
}
});
Boolean opSuccess = myService.operate();
Platform.runLater(new Runnable() {
#Override
public void run() {
loadingDialog.hide();
}
});
if (opSuccess) {
// continue
}
Now, the Problem is, the loadingDialog is never show. The UI only blocks for some time and than continues on "//continue".
So it seems, the runLater call is blocked by the blocking operation (operate)?
I also tried CoundDownLatch, to wait for loadingDialog.show() to complete, before running myService.operate(). But the latch.await() method never completes.
So my question is, how my I show the loadingDialog until myService.operate() finished and returned true or false? Do I have to put the operate() call into another thread and run it async or is there an easier way?
Thanks for help.
Are you sure your entire code does not run in the JavaFX Thread?
Methods of your controller class usually do and I assume it due to your description.
However, better use the Task class. Here you'll find a tutorial and a short snippet for your application:
// here runs the JavaFX thread
// Boolean as generic parameter since you want to return it
Task<Boolean> task = new Task<Boolean>() {
#Override public Boolean call() {
// do your operation in here
return myService.operate();
}
};
task.setOnRunning((e) -> loadingDialog.show());
task.setOnSucceeded((e) -> {
loadingDialog.hide();
Boolean returnValue = task.get();
// process return value again in JavaFX thread
});
task.setOnFailed((e) -> {
// eventual error handling by catching exceptions from task.get()
});
new Thread(task).start();
I assumed Java 8 and the possibility to use Lambda expressions. Of course it is possible without them.
You are better off making use of concurrency mechanisms/Worker interfaces in JavaFx - Tasks and services instead of using Platform.runLater(). The tasks and services allow you to manage the long running tasks in a separate thread. They also provide callbacks to indicate the progress of the tasks.
You could explore further at http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
Also have a look at the Ensemble JavaFX samples for Tasks and Services - http://www.oracle.com/technetwork/java/javase/overview/javafx-samples-2158687.html

Resources