JavaFX ChangeListener not always working

I have a JavaFX application and there the concurrency task. While the task is running, I want to add a message from updateMessage () to TextArea

because the binding does not add new text to TextArea, I used ChangeListener

worker.messageProperty().addListener((observable, oldValue, newValue) -> { ta_Statusbereich.appendText("\n" + newValue); }); 

This works, but not on every change. I checked it with System.out.println () and counted from 1 to 300 in the task

 for (Integer i = 1; i <= 300; i++) { updateMessage(i.toString()); System.out.println(i.toString()); } 

this println () in the Task gives me what I want 1,2,3,4,5,6,7,8 etc. but my TextArea shows 1,4,5,8,9 Then I added println in ChangeListener and get the same result, 1,4,5,8,9 (random result is not always 1,4,5 ...)

why? Are there other ways to add message text to TextAres, possibly with binding?

+6
source share
1 answer

The message property is created as a property that contains the "current message" for the task : i.e. the target use case is something like a status message. In this case, the use does not matter, the message that is stored in the property for only a very short time is never intercepted. Indeed, the documentation for updateMessage() states:

UpdateMessage calls are merged and started later on the FX application stream, so it calls updateMessage, even with FX The application stream may not necessarily lead to immediate updates, this property and intermediate message values ​​can be combined with save event notifications .

(my emphasis). In short, some of the values ​​passed to updateMessage(...) can never be set to messageProperty if they are quickly carried over by another value. In general, you can expect that only one value will be observed each time a frame is displayed on the screen (60 times per second or less). If you have a use case where it is important that you observe each value, you need to use a different mechanism.

A very naive implementation would simply use Platform.runLater(...) and directly update the text area. I do not recommend this implementation, since you run the risk of flood the FX application stream with too many calls (the exact reason updateMessage(...) combines calls), which makes the user interface unresponsive. However, this implementation will look like this:

 for (int i = 1 ; i <= 300; i++) { String value = "\n" + i ; Platform.runLater(() -> ta_Statusbereich.appendText(value)); } 

Another option is to make each operation a separate task and execute them all in parallel in some kind of artist. Add to the text area in each onSucceeded task. In this implementation, the order of the results is not specified, therefore, if the order is important, this is not a suitable mechanism:

 final int numThreads = 8 ; Executor exec = Executors.newFixedThreadPool(numThreads, runnable -> { Thread t = Executors.defaultThreadFactory().newThread(runnable); t.setDaemon(true); return t ; }); // ... for (int i = 1; i <= 300; i++) { int value = i ; Task<String> task = new Task<String>() { @Override public String call() { // in real life, do real work here... return "\n" + value ; // value to be processed in onSucceeded } }; task.setOnSucceeded(e -> ta_Statusbereich.appendText(task.getValue())); exec.execute(task); } 

If you want to do all this from one task and manage the order, you can put all messages in BlockingQueue , taking messages from the blocking queue and placing them in the text area of ​​the FX thread application. So that you do not fill the flow of FX applications with too many calls, you must consume messages from the queue no more than once to render the frame on the screen. You can use AnimationTimer for this purpose: the handle method is guaranteed to be called once to render the frame. It looks like this:

 BlockingQueue<String> messageQueue = new LinkedBlockingQueue<>(); Task<Void> task = new Task<Void>() { @Override public Void call() throws Exception { final int numMessages = 300 ; Platform.runLater(() -> new MessageConsumer(messageQueue, ta_Statusbereich, numMessages).start()); for (int i = 1; i <= numMessages; i++) { // do real work... messageQueue.put(Integer.toString(i)); } return null ; } }; new Thread(task).start(); // or submit to an executor... // ... public class MessageConsumer extends AnimationTimer { private final BlockingQueue<String> messageQueue ; private final TextArea textArea ; private final numMessages ; private int messagesReceived = 0 ; public MessageConsumer(BlockingQueue<String> messageQueue, TextArea textArea, int numMessages) { this.messageQueue = messageQueue ; this.textArea = textArea ; this.numMessages = numMessages ; } @Override public void handle(long now) { List<String> messages = new ArrayList<>(); messagesReceived += messageQueue.drainTo(messages); messages.forEach(msg -> textArea.appendText("\n"+msg)); if (messagesReceived >= numMessages) { stop(); } } } 
+15
source

Source: https://habr.com/ru/post/989195/


All Articles