我的初始fxml(比如说home.fxml
)有很多功能,因此需要花费很多时间才能完全加载。因此,为了避免程序启动和fxml加载之间的时间差,我引入了一个带有gif图像的fxml(比如loader.fxml
),它应该在主fxml加载时出现。
问题是我的loader.fxml中的gif图像没有移动,因为程序挂起直到完全加载home.fxml。
为了避免这种情况,我将home.fxml加载到一个线程中,如下面的代码所示。
public class UATReportGeneration extends Application {
private static Stage mainStage;
@Override
public void start(Stage stage) {
Parent loaderRoot = null;
try {
loaderRoot = FXMLLoader.load(getClass().getResource("/uatreportgeneration/fxml/Loader.fxml"));
} catch (IOException ex) {
Logger.getLogger(UATReportGeneration.class.getName()).log(Level.SEVERE, null, ex);
}
Scene loadScene = new Scene(loaderRoot);
stage.setScene(loadScene);
stage.initStyle(StageStyle.UNDECORATED);
stage.getIcons().add(new Image(this.getClass().getResourceAsStream("/uatreportgeneration/Images/logo.png")));
stage.show();
mainStage = new Stage(StageStyle.UNDECORATED);
mainStage.setTitle("Upgrade Analysis");
mainStage.getIcons().add(new Image(this.getClass().getResourceAsStream("/uatreportgeneration/Images/logo.png")));
setStage(mainStage);
new Thread(() -> {
Platform.runLater(() -> {
try {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("/uatreportgeneration/fxml/Home.fxml"));
Scene scene = new Scene(root);
mainStage.setScene(scene);
mainStage.show();
stage.hide();
System.out.println("Stage showing");
// Get current screen of the stage
ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
// Change stage properties
Rectangle2D bounds = screens.get(0).getVisualBounds();
mainStage.setX(bounds.getMinX());
mainStage.setY(bounds.getMinY());
mainStage.setWidth(bounds.getWidth());
mainStage.setHeight(bounds.getHeight());
System.out.println("thread complete");
} catch (IOException ex) {
Logger.getLogger(UATReportGeneration.class.getName()).log(Level.SEVERE, null, ex);
}
});
}).start();
}
public static Stage getStage() {
return mainStage;
}
public static void setStage(Stage stage) {
mainStage = stage;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
但是在这段代码之后我的程序也挂了(gif图像没有移动)。如果我将fxml加载到Platform.runLater()
之外,我会收到异常Not on FX Thread
。
我也厌倦了使用Task()
,但是如果我尝试将fxml加载到Platform.runLater()
之外,那么gif图像正在移动,但fxml没有在后台加载。
任何人都可以帮助我并告诉我如何更正代码,以便我的fxml在后台加载而不会影响前台进程。
答案 0 :(得分:4)
使用Task
。您需要安排创建场景并更新FX应用程序线程上的阶段。最干净的方法是使用Task<Parent>
:
Task<Parent> loadTask = new Task<Parent>() {
@Override
public Parent call() throws IOException {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("/uatreportgeneration/fxml/Home.fxml"));
return root ;
}
};
loadTask.setOnSucceeded(e -> {
Scene scene = new Scene(loadTask.getValue());
mainStage.setScene(scene);
mainStage.show();
stage.hide();
System.out.println("Stage showing");
// Get current screen of the stage
ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
// Change stage properties
Rectangle2D bounds = screens.get(0).getVisualBounds();
mainStage.setX(bounds.getMinX());
mainStage.setY(bounds.getMinY());
mainStage.setWidth(bounds.getWidth());
mainStage.setHeight(bounds.getHeight());
System.out.println("thread complete");
});
loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());
Thread thread = new Thread(loadTask);
thread.start();
以下是使用此技术的SSCCE:
main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<VBox spacing="10" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
<padding>
<Insets top="24" left="24" right="24" bottom="24"/>
</padding>
<TextField />
<Button fx:id="button" text="Show Window" onAction="#showWindow"/>
</VBox>
MainController(使用上面显示的Task
方法):
import java.io.IOException;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class MainController {
@FXML
private Button button ;
@FXML
private void showWindow() {
Task<Parent> loadTask = new Task<Parent>() {
@Override
public Parent call() throws IOException, InterruptedException {
// simulate long-loading process:
Thread.sleep(5000);
FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
Parent root = loader.load();
return root ;
}
};
loadTask.setOnSucceeded(e -> {
Scene scene = new Scene(loadTask.getValue());
Stage stage = new Stage();
stage.initOwner(button.getScene().getWindow());
stage.setScene(scene);
stage.show();
});
loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());
Thread thread = new Thread(loadTask);
thread.start();
}
}
test.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="TestController">
<padding>
<Insets top="24" left="24" right="24" bottom="24"/>
</padding>
<center>
<Label fx:id="label" text="This is a new window"/>
</center>
<bottom>
<Button text="OK" onAction="#closeWindow" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets top="5" bottom="5" left="5" right="5"/>
</BorderPane.margin>
</Button>
</bottom>
</BorderPane>
的TestController:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class TestController {
@FXML
private Label label ;
@FXML
private void closeWindow() {
label.getScene().getWindow().hide();
}
}
主要应用:
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("main.fxml"))));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
请注意,按下按钮后,您仍然可以在五秒钟内输入文本字段&#34; loading&#34;用户界面,因此用户界面保持响应。
答案 1 :(得分:0)
使用Task
的方法已经正确无误。你只是错过了一点:你只是错过了另一个Platform#invokeLater()
来更新UI:
new Thread(new Task() {
@Override
protected Object call() throws Exception {
// Simulating long loading
Thread.sleep(5000);
FXMLLoader loader = new FXMLLoader(getClass().getResource("home.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
// Updating the UI requires another Platform.runLater()
Platform.runLater(new Runnable() {
@Override
public void run() {
mainStage.setScene(scene);
mainStage.show();
stage.hide();
System.out.println("Stage showing");
// Get current screen of the stage
ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
// Change stage properties
Rectangle2D bounds = screens.get(0).getVisualBounds();
mainStage.setX(bounds.getMinX());
mainStage.setY(bounds.getMinY());
mainStage.setWidth(bounds.getWidth());
mainStage.setHeight(bounds.getHeight());
System.out.println("thread complete");
}
});
return null;
}
}).start();
答案 2 :(得分:0)
除了@James_D的答案。正如他所提到的,Stages和Scene不应该在后台线程中添加,它们应该只在FX主线程中添加。
我在tooltips
中添加了home.fxml
,这只是PopupWindow
。因此,对于后台线程,它似乎是一个新阶段。因此它扔了IllegalStateException
。从fxml中删除tooltips
之后,fxml能够作为后台进程加载,因为在该线程中没有创建阶段。