Return result from javafx platform runlater - javafx-2

I am working on JavaFX application, in my scenario is to show a password prompt created in JavaFX which takes password with two option OK and Cancel. I have returned the password entered by user.
My class of showing password dialog is -
public static String showPasswordDialog(String title, String message, Stage parentStage, double w, double h) {
try {
Stage stage = new Stage();
PasswordDialogController controller = (PasswordDialogController) Utility.replaceScene("Password.fxml", stage);
passwordDialogController.init(stage, message, "/images/password.png");
if (parentStage != null) {
stage.initOwner(parentStage);
}
stage.initModality(Modality.WINDOW_MODAL);
stage.initStyle(StageStyle.UTILITY);
stage.setResizable(false);
stage.setWidth(w);
stage.setHeight(h);
stage.showAndWait();
return controller.getPassword();
} catch (Exception ex) {
return null;
}
My code where to show password prompt is below, actually this prompt will be shown over other UI, so I need to inclose this inside Platform.runlater(), otherwise it throws Not on FX application thread. I need this password prompt to be shown until I get correct one. How can I get value of password if I inclosed showing password inside runlater.
Is there any other better way?
final String sPassword = null;
do {
Platform.runLater(new Runnable() {
#Override
public void run() {
sPassword = JavaFXDialog.showPasswordDialog(sTaskName + "Password", "Enter the password:", parentStage, 400.0, 160.0);
}
});
if (sPassword == null) {
System.out.println("Entering password cancelled.");
throw new Exception("Cancel");
}
} while (sPassword.equalsIgnoreCase(""));

I'd recommend wrapping the code within a FutureTask object. FutureTask is a construct useful (among other things) for executing a portion of code on one thread (usually a worker, in your case the event queue) and safely retrieving it on another. FutureTask#get will block until FutureTask#run has been invoked, therefore your password prompt could look like this:
final FutureTask query = new FutureTask(new Callable() {
#Override
public Object call() throws Exception {
return queryPassword();
}
});
Platform.runLater(query);
System.out.println(query.get());
As FutureTask implements Runnable, you can pass it directly to Platform#runLater(...). queryPassword() will be inokved on the event queue, and the subsequent call to get block until that method completes. Of course, you will want to invoke this code in a loop until the password actually matches.

Important
This code is for the specific case of when you have code which is not on the JavaFX application thread and you want to invoke code which is on the JavaFX application thread to display GUI to a user, then get a result from that GUI before continuing processing off the JavaFX application thread.
You must not be on the JavaFX application thread when you call CountdownLatch.await in the code snippet below. If you invoke CountDownLatch.await on the JavaFX Application thread, you will deadlock your application. Besides which, if you are already on the JavaFX application thread, you don't need to invoke Platform.runLater to execute something on the JavaFX application thread.
Most of the time you know if you are on the JavaFX application thread or not. If you are not sure, you can check your thread by calling Platform.isFxApplicationThread().
An alternate method using CountDownLatch. I like Sarcan's method better though ;-)
final CountDownLatch latch = new CountDownLatch(1);
final StringProperty passwordProperty = new SimpleStringProperty();
Platform.runLater(new Runnable() {
#Override public void run() {
passwordProperty.set(queryPassword());
latch.countDown();
}
});
latch.await();
System.out.println(passwordProperty.get());
Here is some executable sample code demonstrating use of a CountdownLatch to suspend execution of a non-JavaFX application thread until a JavaFX dialog has retrieved a result which can then be accessed by the non-JavaFX application thread.
The application prevents the JavaFX launcher thread for the application from continuing until the user has entered the correct password in a JavaFX dialog. The access granted stage is not shown until the correct password has been entered.
import javafx.application.*;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.TextAlignment;
import javafx.stage.*;
import java.util.concurrent.CountDownLatch;
public class PasswordPrompter extends Application {
final StringProperty passwordProperty = new SimpleStringProperty();
#Override public void init() {
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
#Override public void run() {
passwordProperty.set(new PasswordPrompt(null).getPassword());
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
Platform.exit();
}
System.out.println(passwordProperty.get());
}
#Override public void start(final Stage stage) {
Label welcomeMessage = new Label("Access Granted\nwith password\n" + passwordProperty.get());
welcomeMessage.setTextAlignment(TextAlignment.CENTER);
StackPane layout = new StackPane();
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20px;");
layout.getChildren().setAll(welcomeMessage);
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
class PasswordPrompt {
final Window owner;
PasswordPrompt(Window owner) {
this.owner = owner;
}
public String getPassword() {
final Stage dialog = new Stage();
dialog.setTitle("Pass is sesame");
dialog.initOwner(owner);
dialog.initStyle(StageStyle.UTILITY);
dialog.initModality(Modality.WINDOW_MODAL);
dialog.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override public void handle(WindowEvent windowEvent) {
Platform.exit();
}
});
final TextField textField = new TextField();
textField.setPromptText("Enter sesame");
final Button submitButton = new Button("Submit");
submitButton.setDefaultButton(true);
submitButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
if ("sesame".equals(textField.getText())) {
dialog.close();
}
}
});
final VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER_RIGHT);
layout.setStyle("-fx-background-color: azure; -fx-padding: 10;");
layout.getChildren().setAll(textField, submitButton);
dialog.setScene(new Scene(layout));
dialog.showAndWait();
return textField.getText();
}
}
The above program prints password to the screen and console purely for demonstration purposes, displaying or logging passwords is not something you would do in a real application.

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

