I have a simple undecorated Alert that I am trying to display while a long-running task is being completed in the background.
However, when the task is finished, I want to close the alert. I can not, however, get the alert to close by calling either close() or hide().
This MCVE does not include the background Task, but even this refuses to close the alert:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class AlertClosing extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Alert simpleAlert = new Alert(Alert.AlertType.NONE);
simpleAlert.setContentText("Testing");
simpleAlert.initStyle(StageStyle.UNDECORATED);
simpleAlert.show();
// None of these seem to have any effect.
simpleAlert.close();
simpleAlert.hide();
Platform.runLater(simpleAlert::close);
}
}
I have found a few other answers here that deal with Alert and Dialog panes that have a cancel button, but my Alert has no buttons at all; it's simply meant to display a message while the background task is running.
Per the JavaDocs on Dialog closing rules:
JavaFX dialogs can only be closed 'abnormally' (as defined above) in
two situations:
When the dialog only has one button, or
When the dialog has multiple buttons, as long as one of them meets one of the following requirements:
The button has a ButtonType whose ButtonBar.ButtonData is of type ButtonBar.ButtonData.CANCEL_CLOSE.
The button has a ButtonType whose ButtonBar.ButtonData returns true when ButtonBar.ButtonData.isCancelButton() is called.
In all other situations, the dialog will refuse to respond to all
close requests, remaining open until the user clicks on one of the
available buttons in the DialogPane area of the dialog.
So this too suggests that a simple close() request should be honored since there are no qualifying buttons in an Alert(AlertStyle.NONE), right?
No according to the javadoc not honoring the close request: There's neither one nor multiple buttons.
You can assign one ButtonType as result make the Alert close properly.
Alert simpleAlert = new Alert(Alert.AlertType.NONE);
simpleAlert.setContentText("Testing");
simpleAlert.initStyle(StageStyle.UNDECORATED);
simpleAlert.show();
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
Platform.runLater(() -> {
simpleAlert.setResult(ButtonType.CANCEL);
simpleAlert.close();
});
}).start();
Related
I try to run in JavaFX application background thread periodically, which modifies some GUI property.
I think I know how to use Task and Service classes from javafx.concurrent and can't figure it out how to run such periodic task without using Thread#sleep() method. It would be nice if I can use some Executor from Executors fabricate methods (Executors.newSingleThreadScheduledExecutor())
I tried to run Runnable every 5 sec, which restarts javafx.concurrent.Service but it hangs immediately as service.restart or even service.getState() is called.
So finally I use Executors.newSingleThreadScheduledExecutor(), which fires my Runnable every 5 sec and that Runnable runs another Runnable using:
Platform.runLater(new Runnable() {
//here i can modify GUI properties
}
It looks very nasty :( Is there a better way to do this using Task or Service classes?
You can use Timeline for that task:
Timeline fiveSecondsWonder = new Timeline(
new KeyFrame(Duration.seconds(5),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("this is called every 5 seconds on UI thread");
}
}));
fiveSecondsWonder.setCycleCount(Timeline.INDEFINITE);
fiveSecondsWonder.play();
for the background processes (which don't do anything to the UI) you can use old good java.util.Timer:
new Timer().schedule(
new TimerTask() {
#Override
public void run() {
System.out.println("ping");
}
}, 0, 5000);
Preface: This question is often the duplicate target for questions which ask how to perform periodic actions in JavaFX, whether the action should be done in the background or not. While there are already great answers to this question, this answer attempts to consolidate all the given information (and more) into a single answer and explain/show the differences between each approach.
This answer focuses on the APIs available in JavaSE and JavaFX and not third-party libraries such as ReactFX (showcased in Tomas Mikula's answer).
Background Information: JavaFX & Threads
Like most mainstream GUI frameworks, JavaFX is single-threaded. This means there's a single thread dedicated to reading and writing the state of the UI and processing user-generated events (e.g. mouse events, key events, etc.). In JavaFX this thread is called the "JavaFX Application Thread", sometimes shortened to just "FX thread", but other frameworks may call it something else. Some other names include "UI thread", "event-dispatch thread", and "main thread".
It is absolutely paramount that anything connected to the GUI showing on screen is only ever accessed or manipulated on the JavaFX Application Thread. The JavaFX framework is not thread-safe and using a different thread to improperly read or write the state of the UI can lead to undefined behavior. Even if you don't see any externally-visible problems, access to state shared between threads without the necessary synchronization is broken code.
Many GUI objects, however, can be manipulated on any thread as long as they aren't "live". From the documentation of javafx.scene.Node:
Node objects may be constructed and modified on any thread as long they are not yet attached to a Scene in a Window that is showing [emphasis added]. An application must attach nodes to such a Scene or modify them on the JavaFX Application Thread.
But other GUI objects, such as Window and even some subclasses of Node (e.g. WebView), are more strict. For instance, from the documentation of javafx.stage.Window:
Window objects must be constructed and modified on the JavaFX Application Thread.
If you're unsure about the threading rules of a GUI object, its documentation should provide the needed information.
Since JavaFX is single-threaded you also have to make sure never to block or otherwise monopolize the FX thread. If the thread is not free to do its job then the UI is never redrawn and new user-generated events can't be processed. Not following this rule can lead to the infamous unresponsive/frozen UI and your users are not happy.
It's virtually always wrong to sleep the JavaFX Application Thread.
Periodic Tasks
There are two different kinds of periodic tasks, at least for the purposes of this answer:
Periodic foreground "tasks".
This could include things such as a "blinking" node or periodically switching between images.
Periodic background tasks.
An example might be periodically checking a remote server for updates and, if there are any, downloading the new information and displaying it to the user.
Periodic Foreground Tasks
If your periodic task is short and simple then using a background thread is overkill and just adds unnecessary complexity. The more appropriate solution is to use the javafx.animation API. Animations are asynchronous but stay entirely within the JavaFX Application Thread. In other words, animations provide a way to "loop" on the FX thread, with delays between each iteration, without actually using loops.
There are three classes uniquely suited to periodic foreground tasks.
Timeline
A Timeline is made up of one or more KeyFrames. Each KeyFrame has a specified time of when it should complete. Each one can also have an "on finished" handler which is invoked after the specified amount of time has elapsed. This means you can create a Timeline with a single KeyFrame that periodically executes an action, looping as many times as you want (including forever).
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(100, 100);
// toggle the visibility of 'rect' every 500ms
Timeline timeline =
new Timeline(new KeyFrame(Duration.millis(500), e -> rect.setVisible(!rect.isVisible())));
timeline.setCycleCount(Animation.INDEFINITE); // loop forever
timeline.play();
primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));
primaryStage.show();
}
}
Since a Timeline can have more than one KeyFrame it's possible to have actions being executed at different intervals. Just keep in mind that the times of each KeyFrame do not stack. If you have one KeyFrame with a time of two seconds followed by another KeyFrame with a time of two seconds, both KeyFrames will finish two seconds after the animation is started. To have the second KeyFrame finish two seconds after the first one, its time needs to be four seconds.
PauseTransition
Unlike the other animation classes, a PauseTransition is not used to actually animate anything. It's main purpose is to be used as a child of SequentialTransition to put a pause between two other animations. However, like all subclassses of Animation it can have an "on finished" handler that's executed after it completes, allowing it to be used for periodic tasks.
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(100, 100);
// toggle the visibility of 'rect' every 500ms
PauseTransition pause = new PauseTransition(Duration.millis(500));
pause.setOnFinished(
e -> {
rect.setVisible(!rect.isVisible());
pause.playFromStart(); // loop again
});
pause.play();
primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));
primaryStage.show();
}
}
Notice the on-finished handler invokes playFromStart(). This is necessary to "loop" the animation again. The cycleCount property can't be used since the on-finished handler is not invoked at the end of each cycle, it's only invoked at the end of the last cycle. The same thing is true of Timeline; the reason it works with Timeline above is because the on-finished handler isn't registered with the Timeline but with the KeyFrame.
Since the cycleCount property can't be used for PauseTransition for multiple cycles it makes it more difficult to loop only a certain number of times (rather than forever). You have to keep track of the state yourself and only invoke playFromStart() when appropriate. Keep in mind that local variables declared outside a lambda expression or anonymous class but used inside said lambda expression or anonymous class must be final or effectively final.
AnimationTimer
The AnimationTimer class is the lowest level of JavaFX's animation API. It's not a subclass of Animation and thus doesn't have any of the properties that were used above. Instead, it has an abstract method that, when the timer is started, is invoked once per frame with the timestamp (in nanoseconds) of the current frame: #handle(long). In order to execute something periodically with AnimationTimer (other than once per frame) will require manually calculating the time differences between invocations of handle using the method's argument.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(100, 100);
// toggle the visibility of 'rect' every 500ms
AnimationTimer timer =
new AnimationTimer() {
private long lastToggle;
#Override
public void handle(long now) {
if (lastToggle == 0L) {
lastToggle = now;
} else {
long diff = now - lastToggle;
if (diff >= 500_000_000L) { // 500,000,000ns == 500ms
rect.setVisible(!rect.isVisible());
lastToggle = now;
}
}
}
};
timer.start();
primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));
primaryStage.show();
}
}
For most use cases similar to the above, using either Timeline or PauseTransition would be the better option.
Periodic Background Tasks
If your periodic task is time-consuming (e.g. expensive computations) or blocking (e.g. I/O) then a background thread needs to be used. JavaFX comes with some concurrency utilities built-in to aid with communication between background threads and the FX thread. These utilities are described in:
The Concurrency in JavaFX tutorial, and
The documentation of the classes in the javafx.concurrent package.
For periodic background tasks that need to communicate with the FX thread, the class to use is javafx.concurrent.ScheduledService. That class will execute its task periodically, restarting after successful execution, based on a specified period. If configured to do so it will even retry a configurable amount of times after failed executions.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
// maintain a strong reference to the service
private UpdateCheckService service;
#Override
public void start(Stage primaryStage) {
service = new UpdateCheckService();
service.setPeriod(Duration.seconds(5));
Label resultLabel = new Label();
service.setOnRunning(e -> resultLabel.setText(null));
service.setOnSucceeded(
e -> {
if (service.getValue()) {
resultLabel.setText("UPDATES AVAILABLE");
} else {
resultLabel.setText("UP-TO-DATE");
}
});
Label msgLabel = new Label();
msgLabel.textProperty().bind(service.messageProperty());
ProgressBar progBar = new ProgressBar();
progBar.setMaxWidth(Double.MAX_VALUE);
progBar.progressProperty().bind(service.progressProperty());
progBar.visibleProperty().bind(service.stateProperty().isEqualTo(State.RUNNING));
VBox box = new VBox(3, msgLabel, progBar);
box.setMaxHeight(Region.USE_PREF_SIZE);
box.setPadding(new Insets(3));
StackPane root = new StackPane(resultLabel, box);
StackPane.setAlignment(box, Pos.BOTTOM_LEFT);
primaryStage.setScene(new Scene(root, 400, 200));
primaryStage.show();
service.start();
}
private static class UpdateCheckService extends ScheduledService<Boolean> {
#Override
protected Task<Boolean> createTask() {
return new Task<>() {
#Override
protected Boolean call() throws Exception {
updateMessage("Checking for updates...");
for (int i = 0; i < 1000; i++) {
updateProgress(i + 1, 1000);
Thread.sleep(1L); // fake time-consuming work
}
return Math.random() < 0.5; // 50-50 chance updates are "available"
}
};
}
}
}
Here's a note from the documentation of ScheduledService:
Timing for this class is not absolutely reliable. A very busy event thread might introduce some timing lag into the beginning of the execution of the background Task, so very small values for the period or delay are likely to be inaccurate. A delay or period in the hundreds of milliseconds or larger should be fairly reliable.
And another:
The ScheduledService introduces a new property called lastValue. The lastValue is the value that was last successfully computed. Because a Service clears its value property on each run, and because the ScheduledService will reschedule a run immediately after completion (unless it enters the cancelled or failed states), the value property is not overly useful on a ScheduledService. In most cases you will want to instead use the value returned by lastValue.
The last note means binding to the value property of a ScheduledService is in all likelihood useless. The example above works despite querying the value property because the property is queried in the onSucceeded handler, before the service is rescheduled.
No Interaction with UI
If the periodic background task does not need to interact with the UI then you can use the standard APIs of Java instead. More specifically, either:
The java.util.Timer class (not javax.swing.Timer),
Or the more modern java.util.concurrent.ScheduledExecutorService interface.
Note that ScheduledExecutorService supports thread pools, unlike Timer which only supports a single thread.
ScheduledService is not an Option
If for whatever reason you can't use ScheduledService, but need to need to interact with the UI anyway, then you need to make sure the code interacting with the UI, and only that code, is executed on the FX thread. This can be accomplished by using Platform#runLater(Runnable).
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater. If this method is called after the JavaFX runtime has been shutdown, the call will be ignored: the Runnable will not be executed and no exception will be thrown.
NOTE: applications should avoid flooding JavaFX with too many pending Runnables. Otherwise, the application may become unresponsive. Applications are encouraged to batch up multiple operations into fewer runLater calls. Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.
[...]
Heed the note from the above documentation. The javafx.concurent.Task class avoids this by coalescing updates to its message, progress, and value properties. This is currently implemented by using an AtomicReference and strategic get-and-set operations. If interested, you can take a look at the implementation (JavaFX is open source).
I would Prefer the PauseTransition:
PauseTransition wait = new PauseTransition(Duration.seconds(5));
wait.setOnFinished((e) -> {
/*YOUR METHOD*/
wait.playFromStart();
});
wait.play();
Here is a solution using Java 8 and ReactFX. Say that you want to periodically recompute the value of Label.textProperty().
Label label = ...;
EventStreams.ticks(Duration.ofSeconds(5)) // emits periodic ticks
.supplyCompletionStage(() -> getStatusAsync()) // starts a background task on each tick
.await() // emits task results, when ready
.subscribe(label::setText); // performs label.setText() for each result
CompletionStage<String> getStatusAsync() {
return CompletableFuture.supplyAsync(() -> getStatusFromNetwork());
}
String getStatusFromNetwork() {
// ...
}
Compared to Sergey's solution, you don't dedicate the whole thread to getting status from the network, but instead use the shared thread pool for that.
You can use ScheduledService too. I am using this alternative after noticing that during the use of Timeline and PauseTransition occurred some UI freezes in my application, especially when the user interacts with the elements of a MenuBar (on JavaFX 12). Using the ScheduledService these problems no longer occurred.
class UpdateLabel extends ScheduledService<Void> {
private Label label;
public UpdateLabel(Label label){
this.label = label;
}
#Override
protected Task<Void> createTask(){
return new Task<Void>(){
#Override
protected Void call(){
Platform.runLater(() -> {
/* Modify you GUI properties... */
label.setText(new Random().toString());
});
return null;
}
}
}
}
And then, use it:
class WindowController implements Initializable {
private #FXML Label randomNumber;
#Override
public void initialize(URL u, ResourceBundle res){
var service = new UpdateLabel(randomNumber);
service.setPeriod(Duration.seconds(2)); // The interval between executions.
service.play()
}
}
Was not easy find the way to programing this kind of behavior may be because my process reads I/O, works in milliseconds and I felt was often interrupted by GUI thread, but I made it by creating a BackgroundProcess class & with the help of ScheduledExecutorService.
In the controlle side, I use PauseTransition to read volatile (no contention) info only.
Sample code :
public class HelloApplication extends Application {
final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
final BackgroundProcess backgroundProcess = new BackgroundProcess();
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 720, 610);
HelloController helloController = fxmlLoader.getController();
helloController.setBackgroundProcess(backgroundProcess);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
scheduledExecutor.scheduleWithFixedDelay(
backgroundProcess,
0, 111, TimeUnit.MILLISECONDS);
}
#Override
public void stop() throws Exception {
super.stop();
scheduledExecutor.shutdown();
}
...
}
public class BackgroundProcess implements Runnable{
volatile String status = "";
#Override
public void run() {
status = newStatus();
}
...
}
public class HelloController {
#FXML
protected void initialize() {
PauseTransition refresh = new PauseTransition(Duration.millis(111));
wait.setOnFinished((e) -> {
statusLabel.setText(backgroundProcess.status);
refresh.playFromStart();
});
refresh.play();
}
...
}
To read synchronized (contention) info I use ScheduledService to prepare the info and prevent interruptions in the JavaFX thread.
This is a more complex sample code:
public class HelloApplication extends Application {
final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
final BackgroundProcess backgroundProcess = new BackgroundProcess();
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 720, 610);
HelloController helloController = fxmlLoader.getController();
helloController.setBackgroundProcess(backgroundProcess);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
scheduledExecutor.scheduleWithFixedDelay(
backgroundProcess,
0, 111, TimeUnit.MILLISECONDS);
}
#Override
public void stop() throws Exception {
super.stop();
scheduledExecutor.shutdown();
}
...
}
public class BackgroundProcess implements Runnable{
volatile String status = "";
LinkedTransferQueue<String> queue = new LinkedTransferQueue();
#Override
public void run() {
status = newStatus();
addToQueue();
}
...
}
public class HelloController {
static class SynchronizedInformation {
ArrayList<String> list;
}
private SynchronizedInformation prepareSynchronizedInformation() {
if (backgroundProcess.queue.isEmpty()) {
return null;
}
final SynchronizedInformation r = new SynchronizedInformation();
int size = backgroundProcess.queue.size();
r.list = new ArrayList<>(size);
String line;
while (r.list.size() < size && null != (line = backgroundProcess.queue.poll())) {
r.list.add(line);
}
return r;
}
private void refreshSynchronizedInformation(SynchronizedInformation synchronizedInformation) {
if (null != synchronizedInformation) {
synchronizedInformation.list.forEach(textArea::appendText);
}
statusLabel.setText(backgroundProcess.incoming);
}
#FXML
protected void initialize() {
ScheduledService<SynchronizedInformation> svc = new ScheduledService<>() {
#Override
protected Task<SynchronizedInformation> createTask() {
return new Task<SynchronizedInformation>() {
#Override
protected SynchronizedInformation call() throws Exception {
return prepareSynchronizedInformation();
}
};
}
};
svc.setDelay(Duration.millis(111));
svc.setOnSucceeded(e -> refreshSynchronizedInformation(svc.getValue()));
svc.start();
...
}
I have a Textarea component that is used to display messages going between 2 applications (HL7 messages to be exact). Each time a message is successfully processed, the message is logged as well as the acknowledgement sent back from the receiving system. These messages can be sent by the thousands and i feel like there will inevitably be a point where problems will start happening when this components "overflows". I would like to implement a rollover strategy, kinda like log4j where you can tell it to only keep say 10 files of 1MB. I would like to have a value the user can set and my component (perhaps an extension of the Textarea component) would automatically only keep that number of rows and purge the first ones as new ones are added. I am relatively new to JavaFX (coming from Swing), i have looked at the options but cannot quite figure out how one would do this.
Thanks
As I mention in the comments section of the question, I recommend you use a ListView instead of a TextArea. This gives you a few benefits:
ListView is a "virtual" control—it only renders enough cells to fill the visible space and the cells are reused while scrolling. This allows one to have thousands of items in the ListView without rendering performance suffering.
The model of a ListView is an observable list, which is a much better way to represent separate messages than having one giant String in a TextArea. When adding an element to the list causes it to grow beyond some arbitrary capacity you can simply remove an item(s) from the start of said list (or end, if inserting items at the top rather than the bottom).
A ListView provides much greater flexibility when it comes to displaying your message. This is accomplished with a custom cell factory. For instance, you could have certain ranges of the message be different colors by using a TextFlow as the graphic of the ListCell. Make sure you read the documentation of Cell.updateItem(Object,boolean), however, as you have to override that method correctly; failing to do so can lead to artifacts due to the fact cells are reused.
A simple example:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private static void generateMessages(Consumer<String> onNewMessage) {
AtomicInteger counter = new AtomicInteger();
PauseTransition pt = new PauseTransition(Duration.seconds(1));
pt.setOnFinished(e -> {
onNewMessage.accept(String.format("Message #%,d", counter.incrementAndGet()));
pt.playFromStart();
});
pt.playFromStart();
}
#Override
public void start(Stage primaryStage) {
ListView<String> listView = new ListView<>();
primaryStage.setScene(new Scene(listView, 500, 300));
primaryStage.show();
generateMessages(message -> {
listView.getItems().add(message);
if (listView.getItems().size() > 10) {
listView.getItems().remove(0);
}
});
}
}
I went a step above the accepted answer (without which i would still be trying to use a TextArea!) and built this custom component (yes there is an empty catch block but i have a clear log button that threw an exception when the log was empty, deal with it :) )
public class HL7ListView extends ListView<String>
{
private int serviceLogMaxEntries;
public HL7ListView()
{
super();
getItems().addListener((ListChangeListener<String>) listener ->
{
if (getItems().size() > serviceLogMaxEntries)
{
Platform.runLater(() ->
{
try
{
getItems().remove(0);
}
catch (Exception ex)
{}
});
}
});
}
public void setServiceLogMaxEntries(int serviceLogMaxEntries)
{
this.serviceLogMaxEntries = serviceLogMaxEntries;
}
}
I'm trying to populate my TreeView and update the text of status label before and after the populating.
my code looks something like this:
public void populateTreeView() {
// start message
this.label.setText("Process started...");
// populate the TreeView, this takes several minutes
// finish message
this.label.setText("Done!");
}
It's not working as I expected. I tried put each of the the 3 main sections of this method in Platform.runLater() and that didn't work, too. The app just freezes for some seconds and then I see the populated TreeView and the "Done" text.
How can I get this behavior?
Thanks in advance
You will need to execute the loading method in a background Thread. Basically, the interface that you create and all of its events are executed on the JavaFX Application Thread (JFXAT).
These events are generally executed one at a time, so if you run a long process on this Thread, the UI will appear to be frozen until that process is completed.
While there are several ways to create background tasks in JavaFX, below is a simple demo application that uses a Task to do so.
The example code is commented throughout to explain what we are doing. The example uses a ListView instead of a TreeView, just for simplicity, but the concept is the same regardless.
This example shows a basic interface with a ListView and a Button to start the loading process. At the bottom is a Label that will keep the user updated on the current step in the Task's process.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Just a simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Create the list view
ListView<String> listView = new ListView<>();
listView.setPrefHeight(200);
// Status label
Label lblStatus = new Label();
// Here we will create a new Task that will run in the background
Task<List<String>> loadDataTask = new Task<List<String>>() {
// We override the call() method; this is the code that is run when the task is started. When the Task
// is completed, it will return a new List of Strings
#Override
protected List<String> call() throws Exception {
// Tasks have a messageProperty that allows us to keep the UI informed. We will bind this value to
// our Label later
updateMessage("Loading data ...");
// Generate some sample data
List<String> listOfItems = new ArrayList<>();
listOfItems.add("One");
listOfItems.add("Two");
listOfItems.add("Three");
listOfItems.add("Four");
listOfItems.add("Five");
// Simulate a long-running task by pausing the thread for 10 seconds
Thread.sleep(10000);
// Now we can update the messageProperty again and return the completed data List
updateMessage("Finished!");
return listOfItems;
}
};
// We can now tell our application what to do once the Task has finished (either successfully or failure)
loadDataTask.setOnFailed(wse -> {
// This is called if an exception is thrown during the execution of the Task
// We will just print the Exception in this sample
loadDataTask.getException().printStackTrace();
});
// The Task completed successfully so lets now bind the List to our ListView
loadDataTask.setOnSucceeded(wse -> {
listView.setItems(FXCollections.observableArrayList(loadDataTask.getValue()));
});
// Now that we've defined our background Task and what to do when it's completed, let's create a button
// that allows us to start the task.
Button button = new Button("Load Data");
button.setOnAction(e -> {
// Let's bind our Label to the Task's messageProperty. This will cause the Label to update automatically
// whenever the Task calls the updateMessage() method.
lblStatus.textProperty().bind(loadDataTask.messageProperty());
// Now let's start the Task on a background Thread. This will cause the rest of the UI to remain
// responsive.
new Thread(loadDataTask).start();
});
// Add the controles to the Scene
root.getChildren().addAll(
button,
listView,
new Label("Status:"),
lblStatus);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
When the button is clicked, the background Task is executed, the Label is updated to show "Loading data ..." and the long-running task begins.
When the Task finishes, the ListView gets updated with the data and the Label will show `Finished!"
I've a custom dialog with several UI elements. Some TextFields are for numeric input. This dialog does not close when the escape key is hit and the focus is on any of the numeric text fields. The dialog closes fine when focus is on other TextFields which do not have this custom TextFormatter.
Here's the simplified code:
package application;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
TextField name = new TextField();
HBox hb1 = new HBox();
hb1.getChildren().addAll(new Label("Name: "), name);
TextField id = new TextField();
id.setTextFormatter(getNumberFormatter()); // numbers only
HBox hb2 = new HBox();
hb2.getChildren().addAll(new Label("ID: "), id);
VBox vbox = new VBox();
vbox.getChildren().addAll(hb1, hb2);
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Number Escape");
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setContent(vbox);
Platform.runLater(() -> name.requestFocus());
if (dialog.showAndWait().get() == ButtonType.OK) {
System.out.println("OK: " + name.getText() + id.getText());
} else {
System.out.println("Cancel");
}
} catch (Exception e) {
e.printStackTrace();
}
}
TextFormatter<Number> getNumberFormatter() {
// from https://stackoverflow.com/a/31043122
DecimalFormat format = new DecimalFormat("#");
TextFormatter<Number> tf = new TextFormatter<>(c -> {
if (c.getControlNewText().isEmpty()) {
return c;
}
ParsePosition parsePosition = new ParsePosition(0);
Object object = format.parse(c.getControlNewText(), parsePosition);
if (object == null || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
} else {
return c;
}
});
return tf;
}
public static void main(String[] args) {
launch(args);
}
}
How do I close the dialog when escape key is hit while focus is on id?
The Problem
Before offering a solution I think it's important, or at least interesting, to understand why having a TextFormatter seems to change the behavior of the Dialog. If this doesn't matter to you, feel free to jump to the end of the answer.
Cancel Buttons
According to the documentation of Button, a cancel button is:
the button that receives a keyboard VK_ESC press, if no other node in the scene consumes it.
The end of that sentence is the important part. The way cancel buttons, as well as default buttons, are implemented is by registering an accelerator with the Scene that the Button belongs to. These accelerators are only invoked if the appropriate KeyEvent bubbles up to the Scene. If the event is consumed before it reaches the Scene, the accelerator is not invoked.
Note: To understand more about event processing in JavaFX, especially terms such as "bubbles" and "consumed", I suggest reading this tutorial.
Dialogs
A Dialog has certain rules regarding how and when it can be closed. These rules are documented here, in the Dialog Closing Rules section. Suffice to say, basically everything depends on which ButtonTypes have been added to the DialogPane. In your example you use one of the predefined types: ButtonType.CANCEL. If you look at the documentation of that field, you'll see:
A pre-defined ButtonType that displays "Cancel" and has a ButtonBar.ButtonData of ButtonBar.ButtonData.CANCEL_CLOSE.
And if you look at the documentation of ButtonData.CANCEL_CLOSE, you'll see:
A tag for the "cancel" or "close" button.
Is cancel button: True
What this means, at least for the default implementation, is that the Button created for said ButtonType.CANCEL will be a cancel button. In other words, the Button will have its cancelButton property set to true. This is what allows one to close a Dialog by pressing the Esc key.
Note: It's the DialogPane#createButton(ButtonType) method that's responsible for creating the appropriate button (and can be overridden for customization). While the return type of that method is Node it is typical, as documented, to return an instance of Button.
The TextFormatter
Every control in (core) JavaFX has three components: the control class, the skin class, and the behavior class. The latter class is responsible for handling user input, such as mouse and key events. In this case, we care about TextInputControlBehavior and TextFieldBehavior; the former is the superclass of the latter.
Note: Unlike the skin classes, which became public API in JavaFX 9, the behavior classes are still private API as of JavaFX 12.0.2. Much of what's described below are implementation details.
The TextInputControlBehavior class registers an EventHandler that reacts to the Esc key being pressed, invoking the cancelEdit(KeyEvent) method of the same class. All the base implementation of this method does is forward the KeyEvent to the TextInputControl's parent, if it has one—resulting in two event dispatching cycles for some unknown (to me) reason. However, the TextFieldBehavior class overrides this method:
#Override
protected void cancelEdit(KeyEvent event) {
TextField textField = getNode();
if (textField.getTextFormatter() != null) {
textField.cancelEdit();
event.consume();
} else {
super.cancelEdit(event);
}
}
As you can see, the presence of a TextFormatter causes the KeyEvent to be unconditionally consumed. This stops the event from reaching the Scene, the cancel button is not fired, and thus the Dialog does not close when the Esc key is pressed while the TextField has the focus. When there is no TextFormatter the super implementation is invoked which, as stated before, simply forwards the event to the parent.
The reason for this behavior is hinted at by the call to TextInputControl#cancelEdit(). That method has a "sister method" in the form of TextInputControl#commitValue(). If you look at the documentation of those two methods, you'll see:
If the field is currently being edited, this call will set text to the last commited value.
And:
Commit the current text and convert it to a value.
Respectively. That doesn't explain much, unfortunately, but if you look at the implementation their purpose becomes clear. A TextFormatter has a value property which is not updated in real time while typing into the TextField. Instead, the value is only updated when it's committed (e.g. by pressing Enter). The reverse is also true; the current text can be reverted to the current value by cancelling the edit (e.g. by pressing Esc).
Note: The conversion between String and an object of arbitrary type is handled by the StringConverter associated with the TextFormatter.
When there's a TextFormatter, the act of cancelling the edit is deemed an event-consuming scenario. This makes sense, I suppose. However, even when there's nothing to cancel the event is still consumed—this doesn't make as much sense to me.
A Solution
One way to fix this is to dig into the internals, using reflection, as is shown in kleopatra's answer. Another option is to add an event filter to the TextField or some ancestor of the TextField that closes the Dialog when the Esc key is pressed.
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
event.consume();
dialog.close();
}
});
If you'd like to include the cancel-edit behavior (cancel without closing) then you should only close the Dialog if there's no edit to cancel. Take a look at kleopatra's answer to see how one might determine whether or not a cancel is needed. If there is something to cancel simply don't consume the event and don't close the Dialog. If there isn't anything to cancel then just do the same as the code above (i.e. consume and close).
Is using an event filter the "recommended way"? It's certainly a valid way. JavaFX is event-driven like most, if not all, mainstream UI toolkits. For JavaFX specifically that means reacting to Events or observing Observable[Value]s for invalidations/changes. A framework built "on top of" JavaFX may add its own mechanisms. Since the problem is an event being consumed when we don't want it to be, it is valid to add your own handlers to implement the desired behavior.
The question already has an excellent answer, nothing to add. Just wanted to demonstrate how to tweak the behavior's InputMap to inject/replace our own mappings (as a follow-up to my comment). Beware: it's dirty in reflectively accessing a skin's behavior (private final field) and using internal api (Behavior/InputMap didn't make it into public, yet).
As Slaw pointed out, it's the behavior that prevents the ESCAPE from bubbling up to the cancel button if the TextField has a TextFormatter installed. IMO, it's not misbehaving in that case, just overshooting: the cancel/default buttons should be triggered on ESCAPE/ENTER if and only if no other had used it to change the state of the any input nodes (my somewhat free interpretation of consumed - had done some research on general UX guidelines that I can't find right now, embarassingly ...)
Applied to a form containing both a textField with textFormatter and a cancel button (aka: isCancelButton is true)
if the textField has uncommitted text, a cancel should revert the edit to the most recent committed value and consume the event
if the textField is committed it should let it bubble up to trigger the cancel button
The implementation of cancelEdit in behavior doesn't distinguish between those two states, but always consumes it. The example below implements the expected (by me, at least) behavior. It has
a helper method to decide whether or not is dirty (aka: the textField has an uncommitted edit)
a event handling method that checks for dirtyness, calls cancel and consumes the event only it had been dirty
a configuration method that tweaks the textFields inputMap such that the mapping is replaced by our own.
Note that this is a PoC: doesn't belong into helpers but into a custom skin (at the very least, ideally should be done by the behavior). And it is missing similar support for the ENTER .. which is slightly more involved because it has to take actionHandlers into account (which behavior tries to but fails to achieve)
To test the example:
compile (note: you need to reflectively access a private field, use whatever you have at hand - we all do, don't we) and run
type something into the field
press escape: the field's text is reverted to its initial value
press escape again: the cancel button is triggered
The example code:
public class TextFieldCancelSO extends Application {
/**
* Returns a boolean to indicate whether the given field has uncommitted
* changes.
*
* #param <T> the type of the formatter's value
* #param field the field to analyse
* #return true if the field has a textFormatter with converter and
* uncommitted changes, false otherwise
*/
public static <T> boolean isDirty(TextField field) {
TextFormatter<T> textFormatter = (TextFormatter<T>) field.getTextFormatter();
if (textFormatter == null || textFormatter.getValueConverter() == null) return false;
String fieldText = field.getText();
StringConverter<T> valueConverter = textFormatter.getValueConverter();
String formatterText = valueConverter.toString(textFormatter.getValue());
// todo: handle empty string vs. null value
return !Objects.equals(fieldText, formatterText);
}
/**
* Install a custom keyMapping for ESCAPE in the inputMap of the given field.
* #param field the textField to configure
*/
protected void installCancel(TextField field) {
// Dirty: reflectively access the behavior
// needs --add-exports at compile- and runtime!
// note: FXUtils is a custom helper class not contained in core fx, use your own
// helper or write the field access code as needed.
TextFieldBehavior behavior = (TextFieldBehavior) FXUtils.invokeGetFieldValue(
TextFieldSkin.class, field.getSkin(), "behavior");
// Dirty: internal api/classes
InputMap inputMap = behavior.getInputMap();
KeyBinding binding = new KeyBinding(KeyCode.ESCAPE);
// custom mapping that delegates to helper method
KeyMapping keyMapping = new KeyMapping(binding, e -> {
cancelEdit(field, e);
});
// by default, mappings consume the event - configure not to
keyMapping.setAutoConsume(false);
// remove old
inputMap.getMappings().remove(keyMapping);
// add new
inputMap.getMappings().add(keyMapping);
}
/**
* Custom EventHandler that's mapped to ESCAPE.
*
* #param field the field to handle a cancel for
* #param ev the received keyEvent
*/
protected void cancelEdit(TextField field, KeyEvent ev) {
boolean dirty = isDirty(field);
field.cancelEdit();
if (dirty) {
ev.consume();
}
}
private Parent createContent() {
TextFormatter<String> fieldFormatter = new TextFormatter<>(
TextFormatter.IDENTITY_STRING_CONVERTER, "textField ...");
TextField field = new TextField();
field.setTextFormatter(fieldFormatter);
// listen to skin: behavior is available only after it's set
field.skinProperty().addListener((src, ov, nv) -> {
installCancel(field);
});
// just to see the state of the formatter
Label fieldValue = new Label();
fieldValue.textProperty().bind(fieldFormatter.valueProperty());
// add cancel button
Button cancel = new Button("I'm the cancel");
cancel.setCancelButton(true);
cancel.setOnAction(e -> LOG.info("triggered: " + cancel.getText()));
HBox fields = new HBox(100, field, fieldValue);
BorderPane content = new BorderPane(fields);
content.setBottom(cancel);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TextFieldCancelSO.class.getName());
}
In my JavaFX application, I want to show an error dialog and exit the app whenever some unexpected exception occurs. So in my main-method I have set up a default uncaught exception handler before launching the app:
setDefaultUncaughtExceptionHandler((thread, cause) -> {
try {
cause.printStackTrace();
final Runnable showDialog = () -> {
// create dialog and show
};
if (Platform.isFxApplicationThread()) {
showDialog.run();
} else {
runAndWait(showDialog);
}
} catch (Throwable t) {
// ???
} finally {
System.exit(-1);
}
});
launch(MyApp.class);
Explanation: When the uncaught exception handler is executed on the JavaFX Application Thread (FXAT), I just run the code for showing the dialog. This of course doesn't work when the exception handler is not invoked by the FXAT. In this case, I have to push the code onto the FXAT. But I can't use Platform.runLater because then my app would exit before the dialog is shown. So, I made that custom method runAndWait which internally pushes the runnable via Platform.runLater, but waits until the execution of the runnable (with some countdown latch mechanism).
Now the problem with this: When an exception occurs in my start() method then my app gets stuck. Because it tries to wait until the execution of the dialog showing, but the FXAT never does this execution. I guess this is because when the start() method fails with an exception, the FXAT is just dead? I'm not sure whether this is a special case for the start() method or whether this will happen in any situation when an exception is thrown and not caught within code that is executed by the FXAT.
In Swing as I know the EDT is a complex architecture consisting of several threads. It wasn't the case that when some execution on the EDT failed that the entire Swing broke down. But here this is what seems to happen?
So what can I do here? How can I show to the user that the application cannot start?
Well....
I have a solution but I don't particularly recommend it. By default, Application.launch() will catch the exception thrown by the start method, exit the FX Platform, and then rethrow the exception. Since the FX Application Thread has shut down when your default uncaught exception handler executes, waiting for something to happen on the FX Application Thread just blocks indefinitely.
The exception to this is when the FX Application is running in web start. The way that the launcher checks for this is to check for the presence of a security manager. So a (really, really ugly) workaround is to install a security manager so that it looks like you are running in web start mode. This line will install a permissive security manager:
System.setSecurityManager(new SecurityManager(){
#Override
public void checkPermission(Permission perm) {}
});
Here's a SSCCE:
import java.lang.Thread.UncaughtExceptionHandler;
import java.security.Permission;
import java.util.concurrent.FutureTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
public class ShowDialogOnException {
public static final UncaughtExceptionHandler ALERT_EXCEPTION_HANDLER = (thread, cause) -> {
try {
cause.printStackTrace();
final Runnable showDialog = () -> {
Alert alert = new Alert(AlertType.ERROR);
alert.setContentText("An unknown error occurred");
alert.showAndWait();
};
if (Platform.isFxApplicationThread()) {
showDialog.run();
} else {
FutureTask<Void> showDialogTask = new FutureTask<Void>(showDialog, null);
Platform.runLater(showDialogTask);
showDialogTask.get();
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
System.exit(-1);
}
};
public static void main(String[] args) {
System.setSecurityManager(new SecurityManager(){
#Override
public void checkPermission(Permission perm) {}
});
Thread.setDefaultUncaughtExceptionHandler(ALERT_EXCEPTION_HANDLER);
Application.launch(App.class, args);
}
}
and a test app:
import javafx.application.Application;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
throw new Exception("An exception");
}
#Override
public void stop() {
System.out.println("Stop");
}
}
As I said, this is really something of a big hack, and I don't really recommend this unless you have no other option.