gpt4 book ai didi

java - 从服务线程更新 JavaFX GUI

转载 作者:塔克拉玛干 更新时间:2023-11-01 23:08:34 26 4
gpt4 key购买 nike

如何从 JavaFX 服务中安全地更新 JavaFX GUI 上的小部件。我记得当我使用 Swing 进行开发时,我习惯于“稍后调用”和其他各种 swing worker 实用程序,以确保对 UI 的所有更新都在 Java 事件线程中安全地处理。下面是一个处理数据报消息的简单服务线程的示例。缺少的部分是解析数据报消息和更新相应的 UI 小部件的位置。如您所见,服务类非常简单。

我不确定我是否需要使用简单的绑定(bind)属性(如消息),或者我是否应该将小部件传递给 StatusListenerService 的构造函数(这可能不是最好的做法)。有人能给我一个很好的类似例子吗?

public class StatusListenerService extends Service<Void> {
private final int mPortNum;

/**
*
* @param aPortNum server listen port for inbound status messages
*/
public StatusListenerService(final int aPortNum) {
this.mPortNum = aPortNum;
}

@Override
protected Task<Void> createTask() {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
updateMessage("Running...");
try {
DatagramSocket serverSocket = new DatagramSocket(mPortNum);
// allocate space for received datagrams
byte[] bytes = new byte[512];
//message.setByteBuffer(ByteBuffer.wrap(bytes), 0);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (!isCancelled()) {
serverSocket.receive(packet);
SystemStatusMessage message = new SystemStatusMessage();
message.setByteBuffer(ByteBuffer.wrap(bytes), 0);
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
updateMessage("Cancelled");
return null;
}
};
}
}

最佳答案

“低级”方法是使用 Platform.runLater(Runnable r) 来更新 UI。这将在 FX 应用程序线程上执行 r,相当于 Swing 的 SwingUtilities.invokeLater(...)。因此,一种方法是简单地从 call() 方法内部调用 Platform.runLater(...) 并更新 UI。不过,正如您所指出的,这本质上需要服务了解 UI 的详细信息,这是不可取的(尽管有一些模式可以解决此问题)。

Task 定义了一些属性并有相应的 updateXXX 方法,例如您在示例代码中调用的 updateMessage(...) 方法.这些方法可以安全地从任何线程调用,并导致对要在 FX 应用程序线程上执行的相应属性的更新。 (因此,在您的示例中,您可以安全地将标签的文本绑定(bind)到服务的 messageProperty。)除了确保在正确的线程上执行更新外,这些 updateXXX 方法还限制了更新,因此您基本上可以根据需要经常调用它们,而不会用太多事件淹没 FX 应用程序线程来处理:在 UI 的单个框架内发生的更新将被合并,以便仅最后一次这样的更新(在给定的帧内)是可见的。

如果适合您的用例,您可以利用它来更新任务/服务的 valueProperty。因此,如果您有一些(最好是不可变的)类来表示解析数据包的结果(我们称它为 PacketData;但也许它就像 String 一样简单),您制作

public class StatusListener implements Service<PacketData> {

// ...

@Override
protected Task<PacketData> createTask() {
return new Task<PacketData>() {
// ...

@Override
public PacketData call() {
// ...
while (! isCancelled()) {
// receive packet, parse data, and wrap results:
PacketData data = new PacketData(...);
updateValue(data);
}
return null ;
}
};
}
}

现在你可以做

StatusListener listener = new StatusListener();
listener.valueProperty().addListener((obs, oldValue, newValue) -> {
// update UI with newValue...
});
listener.start();

请注意,当服务被取消时,该值被代码更新为 null,因此对于我概述的实现,您需要确保 valueProperty()< 上的监听器 处理这种情况。

另请注意,如果它们发生在同一帧渲染中,这将合并对 updateValue() 的连续调用。因此,如果您需要确保处理处理程序中的每个数据,那么这不是一种合适的方法(尽管通常不需要在 FX 应用程序线程上执行此类功能反正)。如果您的 UI 只需要显示后台进程的“最新状态”,这是一个很好的方法。

SSCCE 展示了这种技术:

import java.util.Random;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class LongRunningTaskExample extends Application {

@Override
public void start(Stage primaryStage) {
CheckBox enabled = new CheckBox("Enabled");
enabled.setDisable(true);
CheckBox activated = new CheckBox("Activated");
activated.setDisable(true);
Label name = new Label();
Label value = new Label();

Label serviceStatus = new Label();

StatusService service = new StatusService();
serviceStatus.textProperty().bind(service.messageProperty());

service.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) {
enabled.setSelected(false);
activated.setSelected(false);
name.setText("");
value.setText("");
} else {
enabled.setSelected(newValue.isEnabled());
activated.setSelected(newValue.isActivated());
name.setText(newValue.getName());
value.setText("Value: "+newValue.getValue());
}
});

Button startStop = new Button();
startStop.textProperty().bind(Bindings
.when(service.runningProperty())
.then("Stop")
.otherwise("Start"));

startStop.setOnAction(e -> {
if (service.isRunning()) {
service.cancel() ;
} else {
service.restart();
}
});

VBox root = new VBox(5, serviceStatus, name, value, enabled, activated, startStop);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}

private static class StatusService extends Service<Status> {
@Override
protected Task<Status> createTask() {
return new Task<Status>() {
@Override
protected Status call() throws Exception {
Random rng = new Random();
updateMessage("Running");
while (! isCancelled()) {

// mimic sporadic data feed:
try {
Thread.sleep(rng.nextInt(2000));
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
if (isCancelled()) {
break ;
}
}

Status status = new Status("Status "+rng.nextInt(100),
rng.nextInt(100), rng.nextBoolean(), rng.nextBoolean());
updateValue(status);
}
updateMessage("Cancelled");
return null ;
}
};
}
}

private static class Status {
private final boolean enabled ;
private final boolean activated ;
private final String name ;
private final int value ;

public Status(String name, int value, boolean enabled, boolean activated) {
this.name = name ;
this.value = value ;
this.enabled = enabled ;
this.activated = activated ;
}

public boolean isEnabled() {
return enabled;
}

public boolean isActivated() {
return activated;
}

public String getName() {
return name;
}

public int getValue() {
return value;
}
}

public static void main(String[] args) {
launch(args);
}
}

关于java - 从服务线程更新 JavaFX GUI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35140569/

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