繁体   English   中英

JavaFX ChangeListener 并不总是有效

[英]JavaFX ChangeListener not always working

我有一个 JavaFX 应用程序,里面有一个并发任务。 在任务运行时,我想将 updateMessage() 中的消息附加到 TextArea

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

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

这是有效的,但不是每一个变化。 我用 System.out.println() 检查它并在任务中计数从 1 到 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 并得到相同的结果,1,4,5,8,9(结果是随机的,并不总是 1,4,5...)

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

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

对 updateMessage 的调用会合并并稍后在 FX 应用程序线程上运行,因此即使来自 FX 应用程序线程对 updateMessage 的调用也不一定会立即更新此属性,并且中间消息值可能会合并以保存事件通知

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

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

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

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

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方法保证在每帧渲染时调用一次。 这看起来像:

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();
        }
    }
}

替代解决方案,但我认为这是不好的做法。

您可以在updateMessage()之后使用 sleep 方法

void showUpdateMessage(String msg) throws InterruptedException {
    updateMessage(msg);
    TimeUnit.MILLISECONDS.sleep(100);
}

小心使用!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM