JavaFX ChangeListener不总是工作 [英] JavaFX ChangeListener not always working

查看:759
本文介绍了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("\n" + 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
i然后在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 ?

推荐答案

属性设计为一个属性,它为任务保存当前消息:即目标用例类似于状态消息。在这种使用情况下,如果仅在很短的时间内存储在属性中的消息从不被拦截也没关系。确实, <$ c $的文档c> 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
应用程序线程,可能不一定导致立即更新
此属性,并且中间消息值可以合并到
保存活动通知

(我强调)。因此,简而言之,传递给 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 = "\n" + 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 "\n" + 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("\n"+msg));
        if (messagesReceived >= numMessages) {
            stop();
        }
    }
}

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

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