I try to high frequently update the cells in a JFX TableView (proof of concept application). I load a TableView via FXML and start an ExecutorService to change the value of a cell.
When I start the application I notice, that the update works for the first 3-4 million elements and then it stucks. If I slow down the updates (see MAGIC#1) it works (10ms is still too fast, but 100ms delay works). So I thought it might be a threading issue.
But then I found out that if I add an empty ChangeListener (see MAGIC#2) to the property it works fine. Even without the need of MAGIC#1.
Am I doing something wrong? Do I have to update the cells in a different way?
Thanks in advance for your help!!
The elements in the TableView:
public class Element {
public static final AtomicInteger x = new AtomicInteger(0);
private final StringProperty nameProperty = new SimpleStringProperty("INIT");
public Element() {
// MAGIC#2
// this.nameProperty.addListener((observable, oldValue, newValue) -> {});
}
public void tick() {
this.setName(String.valueOf(x.incrementAndGet()));
}
public String getName() ...
public void setName(String name)...
public StringProperty nameProperty() ...
}
The controller for FXML:
public class TablePerformanceController implements Initializable {
private final ObservableList<Element> data = FXCollections.observableArrayList();
public Runnable changeValues = () -> {
while (true) {
if (Thread.currentThread().isInterrupted()) break;
data.get(0).tick();
// MAGIC#1
// try { Thread.sleep(100); } catch (Exception e) {}
}
};
private ExecutorService executor = null;
#FXML
public TableView<Element> table;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.table.setEditable(true);
TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
this.table.getColumns().addAll(nameCol);
this.data.add(new Element());
this.table.setItems(this.data);
this.executor = Executors.newSingleThreadExecutor();
this.executor.submit(this.changeValues);
}
}
You are violating the single threaded rule for JavaFX: updates to the UI must only be made from the FX Application Thread. Your tick() method updates the nameProperty(), and since the table cell is observing the nameProperty(), tick() results in an update to the UI. Since you're calling tick() from a background thread, this update to the UI happens on the background thread. The resulting behavior is essentially undefined.
Additionally, your code ends up with too many requests to update the UI. So even if you fix the threading issues, you need to somehow throttle the requests so that you don't flood the FX Application Thread with too many requests to update, which will make it unresponsive.
The technique to do this is addressed in Throttling javafx gui updates. I'll repeat it here in the context of a table model class:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Element {
// Note that in the example we only actually reference this from a single background thread,
// in which case we could just make this a regular int. However, for general use this might
// need to be threadsafe.
private final AtomicInteger x = new AtomicInteger(0);
private final StringProperty nameProperty = new SimpleStringProperty("INIT");
private final AtomicReference<String> name = new AtomicReference<>();
/** This method is safe to call from any thread. */
public void tick() {
if (name.getAndSet(Integer.toString(x.incrementAndGet())) == null) {
Platform.runLater(() -> nameProperty.set(name.getAndSet(null)));
}
}
public String getName() {
return nameProperty().get();
}
public void setName(String name) {
nameProperty().set(name);
}
public StringProperty nameProperty() {
return nameProperty;
}
}
The basic idea here is to use an AtomicReference<String to "shadow" the real property. Atomically update it and check if it's null, and if so schedule an update to the real property on the FX Application Thread. In the update, atomically retrieve the "shadow" value and reset it to null, and set the real property to the retrieved value. This ensures that new requests to update on the FX Application thread are only made as often as the FX Application Thread consumes them, ensuring that the FX Application Thread is not flooded. Of course, if there is a delay between scheduling the update on the FX Application Thread, and the update actually occurring, when the update does happen it will still retrieve the latest value to which the "shadow" value was set.
Here's a standalone test, which is basically equivalent to the controller code you showed:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class FastTableUpdate extends Application {
private final ObservableList<Element> data = FXCollections.observableArrayList();
public final Runnable changeValues = () -> {
while (true) {
if (Thread.currentThread().isInterrupted()) break;
data.get(0).tick();
}
};
private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
#Override
public void start(Stage primaryStage) {
TableView<Element> table = new TableView<>();
table.setEditable(true);
TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setPrefWidth(200);
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
table.getColumns().add(nameCol);
this.data.add(new Element());
table.setItems(this.data);
this.executor.submit(this.changeValues);
Scene scene = new Scene(table, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
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());
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);
}
}
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.
I've spent like the last 24 hours trying to learn JavaFX. I'm trying to build a GUI that will display values from a data source (for example a database). My question is what the preferred way is to do this. So far I've come up with this code to build a simple GUI and get some data from a data source:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class AvcHmi extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Text t = new Text(10, 50, "Replace/update this text periodically with data");
Group root = new Group();
root.getChildren().add(t);
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
new Thread() {
private DataSource dataSource = new DataSource();
{ setDaemon(true); }
#Override
public void run() {
try {
for(;;) {
Thread.sleep(100);
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println(dataSource.getDataMap().get("key1"));
}});
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
Datasource:
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class DataSource {
Map<String,String> dataMap = new HashMap<>();
public DataSource() {
dataMap.put("key1", "value1");
dataMap.put("key2", "value2");
dataMap.put("key3", "value3");
}
public Map<String, String> getDataMap() {
Random generator = new Random();
int randInt = generator.nextInt();
dataMap.put("key1", "value"+randInt);
return dataMap;
}
}
100 ms is OK interval to update this GUI as far as I'm concerned. But is this a viable solution?
The next step is to replace the text with a value from the data source. Been looking at Collections and ObservableMap and wondering if it's a preferred way of doing the actual GUI updates? I'm aving some problems with inner classes and final variables but might reason that out after some sleep.
Also, the target machine is not that powerful (somewhere between 350-512 mb RAM). Could this be an issue? My simple tests so far seems to run fine.
Thank you for any feedback on this.
This Oracle example shows how to achieve concurrency loading in data table, with source code; it might help you
You could also look at reading about javafx.concurrent.Task<V> API.
The code on the Oracle example is as follows:
public class UpdateCustomerTask extends Task<Customer> {
private final Customer customer;
public UpdateCustomerTask(Customer customer) {
this.customer = customer;
}
#Override protected Customer call() throws Exception {
// pseudo-code:
// query the database
// read the values
// Now update the customer
Platform.runLater(new Runnable() {
#Override public void run() {
customer.setF setFirstName(rs.getString("FirstName"));
// etc
}
});
return customer;
}
}
i have a JSF web application deployed under glassfish in which i have two buttons.The first start a infinite thread and the second stop it.My problem is that i can not stop a running thread.I have searched for a solution on the net but in vain.it works in case i have a J2SE application but not with a J2EE application here is my code
package com.example.beans;
import org.apache.commons.lang.RandomStringUtils;
public class MyBusinessClass {
public static void myBusinessMethod() {
/* this method takes a lot of time */
int i = 1;
while (i == 1) {
String random = RandomStringUtils.random(3);
System.out.println(random);
}
}
}
package com.example.beans;
import java.util.Random;
import java.util.TimerTask;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.log4j.Logger;
import com.example.core.RandomUtils;
public class MySimpleRunnableTask implements Runnable {
private Logger logger = Logger.getLogger(MySimpleRunnableTask.class);
#Override
public void run() {
MyBusinessClass.myBusinessMethod();
}
}
#ManagedBean(name = "MainView")
#SessionScoped
public class MainView {
private static Thread myThread;
#SuppressWarnings({ "unchecked", "rawtypes", "deprecation" })
public String startSimpleThread() throws SecurityException,
NoSuchMethodException,
InterruptedException {
MySimpleRunnableTask mySimpleRunnableTask = new MySimpleRunnableTask();
myThread = new Thread(mySimpleRunnableTask);
myThread.start();
return null;
}
#SuppressWarnings({ "unchecked", "rawtypes", "deprecation" })
public String stopSimpleThread() throws SecurityException,
NoSuchMethodException,
InterruptedException {
myThread.interrupt();
return null;
}
}
I have changed my code so you can understand really what's my problem
interrupt only sets the interrupt status in the thread to true. The thread needs to regularly pool the interrupt status flag to stop running:
public void run() {
/* you will have to touch the code here */
int i = 1;
while (i == 1) {
String random = RandomStringUtils.random(3);
logger.info(random);
if (Thread.currentThread().isInterrupted()) {
// the thread has been interrupted. Stop running.
return;
}
}
}
This is the only way to properly stop a thread : ask him to stop. Without cooperation from the running thread, there is no clean way.