Update label consecutively using scanner, inJavaFx

I'm currently trying to learn JavaFx, and I'm now stuck on a problem. By using a scanner I want to update my label on stage consecutively.
I have tried to use platform.runLater, but this only shows one update. It doesnt update the label every time I write something new in my console.
This is what I have been using:
Platform.runLater(new Runnable() {
#Override
public void run() {
label.setText(sc.nextLine());
}
});
The nextLine() method in Scanner is a blocking call: you should never block the FX Application Thread. You need to create a background thread to read from the scanner, and then update the label on the FX Application Thread:
import java.util.Scanner;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class UpdateLabelFromScanner extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
Thread scannerReadThread = new Thread(() -> {
try (Scanner scanner = new Scanner(System.in)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine() ;
Platform.runLater(() -> label.setText(line));
}
} catch (Exception exc) {
exc.printStackTrace();
}
});
scannerReadThread.setDaemon(true);
scannerReadThread.start();
StackPane root = new StackPane(label);
primaryStage.setScene(new Scene(root, 180, 120));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
There are a few ways you can do this. One way to is to read the text into an observable property, and when that property changes, you update your label. One of the objects you can use to do this is called SimpleStringProperty.
Declare it like this:
private StringProperty someText = new SimpleStringProperty();
In a constructor or some initialization function, add a new ChangeListener to the property:
someText.addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
label.setText(newValue);
});
});
When you read input from your scanner, change the value of your observable and the listener you added will be invoked, thus changing the text of your label:
someText.set(sc.nextLine());

How to play fragments of mp3 with JavaFX?

