2

I'm new to JavaFX and hung up on a minor issue when using ControlsFX Notification. During some longer running tasks (downloading some files for example) I'd like to show a notification when the downloads start, and again when they finish; however, when I try this the notifications both show up only after the task is finished.

Here's a sample with Thread.Sleep simulating a delay for some code to run:

 Notifications.create() .title("Title Text") .text("Hello World 1!") .darkStyle() .show();
 Thread.sleep(500);
 Notifications.create() .title("Title Text") .text("Hello World 2!") .darkStyle() .show();
 Thread.sleep(500);
 Notifications.create() .title("Title Text") .text("Hello World 3!") .darkStyle() .show();
 Thread.sleep(500);

When this runs, all the notifications show at once, not a half second apart.

Here's an image of what the notifications look like in the lower right corner

However, they fade away a half second apart..

So they're being created 500 milliseconds apart but not being displayed right away. How do I get them to show right away?

docb45
  • 274
  • 3
  • 9

2 Answers2

3

In your code you block the JavaFX application thread that is responsible for redrawing the UI. This way any changes in the GUI won't be visible and events won't be handled, ect.

You could use a Task and add a listener to the message property for diplaying the messages. Updates for this property are guarantied to be executed on the application thread. The only drawback is that for fast updates to the property some notifications could be skipped.

Task<Void> task = new Task<Void>() {

    @Override
    protected Void call() throws Exception {
        // update message property
        updateMessage("Hello World 1!");

        Thread.sleep(500);
        updateMessage("Hello World 2!");
        Thread.sleep(500);
        updateMessage("Hello World 3!");
        Thread.sleep(500);
        return null;
    }

};

// display message changes as notifications
task.messageProperty().addListener((observable, oldMessage, newMessage) ->
        Notifications.create().title("Title Text").text(newMessage).darkStyle().show());

// execute long running task on background thread
new Thread(task).start();

If you need every message to be displayed, you should use Platform.runLater to trigger the notifications.

Thread thread = new Thread(new Runnable() {

    private void postMessage(final String message) {
        Platform.runLater(() -> Notifications.create().title("Title Text").text(message).darkStyle().show());
    }

    @Override
    public void run() {
        try {
            postMessage("Hello World 1!");
            Thread.sleep(500);
            postMessage("Hello World 2!");
            Thread.sleep(500);
            postMessage("Hello World 3!");
            Thread.sleep(500);
        } catch (InterruptedException ex) {
            Logger.getLogger(NotificationsTestMain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

});

thread.start();

Note that you should take care not to use Platform.runLater too often. In case there are many updates, it's sometimes best to group them. However in this case you'll probably not want to use notifications anyway, since there would simply be to many to display all of them. Nontheless here's some code that demonstrates grouping the updates:

private final List<String> messageQueue = new ArrayList<>();
private boolean updating;
private final Runnable updater = () -> {
    synchronized (messageQueue) {
        for (String message : messageQueue) {
            Notifications.create().title("Title Text").text(message).darkStyle().show();
        }
        messageQueue.clear();
        updating = false;
    }
};

private void postMessage(String message) {
    synchronized (messageQueue) {
        messageQueue.add(message);
        if (!updating) {
            updating = true;
            Platform.runLater(updater);
        }
    }
}
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Using Timeline I run into the same problem as platform.runLater. All three are created and shown after the application thread is finished doing whatever it's doing. In my question I used Thread.sleep but my actual application downloads some files and does some other file processing. I'd like it to be able to notify "Starting Downloads" then "Downloads Finished" etc. So showing the user all the notifications at once at the end obviously isn't very informative. I'm unfamiliar with Timeline so I'll play with this a bit to see if I can get it behaving the way I want. – docb45 Oct 31 '16 at 17:31
  • @docb45 `Timeline` was a bad idea (I somehow didn't pay the proper attention to the *"Thread.Sleep simulating a delay"* part). Modified the answer... – fabian Nov 01 '16 at 01:02
  • Works amazing! I played with Task but I was missing the part about adding a listener to display the notification. This works perfectly. – docb45 Nov 01 '16 at 14:17
  • Why even bother having updateMessage and updateProgress when they could very easily get out of sync and are not able to handle multiple events/messages in a small timeframe? That just seems like bad design on JavaFX's part to me. – FireController1847 May 02 '21 at 22:25
0

You don't show any context but I'd say your code is just blocking the JavaFX GUI thread. You have to execute your code above in a separate thread and wrap each notification creation in a Platform.runLater call. Alternatively you could also use an AnimationTimer or Timeline. Then you won't need a separate thread and no Thread.sleep.

mipa
  • 10,369
  • 2
  • 16
  • 35
  • Ah, yes, I completely forgot about the Platform.runLater. I did try that but the problem I ran into there was all notifications would be created after the application thread finished. With the above they are **created** separately and all are **shown** at the end at the same time. So the first one (which should display for 3 seconds) has to wait for the three sleep calls and then appears but only for 1.5 seconds. If I use platform.runLater all three appear and stay for 3 seconds but they are created simultaneously only after the application thread has finished. – docb45 Oct 31 '16 at 17:18