gpt4 book ai didi

JavaFX ChangeListener 并不总是有效

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:46:57 25 4
gpt4 key购买 nike

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

因为绑定(bind)不会将新文本附加到 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 中添加了一个 println 并得到相同的结果,1,4,5,8,9(结果是随机的,并不总是 1,4,5...)

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

最佳答案

message 属性被设计为保存任务 的“当前消息”的属性:即目标用例类似于状态消息。在此用例中,仅在属性中存储很短时间的消息是否永远不会被截取都无关紧要。事实上,documentation for updateMessage()状态:

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 次或更少),您可以期望只观察到一个值。如果您的用例需要观察每个值,那么您需要使用另一种机制。

一个非常幼稚的实现将只使用 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();
}
}
}

关于JavaFX ChangeListener 并不总是有效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31408363/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com