I want to develop a Java program playing an mp3-file in a specific manner. I marked a number of fragments in this file with startTime and endTime. The program should play the first fragment and then sleep for 5 seconds. Then play the second fragment and sleep again. And so on. I use JavaFX class MediaPlayer. The program prototype is as follows:
import javafx.application.Application;
import javafx.stage.Stage;
import java.io.FileNotFoundException;
import java.io.IOException;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.util.Duration;
import java.util.concurrent.TimeUnit;
public class JavaFXMediaPlayer02 extends Application {
#Override
public void start(Stage stage) throws FileNotFoundException,IOException,InterruptedException {
Media media = new Media("file:///D:/1016_00.mp3");
MediaPlayer mediaPlayer = new MediaPlayer(media);
//Set and play the first fragment of mp3-file
mediaPlayer.setStartTime(Duration.millis(1219.0));
mediaPlayer.setStopTime(Duration.millis(2728.0));
mediaPlayer.play();
System.out.println("1st fragment played!");
TimeUnit.SECONDS.sleep(5);
//Set and play the second fragment
mediaPlayer.setStartTime(Duration.millis(3947.0));
mediaPlayer.setStopTime(Duration.millis(6629.0));
mediaPlayer.play();
System.out.println("2nd fragment played!");
TimeUnit.SECONDS.sleep(5);
//Set and play the second fragment
mediaPlayer.setStartTime(Duration.millis(7453.0));
mediaPlayer.setStopTime(Duration.millis(10704.0));
mediaPlayer.play();
System.out.println("3rd fragment played!");
}
public static void main(String[] args) {
launch(args);
}
}
But I only hear the 3rd fragment. What's the matter? Why don't I hear the first and the second fragments? How to correct my program? Isn't JavaFX an appropriate tool for my task?
The problem here lies in the TimeUnit.SECONDS.sleep(5); invokation. This method sets the current thread into sleep. And in your case this thread is the JavaFX Application Thread. That causes the whole application to "freeze" (which would be more obviously if you added some GUI-Elements) and therefore the mediaPlayer.play(); commands are executed, but are instantly "freezed" because of the sleep function. After the `TimeUnit.SECONDS.sleep(5); calls, you set new start and end times for your MediaPlayer and execute play() again, so that the track starts at the new start time. Thats why only your last fragment is played.
Now to the solution:
You should never invoke Thread.sleep() or similar methods on the JavaFX App Thread. But in your case you have to wait a certain amount of time between playing the fragments. The first approach would be invoke Thread.sleep() or TimeUnit.SECONDS.sleep(5); on a new thread and call the Mediaplayer methods on the JFX App Thread. But that doesn't work properly because you haven't set up an "order" in which the threads are called. There are various ways to do this (via Semaphores, Locks and Conditions, JavaFX Concurrency and so on...)
I tried to solve your problem by doing some quick-and-dirty programming, but i came across a problem with mediaPlayer.setStopTime(Duration.millis());. It does not seem to work on my computers, so that the files are always played to the end. I added a stop button to simulate the automatic stopping.
The following class sets the new start and endpoints and plays the fragment. If the mediaplayer is stops, it calls the next fragment on the LittleMediaScheduler class.
public class LittleMediaHelper implements Runnable {
public double startTime;
public double endTime;
public MediaPlayer player;
public int id;
public LittleMediaScheduler scheduler;
public LittleMediaHelper(double startTime, double endTime,
MediaPlayer player, int id) {
this.startTime = startTime;
this.endTime = endTime;
this.player = player;
this.id = id;
}
public LittleMediaScheduler getScheduler() {
return scheduler;
}
public void setScheduler(LittleMediaScheduler scheduler) {
this.scheduler = scheduler;
}
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
player.setStartTime(Duration.millis(startTime));
player.setStopTime(Duration.millis(endTime));
System.out.println(player.getStartTime());
System.out.println(player.getStopTime());
player.play();
player.setOnStopped(new Runnable() {
#Override
public void run() {
int idtmp = id + 1;
System.out.println("NEXT " + idtmp);
scheduler.call(idtmp);
}
});
}
});
}
}
This class is responsibly for sleeping a certain amount on a new thread and after successfully sleeping invoking the next LittleMediaHelper class play functionality.
public class LittleMediaScheduler {
private ArrayList<LittleMediaHelper> hArrL;
private int SLEEPTIME = 2000;
public LittleMediaScheduler(LittleMediaHelper... helpers) {
this.hArrL = new ArrayList<>();
for (LittleMediaHelper h : helpers) {
h.setScheduler(this);
System.out.println(h.startTime);
this.hArrL.add(h);
}
System.out.println(hArrL.size());
}
public void init() {
Thread t = new Thread(this.hArrL.get(0));
t.start();
}
public void call(final int id) {
Thread t = new Thread(new Task<String>() {
#Override
protected String call() throws Exception {
Thread.sleep(SLEEPTIME);
return null;
}
#Override
protected void succeeded() {
super.succeeded();
System.out.println("Next playing...");
if (id > LittleMediaScheduler.this.hArrL.size() - 1) {
return;
}
LittleMediaHelper next = LittleMediaScheduler.this.hArrL
.get(id);
Thread nextT = new Thread(next);
nextT.start();
}
});
t.start();
}
}
The main class with a stop button. Without mediaPlayer.pause() the player somehow repeats one step twice although new start end endpoints are set. Don't know if this is a bug or not.
public class JavaFXMediaPlayer02 extends Application {
#Override
public void start(Stage stage) throws FileNotFoundException, IOException,
InterruptedException {
Media media = new Media("file:///C:/test.mp3");
final MediaPlayer mediaPlayer = new MediaPlayer(media);
LittleMediaHelper phase1 = new LittleMediaHelper(0, 1000, mediaPlayer,
0);
LittleMediaHelper phase2 = new LittleMediaHelper(50000, 55000,
mediaPlayer, 1);
LittleMediaHelper phase3 = new LittleMediaHelper(200000, 200500,
mediaPlayer, 2);
LittleMediaScheduler scheduler = new LittleMediaScheduler(phase1,
phase2, phase3);
scheduler.init();
Group g = new Group();
Button b = new Button("STOP");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
mediaPlayer.pause();
mediaPlayer.stop();
}
});
g.getChildren().add(b);
Scene sc = new Scene(g);
stage.setScene(sc);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Display a ProgressIndicator during an async loading of ListView items

I am trying to display a ProgressIndicator while performing an async background ListView item loading. The behaviour that I desire is:
Before start loading the ListView items, display a ProgressIndicator with a indeterminate progress;
Asynchronously start loading the ListView items;
After the ListView items loading was finished, hide the ProgressIndicator.
Here is a ssce of my unsuccessful attempt:
public class AsyncLoadingExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<String>();
final ObservableList<String> listItems = FXCollections.observableArrayList();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
final Button button = new Button("Click me to start loading");
primaryStage.setTitle("Async Loading Example");
listView.setPrefSize(200, 250);
listView.setItems(listItems);
loadingIndicator.setVisible(false);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// I have hoped it whould start displaying the loading indicator (actually, at the end of this
// method execution (EventHandler.handle(ActionEvent))
loadingIndicator.setVisible(true);
// asynchronously loads the list view items
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000l); // just emulates some loading time
// populates the list view with dummy items
while (listItems.size() < 10) listItems.add("Item " + listItems.size());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
loadingIndicator.setVisible(false); // stop displaying the loading indicator
}
}
});
}
});
VBox root = VBoxBuilder.create()
.children(
StackPaneBuilder.create().children(listView, loadingIndicator).build(),
button
)
.build();
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
}
In this example, the ListView items are loaded asynchronously. However, the ProgressIndicator do not show up. Still in this example, if I omit all the Platform.runLater(...) code, the ProgressIndicator shows up, but, of course, the ListView items are not loaded.
Thus, how can I achieve the desired behaviour?
Crferreira's self answer is perfectly fine.
This answer just demonstrates an alternate implementation that does not require the use of any Platform.runLater calls and instead uses a JavaFX Task (as well as Java 8 lambda syntax).
import javafx.application.Application;
import javafx.collections.*;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.*;
public class AsyncLoadingExample extends Application {
private void loadItems(final ObservableList<String> listItems, final ProgressIndicator loadingIndicator) {
if (loadingIndicator.isVisible()) {
return;
}
// clears the list items and start displaying the loading indicator at the Application Thread
listItems.clear();
loadingIndicator.setVisible(true);
// loads the items at another thread, asynchronously
Task listLoader = new Task<List<String>>() {
{
setOnSucceeded(workerStateEvent -> {
listItems.setAll(getValue());
loadingIndicator.setVisible(false); // stop displaying the loading indicator
});
setOnFailed(workerStateEvent -> getException().printStackTrace());
}
#Override
protected List<String> call() throws Exception {
final List<String> loadedItems = new LinkedList<>();
Thread.sleep(2000l); // just emulates some loading time
// populates the list view with dummy items
while (loadedItems.size() < 10) {
loadedItems.add("Item " + loadedItems.size());
}
return loadedItems;
}
};
Thread loadingThread = new Thread(listLoader, "list-loader");
loadingThread.setDaemon(true);
loadingThread.start();
}
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<>();
final ObservableList<String> listItems = FXCollections.observableArrayList();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
final Button button = new Button("Click me to start loading");
primaryStage.setTitle("Async Loading Example");
listView.setPrefSize(200, 250);
listView.setItems(listItems);
loadingIndicator.setVisible(false);
button.setOnAction(event -> loadItems(listItems, loadingIndicator));
VBox root = new VBox(
new StackPane(
listView,
loadingIndicator
),
button
);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
The problem is that, at the presented example, I am misusing the Platform.runLater(...) method, and consequently, the JavaFX Application Thread.
As mentioned at the Platform.runLater() method documentation, this method
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future.
And, the JavaFX Application Thread is the thread from which the JavaFX scene graph can be accessed and modified by the developer code, visually reflecting the performed modifications.
Thus, when I start loading the ListView items from this thread, the UI becomes unresponsive (this is also stated here) until the loading is finished.
To solve the problem, the ListView items must be loaded at another thread and only the ListView update must be performed at Application Thread.
The above correction is presented in the following:
public class AsyncLoadingExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<String>();
final ObservableList<String> listItems = FXCollections.observableArrayList();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
final Button button = new Button("Click me to start loading");
primaryStage.setTitle("Async Loading Example");
listView.setPrefSize(200, 250);
listView.setItems(listItems);
loadingIndicator.setVisible(false);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final List<String> loadedItems = new LinkedList<String>();
// clears the list items and start displaying the loading indicator at the Application Thread
listItems.clear();
loadingIndicator.setVisible(true);
// loads the items at another thread, asynchronously
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000l); // just emulates some loading time
// populates the list view with dummy items
while (loadedItems.size() < 10) loadedItems.add("Item " + loadedItems.size());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// just updates the list view items at the
// Application Thread
Platform.runLater(new Runnable() {
#Override
public void run() {
listItems.addAll(loadedItems);
loadingIndicator.setVisible(false); // stop displaying the loading indicator
}
});
}
}
}).start();
}
});
VBox root = VBoxBuilder.create()
.children(
StackPaneBuilder.create().children(listView, loadingIndicator).build(),
button
)
.build();
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
}

JavaFX, MediaPlayer - volume trouble! Why the volume of mediaPlayer do not changing bit by bit?

I setVolume to 0.0, and then change the volume bit by bit in the while loop. Yet, the volume jumps from 0.0 to 1.0 ? How can I change the volume smoothly?
I tried
public class EngineSound extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
mp3File = new File(metronom);
media = new Media(mp3File.toURI().toURL().toString());
mediaPlayer = new MediaPlayer(media);
mediaView = new MediaView(mediaPlayer);
mediaPlayer.play();
mediaPlayer.setVolume(0.0);
slider = new Slider();
slider.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
mediaPlayer.setVolume(slider.getValue());
}
});
}
});
double count = 1;
while (count != 101) {
for (int i = 0; i < 100000000; i++) {
}
slider.setValue(count / 100);
count++;
System.out.println(mediaPlayer.getVolume());
}
}
}
There are a few things wrong with your code.
JavaFX code should be executed on the JavaFX Application Thread, not the Swing event dispatch thread.
Instead of using EventQueue.invokeLater to execute on the Swing thread, use Platform.runLater to execute on the JavaFX thread.
There is no reason to use the Swing event dispatch thread at all.
Your program only makes use of JavaFX controls, so don't run anything on the Swing thread.
You usually don't need any thread switching calls in a ChangeListener.
Even though using EventQueue.invokeLater is wrong, in this case you don't even need to Platform.runLater either as only the JavaFX application thread should be modifying the Slider value anyway. There is a rule you can see in the JavaFX Node documentation:
An application must attach nodes to a Scene, and modify nodes that are already attached to a Scene, on the JavaFX Application Thread.
Don't do busy waiting on the JavaFX Application Thread.
The loop where you count to one hundred million will just block the application thread resulting in a frozen UI as control will never be returned to the framework to update the UI.
Don't change a slider value in a loop.
Once you set a value on a UI control, you must return control back to the JavaFX framework to allow the value change to be reflected in the control and to the user.
Try the following code which addresses all of the above issues through the use of Timeline and Binding.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.*;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.media.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class EngineSound extends Application {
private static final String MEDIA_URL =
"http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";
private static final Duration FADE_DURATION = Duration.seconds(2.0);
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) throws Exception {
final MediaPlayer mediaPlayer = new MediaPlayer(
new Media(
MEDIA_URL
)
);
final MediaView mediaView = new MediaView(mediaPlayer);
HBox layout = new HBox(5);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().addAll(
createVolumeControls(mediaPlayer),
mediaView
);
stage.setScene(new Scene(layout, 650, 230));
stage.show();
mediaPlayer.play();
}
public Region createVolumeControls(final MediaPlayer mediaPlayer) {
final Slider volumeSlider = new Slider(0, 1, 0);
volumeSlider.setOrientation(Orientation.VERTICAL);
mediaPlayer.volumeProperty().bindBidirectional(volumeSlider.valueProperty());
final Timeline fadeInTimeline = new Timeline(
new KeyFrame(
FADE_DURATION,
new KeyValue(mediaPlayer.volumeProperty(), 1.0)
)
);
final Timeline fadeOutTimeline = new Timeline(
new KeyFrame(
FADE_DURATION,
new KeyValue(mediaPlayer.volumeProperty(), 0.0)
)
);
Button fadeIn = new Button("Fade In");
fadeIn.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
fadeInTimeline.play();
}
});
fadeIn.setMaxWidth(Double.MAX_VALUE);
Button fadeOut = new Button("Fade Out");
fadeOut.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
fadeOutTimeline.play();
}
});
fadeOut.setMaxWidth(Double.MAX_VALUE);
VBox controls = new VBox(5);
controls.getChildren().setAll(
volumeSlider,
fadeIn,
fadeOut
);
controls.setAlignment(Pos.CENTER);
VBox.setVgrow(volumeSlider, Priority.ALWAYS);
controls.disableProperty().bind(
Bindings.or(
Bindings.equal(Timeline.Status.RUNNING, fadeInTimeline.statusProperty()),
Bindings.equal(Timeline.Status.RUNNING, fadeOutTimeline.statusProperty())
)
);
return controls;
}
}
The code controls a Video, but making it do audio only is just a matter of setting the Media URL to an audio only format such as mp3 or aac.

Resources