JavaFX ChangeListener 并不总是有效 [英] JavaFX ChangeListener not always working

查看:41
本文介绍了JavaFX ChangeListener 并不总是有效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 JavaFX 应用程序和一个并发任务.当任务正在运行时,我想将来自 updateMessage() 的消息附加到 TextArea

i have a JavaFX Application and in there a concurrency Task. While the Task is running, i want to append the message from updateMessage() to a TextArea

因为绑定不会将新文本附加到 TextArea,所以我使用了 ChangeListener

because the binding doesn't append new text to the TextArea, i used a ChangeListener

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

这是有效的,但不是每次更改都有效.我用 System.out.println() 查了一下,把任务从 1 算到 300

That is working but not on every change. I checked it with a System.out.println() and counted in the task from 1 to 300

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

任务中的这个 println() 给了我我想要的 1,2,3,4,5,6,7,8 等等,但我的 TextArea 显示 1,4,5,8,9然后我在 ChangeListener 中添加了一个 println 并得到相同的结果,1,4,5,8,9(结果是随机的,并不总是 1,4,5...)

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

为什么?是否有其他方法可以将消息文本附加到 TextAres,也许使用 bind ?

why ? are there other ways to append the message text to the TextAres, maybe with bind ?

推荐答案

message 属性被设计为保存 task 的当前消息"的属性:即目标用例类似于状态消息.在此用例中,是否从未拦截仅在属性中存储很短时间的消息并不重要.事实上,文档对于 updateMessage() 状态:

The message property is designed as a property which holds a "current message" for the task: i.e. the target use case is something akin to a status message. In this use case, it doesn't matter if a message that is stored in the property for only a very brief time is never intercepted. Indeed, the documentation for updateMessage() states:

对 updateMessage 的调用被合并并稍后在 FX 上运行应用程序线程,因此调用 updateMessage,甚至来自 FX应用程序线程,不一定会立即更新此属性和 中间消息值可以合并为节省事件通知.

Calls to updateMessage are coalesced and run later on the FX application thread, so calls to updateMessage, even from the FX Application thread, may not necessarily result in immediate updates to this property, and intermediate message values may be coalesced to save on event notifications.

(我的重点).因此,简而言之,如果传递给 updateMessage(...) 的某些值被另一个值快速取代,则它们实际上可能永远不会被设置为 messageProperty 的值.通常,每次将一帧渲染到屏幕时(每秒 60 次,或更少),您只能期望观察到一个值.如果您有一个需要观察每个值的用例,那么您需要使用另一种机制.

(my emphasis). So, in short, some values passed to updateMessage(...) may never actually be set as the value of messageProperty if they are superceded quickly by another value. In general, you can expect only one value to be observed every time a frame is rendered to the screen (60 times per second, or fewer). If you have a use case where it is important you want to observe every value, then you need to use another mechanism.

一个非常简单的实现只是使用 Platform.runLater(...) 并直接更新文本区域.我不推荐这种实现,因为您可能会因过多的调用而导致 FX 应用程序线程泛滥(这正是 updateMessage(...) 合并调用的确切原因),从而使 UI 无响应.然而,这个实现看起来像:

A very naïve implementation would just use Platform.runLater(...) and directly update the text area. I do not recommend this implementation, as you risk flooding the FX Application Thread with too many calls (the exact reason why updateMessage(...) coalesces calls), making the UI unresponsive. However, this implementation would look like:

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

另一种选择是让每个操作成为一个单独的任务,并在某个执行器中并行执行它们.附加到每个任务的 onSucceeded 处理程序中的文本区域.在这个实现中,结果的顺序不是预先确定的,所以如果顺序很重要,这不是一个合适的机制:

Another option is to make each operation a separate task, and execute them all in parallel in some executor. Append to the text area in each task's onSucceeded handler. In this implementation, the order of the results is not predetermined, so if order is important, this is not an appropriate 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 "
" + value ; // value to be processed in onSucceeded
        }
    };
    task.setOnSucceeded(e -> ta_Statusbereich.appendText(task.getValue()));
    exec.execute(task);
}

如果你想从一个任务中完成所有这些,并控制顺序,那么你可以将所有消息放入一个BlockingQueue,从阻塞队列中取出消息并将它们放入文本中FX 应用程序线程上的区域.为确保您不会用过多的调用淹没 FX 应用程序线程,您应该在每帧渲染到屏幕时使用队列中的消息不超过一次.您可以使用 AnimationTimer 为此目的:它的 handle 方法保证在每帧渲染时被调用一次.这看起来像:

If you want to do all this from a single task, and control the order, then you can put all the messages into a BlockingQueue, taking messages from the blocking queue and placing them in the text area on the FX Application thread. To ensure you don't flood the FX Application thread with too many calls, you should consume the messages from the queue no more than once per frame rendering to the screen. You can use an AnimationTimer for this purpose: it's handle method is guaranteed to be invoked once per frame rendering. This looks like:

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("
"+msg));
        if (messagesReceived >= numMessages) {
            stop();
        }
    }
}

这篇关于JavaFX ChangeListener 并不总是有效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