我处于启动新视图但在其控制器中向服务器发出请求以获取一些对象的情况。这个请求需要一些时间,所以我想在这个等待时间内实现一点加载视图。因此,在我的控制器中,我实现了一个服务对象以在另一个线程中发出此请求:
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));
Scene scene = new Scene(root);
Stage primaryStage = new Stage();
primaryStage.initStyle(StageStyle.UNDECORATED);
Service<ProjectModel> service = new Service<ProjectModel>() {
@Override
protected Task<ProjectModel> createTask() {
return new Task<ProjectModel>() {
@Override
protected ProjectModel call() throws Exception {
SenderText data = new SenderText();
int id = Integer.parseInt(data.getData());
Client client = Client.getInstance();
JSONObject tosend = new JSONObject();
tosend.put("Type", "Get Project");
tosend.put("IDproject", id);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
client.sendText(tosend.toString());
String response = client.receiveText();
ProjectModel project = gson.fromJson(response, ProjectModel.class);
projectLocal = project;
System.out.println(gson.toJson(projectLocal));
primaryStage.close();
primaryStage.hide();
return project;
}
};
}
};
service.start();
primaryStage.setScene(scene);
primaryStage.initModality(Modality.APPLICATION_MODAL);
while (service.getValue() == null) {
primaryStage.showAndWait();
}
primaryStage.close();
System.out.println("Finish");
primaryStage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
我测试并在 call() 方法上设置了对象。但是加载视图没有关闭。我也尝试调用 primaryStage.show()
而不是 showandWait()
但它不起作用。知道如何解决吗?
回答1
JavaFX 中有两个主要的线程规则(类似于大多数其他 UI 工具包)。
- 您不得从后台线程对 UI 执行操作。这包括创建、显示或隐藏窗口。
- 您不得阻塞 UI 线程(“FX 应用程序线程”)。
在 call()
方法中调用 primaryStage.close()
违反了第一条规则。
您的繁忙 while
循环违反了第二条规则。 (更糟糕的是;我认为服务的 value
属性仅在 FX 应用程序线程上更新。因此,由于您阻塞了该线程,因此无法更新 value
属性并且 while
循环永远不会退出。)
要在后台 Task
完成时执行 UI 操作,可以使用 onSucceeded
处理程序。在后台任务成功完成后,在 FX 应用程序线程上调用此处理程序。
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));
Scene scene = new Scene(root);
Stage primaryStage = new Stage();
primaryStage.initStyle(StageStyle.UNDECORATED);
Service<ProjectModel> service = new Service<ProjectModel>() {
@Override
protected Task<ProjectModel> createTask() {
return new Task<ProjectModel>() {
@Override
protected ProjectModel call() throws Exception {
SenderText data = new SenderText();
int id = Integer.parseInt(data.getData());
Client client = Client.getInstance();
JSONObject tosend = new JSONObject();
tosend.put("Type", "Get Project");
tosend.put("IDproject", id);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
client.sendText(tosend.toString());
String response = client.receiveText();
ProjectModel project = gson.fromJson(response, ProjectModel.class);
return project;
}
};
}
};
service.setOnSucceeded(event -> {
System.out.println("Finish");
// I think; don't know what projectLocal is, or
// which threads it should be accessed from:
projectLocal = project ;
primaryStage.hide();
});
service.start();
primaryStage.setScene(scene);
primaryStage.showAndWait();
} catch (Exception e) {
e.printStackTrace();
}
}
顺便说一句,您可能在这里不需要 Service
。 Service
真的是为重复使用而设计的,而且你只使用一次。只需创建一个 Task
并在后台线程上运行它就足够了:
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));
Scene scene = new Scene(root);
Stage primaryStage = new Stage();
primaryStage.initStyle(StageStyle.UNDECORATED);
Task<ProjectModel> task = new Task<ProjectModel>() {
@Override
protected ProjectModel call() throws Exception {
SenderText data = new SenderText();
int id = Integer.parseInt(data.getData());
Client client = Client.getInstance();
JSONObject tosend = new JSONObject();
tosend.put("Type", "Get Project");
tosend.put("IDproject", id);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
client.sendText(tosend.toString());
String response = client.receiveText();
ProjectModel project = gson.fromJson(response, ProjectModel.class);
return project;
}
};
task.setOnSucceeded(event -> {
System.out.println("Finish");
// I think; don't know what projectLocal is, or
// which threads it should be accessed from:
projectLocal = project ;
primaryStage.hide();
});
new Thread(task).start();
primaryStage.setScene(scene);
primaryStage.showAndWait();
} catch (Exception e) {
e.printStackTrace();
}
}
您的代码结构在这里看起来有点奇怪。看起来您需要在启动时为您的应用程序创建一个模型,并且该启动需要一些时间,因此您在加载时会显示某种“启动画面”。这里奇怪的部分是您在 FXML 控制器的 initialize()
方法中执行此操作。这确实应该在您的 Application
类中作为生命周期方法 start()
的一部分来完成。该结构应如下所示:
public class App extends Application {
public static void main(String[] args) {
Application.launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
Task<ProjectModel> task = new Task<>() {
@Override
public ProjectModel call() throws Exception {
return loadModel();
}
};
Stage loadingStage = showLoadingStage();
task.setOnSucceeded(e -> {
loadingStage.hide();
showMainStage(primaryStage, task.getValue());
});
new Thread(task).start();
}
private ProjectModel loadModel() throws Exception {
// load model and return it....
}
private Stage showLoadingStage() throws Exception {
Scene scene = new Scene(getClass().getResource("LoadingPage.fxml"));
Stage loadingStage = new Stage();
loadingStage.initStyle(StageStyle.UNDECORATED);
loadingStage.setScene(scene);
loadingStage.show();
return loadingStage ;
}
private void showMainStage(Stage stage, ProjectModel model) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
Parent root = loader.load();
MainController controller = loader.getController();
controller.setModel(model);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
MainController
是具有 setModel(...)
方法的常规 FXML 控制器:
public class MainController {
private ProjectModel model ;
// @FXML-annotated fields
@FXML
private void initialize() {
// initialization work that does not require model
}
public void setModel(ProjectModel model) {
this.model = model ;
// initialize any UI that depends on the model, etc.
}
}